summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/api.js17
-rw-r--r--app/assets/javascripts/ci_variable_list/ajax_variable_list.js4
-rw-r--r--app/assets/javascripts/commons/jquery.js2
-rw-r--r--app/assets/javascripts/contributors/components/contributors.vue6
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js2
-rw-r--r--app/assets/javascripts/jobs/components/environments_block.vue184
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue1
-rw-r--r--app/assets/javascripts/notes.js2
-rw-r--r--app/assets/javascripts/pages/admin/application_settings/usage_ping_payload.js6
-rw-r--r--app/assets/stylesheets/application.scss2
-rw-r--r--app/assets/stylesheets/framework/selects.scss3
-rw-r--r--app/assets/stylesheets/framework/spinner.scss6
-rw-r--r--app/models/deployment.rb1
-rw-r--r--app/serializers/build_details_entity.rb6
-rw-r--r--app/serializers/cluster_basic_entity.rb10
-rw-r--r--app/serializers/deployment_cluster_entity.rb20
-rw-r--r--app/serializers/deployment_entity.rb6
-rw-r--r--app/views/admin/application_settings/_usage.html.haml2
-rw-r--r--app/views/shared/milestones/_delete_button.html.haml2
-rw-r--r--app/views/shared/milestones/_sidebar.html.haml4
-rw-r--r--app/views/shared/milestones/_tab_loading.html.haml2
-rw-r--r--changelogs/unreleased/196678-replace-_-with-lodash.yml5
-rw-r--r--changelogs/unreleased/an-sidekiq-query.yml5
-rw-r--r--changelogs/unreleased/leipert-fix-user-label-bug.yml5
-rw-r--r--changelogs/unreleased/refactoring-entities-file-31.yml5
-rw-r--r--changelogs/unreleased/show-kubernetes-namespace-on-job-show-page.yml5
-rw-r--r--config/webpack.vendor.config.js2
-rw-r--r--doc/administration/operations/extra_sidekiq_processes.md87
-rw-r--r--doc/user/gitlab_com/index.md25
-rw-r--r--doc/user/project/issues/design_management.md5
-rw-r--r--lib/api/entities.rb33
-rw-r--r--lib/api/entities/internal_post_receive/message.rb12
-rw-r--r--lib/api/entities/internal_post_receive/response.rb12
-rw-r--r--lib/api/entities/platform/kubernetes.rb14
-rw-r--r--lib/api/entities/provider/gcp.rb17
-rw-r--r--lib/gitlab/sidekiq_config/cli_methods.rb84
-rw-r--r--locale/gitlab.pot30
-rw-r--r--package.json4
-rw-r--r--spec/factories/deployment_clusters.rb9
-rw-r--r--spec/fixtures/api/schemas/deployment.json2
-rw-r--r--spec/fixtures/api/schemas/deployment_cluster.json (renamed from spec/fixtures/api/schemas/cluster_basic.json)6
-rw-r--r--spec/fixtures/api/schemas/job/job_details.json6
-rw-r--r--spec/frontend/gfm_auto_complete_spec.js2
-rw-r--r--spec/javascripts/badges/dummy_badge.js4
-rw-r--r--spec/javascripts/jobs/components/environments_block_spec.js134
-rw-r--r--spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb111
-rw-r--r--spec/models/deployment_spec.rb1
-rw-r--r--spec/serializers/deployment_cluster_entity_spec.rb (renamed from spec/serializers/cluster_basic_entity_spec.rb)14
-rw-r--r--yarn.lock18
49 files changed, 749 insertions, 196 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 76f3020c5c2..4dc4ce543e9 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -24,6 +24,7 @@ const Api = {
projectMergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
projectMergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions',
projectRunnersPath: '/api/:version/projects/:id/runners',
+ projectProtectedBranchesPath: '/api/:version/projects/:id/protected_branches',
mergeRequestsPath: '/api/:version/merge_requests',
groupLabelsPath: '/groups/:namespace_path/-/labels',
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
@@ -220,6 +221,22 @@ const Api = {
return axios.get(url, config);
},
+ projectProtectedBranches(id, query = '') {
+ const url = Api.buildUrl(Api.projectProtectedBranchesPath).replace(
+ ':id',
+ encodeURIComponent(id),
+ );
+
+ return axios
+ .get(url, {
+ params: {
+ search: query,
+ per_page: DEFAULT_PER_PAGE,
+ },
+ })
+ .then(({ data }) => data);
+ },
+
mergeRequests(params = {}) {
const url = Api.buildUrl(Api.mergeRequestsPath);
diff --git a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
index 0bba2a2e160..da33e092086 100644
--- a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
@@ -1,4 +1,4 @@
-import _ from 'underscore';
+import { escape as esc } from 'lodash';
import axios from '../lib/utils/axios_utils';
import { s__ } from '../locale';
import Flash from '../flash';
@@ -10,7 +10,7 @@ function generateErrorBoxContent(errors) {
const errorList = [].concat(errors).map(
errorString => `
<li>
- ${_.escape(errorString)}
+ ${esc(errorString)}
</li>
`,
);
diff --git a/app/assets/javascripts/commons/jquery.js b/app/assets/javascripts/commons/jquery.js
index 2f268419bff..25640f71af2 100644
--- a/app/assets/javascripts/commons/jquery.js
+++ b/app/assets/javascripts/commons/jquery.js
@@ -4,6 +4,6 @@ import 'jquery';
import 'jquery-ujs';
import 'vendor/jquery.endless-scroll';
import 'jquery.caret'; // must be imported before at.js
-import 'at.js';
+import '@gitlab/at.js';
import 'vendor/jquery.scrollTo';
import 'jquery.waitforimages';
diff --git a/app/assets/javascripts/contributors/components/contributors.vue b/app/assets/javascripts/contributors/components/contributors.vue
index caad2a835fa..8dbf0a68c43 100644
--- a/app/assets/javascripts/contributors/components/contributors.vue
+++ b/app/assets/javascripts/contributors/components/contributors.vue
@@ -1,5 +1,5 @@
<script>
-import _ from 'underscore';
+import { debounce, uniq } from 'lodash';
import { mapActions, mapState, mapGetters } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import { GlAreaChart } from '@gitlab/ui/dist/charts';
@@ -120,7 +120,7 @@ export default {
return this.xAxisRange[this.xAxisRange.length - 1];
},
charts() {
- return _.uniq(this.individualCharts);
+ return uniq(this.individualCharts);
},
},
mounted() {
@@ -171,7 +171,7 @@ export default {
});
})
.catch(() => {});
- this.masterChart.on('datazoom', _.debounce(this.setIndividualChartsZoom, 200));
+ this.masterChart.on('datazoom', debounce(this.setIndividualChartsZoom, 200));
},
onIndividualChartCreated(chart) {
this.individualCharts.push(chart);
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index c762e298eb8..6f486e1a539 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -1,5 +1,5 @@
import $ from 'jquery';
-import 'at.js';
+import '@gitlab/at.js';
import _ from 'underscore';
import SidebarMediator from '~/sidebar/sidebar_mediator';
import glRegexp from './lib/utils/regexp';
diff --git a/app/assets/javascripts/jobs/components/environments_block.vue b/app/assets/javascripts/jobs/components/environments_block.vue
index 163849d3c40..797aa15a2b9 100644
--- a/app/assets/javascripts/jobs/components/environments_block.vue
+++ b/app/assets/javascripts/jobs/components/environments_block.vue
@@ -12,6 +12,11 @@ export default {
type: Object,
required: true,
},
+ deploymentCluster: {
+ type: Object,
+ required: false,
+ default: null,
+ },
iconStatus: {
type: Object,
required: true,
@@ -61,14 +66,14 @@ export default {
: '';
},
hasCluster() {
- return this.hasLastDeployment && this.lastDeployment.cluster;
+ return Boolean(this.deploymentCluster) && Boolean(this.deploymentCluster.name);
},
clusterNameOrLink() {
if (!this.hasCluster) {
return '';
}
- const { name, path } = this.lastDeployment.cluster;
+ const { name, path } = this.deploymentCluster;
const escapedName = _.escape(name);
const escapedPath = _.escape(path);
@@ -86,6 +91,9 @@ export default {
false,
);
},
+ kubernetesNamespace() {
+ return this.hasCluster ? this.deploymentCluster.kubernetes_namespace : null;
+ },
},
methods: {
deploymentLink(name) {
@@ -109,75 +117,153 @@ export default {
);
},
lastEnvironmentMessage() {
- const { environmentLink, clusterNameOrLink, hasCluster } = this;
-
- const message = hasCluster
- ? __('This job is deployed to %{environmentLink} using cluster %{clusterNameOrLink}.')
- : __('This job is deployed to %{environmentLink}.');
-
- return sprintf(message, { environmentLink, clusterNameOrLink }, false);
+ const { environmentLink, clusterNameOrLink, hasCluster, kubernetesNamespace } = this;
+ if (hasCluster) {
+ if (kubernetesNamespace) {
+ return sprintf(
+ __(
+ 'This job is deployed to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}.',
+ ),
+ { environmentLink, clusterNameOrLink, kubernetesNamespace },
+ false,
+ );
+ }
+ // we know the cluster but not the namespace
+ return sprintf(
+ __('This job is deployed to %{environmentLink} using cluster %{clusterNameOrLink}.'),
+ { environmentLink, clusterNameOrLink },
+ false,
+ );
+ }
+ // not a cluster deployment
+ return sprintf(__('This job is deployed to %{environmentLink}.'), { environmentLink }, false);
},
outOfDateEnvironmentMessage() {
- const { hasLastDeployment, hasCluster, environmentLink, clusterNameOrLink } = this;
+ const {
+ hasLastDeployment,
+ hasCluster,
+ environmentLink,
+ clusterNameOrLink,
+ kubernetesNamespace,
+ } = this;
if (hasLastDeployment) {
- const message = hasCluster
- ? __(
- 'This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink}. View the %{deploymentLink}.',
- )
- : __(
- 'This job is an out-of-date deployment to %{environmentLink}. View the %{deploymentLink}.',
+ const deploymentLink = this.deploymentLink(__('most recent deployment'));
+ if (hasCluster) {
+ if (kubernetesNamespace) {
+ return sprintf(
+ __(
+ 'This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}. View the %{deploymentLink}.',
+ ),
+ { environmentLink, clusterNameOrLink, kubernetesNamespace, deploymentLink },
+ false,
);
-
+ }
+ // we know the cluster but not the namespace
+ return sprintf(
+ __(
+ 'This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink}. View the %{deploymentLink}.',
+ ),
+ { environmentLink, clusterNameOrLink, deploymentLink },
+ false,
+ );
+ }
+ // not a cluster deployment
return sprintf(
- message,
- {
- environmentLink,
- clusterNameOrLink,
- deploymentLink: this.deploymentLink(__('most recent deployment')),
- },
+ __(
+ 'This job is an out-of-date deployment to %{environmentLink}. View the %{deploymentLink}.',
+ ),
+ { environmentLink, deploymentLink },
false,
);
}
-
- const message = hasCluster
- ? __(
+ // no last deployment, i.e. this is the first deployment
+ if (hasCluster) {
+ if (kubernetesNamespace) {
+ return sprintf(
+ __(
+ 'This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}.',
+ ),
+ { environmentLink, clusterNameOrLink, kubernetesNamespace },
+ false,
+ );
+ }
+ // we know the cluster but not the namespace
+ return sprintf(
+ __(
'This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink}.',
- )
- : __('This job is an out-of-date deployment to %{environmentLink}.');
-
+ ),
+ { environmentLink, clusterNameOrLink },
+ false,
+ );
+ }
+ // not a cluster deployment
return sprintf(
- message,
- {
- environmentLink,
- clusterNameOrLink,
- },
+ __('This job is an out-of-date deployment to %{environmentLink}.'),
+ { environmentLink },
false,
);
},
creatingEnvironmentMessage() {
- const { hasLastDeployment, hasCluster, environmentLink, clusterNameOrLink } = this;
+ const {
+ hasLastDeployment,
+ hasCluster,
+ environmentLink,
+ clusterNameOrLink,
+ kubernetesNamespace,
+ } = this;
if (hasLastDeployment) {
- const message = hasCluster
- ? __(
- 'This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink}. This will overwrite the %{deploymentLink}.',
- )
- : __(
- 'This job is creating a deployment to %{environmentLink}. This will overwrite the %{deploymentLink}.',
+ const deploymentLink = this.deploymentLink(__('latest deployment'));
+ if (hasCluster) {
+ if (kubernetesNamespace) {
+ return sprintf(
+ __(
+ 'This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}. This will overwrite the %{deploymentLink}.',
+ ),
+ { environmentLink, clusterNameOrLink, kubernetesNamespace, deploymentLink },
+ false,
);
-
+ }
+ // we know the cluster but not the namespace
+ return sprintf(
+ __(
+ 'This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink}. This will overwrite the %{deploymentLink}.',
+ ),
+ { environmentLink, clusterNameOrLink, deploymentLink },
+ false,
+ );
+ }
+ // not a cluster deployment
return sprintf(
- message,
- {
- environmentLink,
- clusterNameOrLink,
- deploymentLink: this.deploymentLink(__('latest deployment')),
- },
+ __(
+ 'This job is creating a deployment to %{environmentLink}. This will overwrite the %{deploymentLink}.',
+ ),
+ { environmentLink, deploymentLink },
false,
);
}
-
+ // no last deployment, i.e. this is the first deployment
+ if (hasCluster) {
+ if (kubernetesNamespace) {
+ return sprintf(
+ __(
+ 'This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}.',
+ ),
+ { environmentLink, clusterNameOrLink, kubernetesNamespace },
+ false,
+ );
+ }
+ // we know the cluster but not the namespace
+ return sprintf(
+ __(
+ 'This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink}.',
+ ),
+ { environmentLink, clusterNameOrLink },
+ false,
+ );
+ }
+ // not a cluster deployment
return sprintf(
__('This job is creating a deployment to %{environmentLink}.'),
{ environmentLink },
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index 0ca13e897f3..bc310f77a58 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -256,6 +256,7 @@ export default {
v-if="hasEnvironment"
class="js-job-environment"
:deployment-status="job.deployment_status"
+ :deployment-cluster="job.deployment_cluster"
:icon-status="job.status"
/>
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index c01024fc2cd..b3b189c1114 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -15,7 +15,7 @@ import { escape, uniqueId } from 'lodash';
import Cookies from 'js-cookie';
import Autosize from 'autosize';
import 'jquery.caret'; // required by at.js
-import 'at.js';
+import '@gitlab/at.js';
import Vue from 'vue';
import { GlSkeletonLoading } from '@gitlab/ui';
import AjaxCache from '~/lib/utils/ajax_cache';
diff --git a/app/assets/javascripts/pages/admin/application_settings/usage_ping_payload.js b/app/assets/javascripts/pages/admin/application_settings/usage_ping_payload.js
index 9a1bc46bf4a..95f4ba28b42 100644
--- a/app/assets/javascripts/pages/admin/application_settings/usage_ping_payload.js
+++ b/app/assets/javascripts/pages/admin/application_settings/usage_ping_payload.js
@@ -26,18 +26,18 @@ export default class UsagePingPayload {
requestPayload() {
if (this.isInserted) return this.showPayload();
- this.spinner.classList.add('d-inline');
+ this.spinner.classList.add('d-inline-flex');
return axios
.get(this.container.dataset.endpoint, {
responseType: 'text',
})
.then(({ data }) => {
- this.spinner.classList.remove('d-inline');
+ this.spinner.classList.remove('d-inline-flex');
this.insertPayload(data);
})
.catch(() => {
- this.spinner.classList.remove('d-inline');
+ this.spinner.classList.remove('d-inline-flex');
flash(__('Error fetching usage ping data.'));
});
}
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index e98030f1511..657e52674db 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -11,7 +11,7 @@
// like a table or typography then make changes in the framework/ directory.
// If you need to add unique style that should affect only one page - use pages/
// directory.
-@import "at.js/dist/css/jquery.atwho";
+@import "@gitlab/at.js/dist/css/jquery.atwho";
@import "dropzone/dist/basic";
@import "select2/select2";
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index bd0134a82d3..a8244219b10 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -63,7 +63,8 @@
display: block;
}
- .select2-choices {
+ .select2-choices,
+ .select2-choice {
border-color: $red-500;
}
}
diff --git a/app/assets/stylesheets/framework/spinner.scss b/app/assets/stylesheets/framework/spinner.scss
index 91fe75075dc..5e05311041c 100644
--- a/app/assets/stylesheets/framework/spinner.scss
+++ b/app/assets/stylesheets/framework/spinner.scss
@@ -49,3 +49,9 @@
@include spinner-color($white);
}
}
+
+.btn {
+ .spinner {
+ vertical-align: text-bottom;
+ }
+}
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 5450357fe1b..68f50b13a07 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -30,6 +30,7 @@ class Deployment < ApplicationRecord
validate :valid_ref, on: :create
delegate :name, to: :environment, prefix: true
+ delegate :kubernetes_namespace, to: :deployment_cluster, allow_nil: true
scope :for_environment, -> (environment) { where(environment_id: environment) }
scope :for_environment_name, -> (name) do
diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb
index 0ef71ff8af5..fe6afa4ff6f 100644
--- a/app/serializers/build_details_entity.rb
+++ b/app/serializers/build_details_entity.rb
@@ -22,6 +22,12 @@ class BuildDetailsEntity < JobEntity
end
end
+ expose :deployment_cluster, if: -> (build) { build&.deployment&.cluster } do |build, options|
+ # Until data is copied over from deployments.cluster_id, this entity must represent Deployment instead of DeploymentCluster
+ # https://gitlab.com/gitlab-org/gitlab/issues/202628
+ DeploymentClusterEntity.represent(build.deployment, options)
+ end
+
expose :artifact, if: -> (*) { can?(current_user, :read_build, build) } do
expose :download_path, if: -> (*) { build.artifacts? } do |build|
download_project_job_artifacts_path(project, build)
diff --git a/app/serializers/cluster_basic_entity.rb b/app/serializers/cluster_basic_entity.rb
deleted file mode 100644
index d104f2c8bbd..00000000000
--- a/app/serializers/cluster_basic_entity.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-class ClusterBasicEntity < Grape::Entity
- include RequestAwareEntity
-
- expose :name
- expose :path, if: -> (cluster) { can?(request.current_user, :read_cluster, cluster) } do |cluster|
- cluster.present(current_user: request.current_user).show_path
- end
-end
diff --git a/app/serializers/deployment_cluster_entity.rb b/app/serializers/deployment_cluster_entity.rb
new file mode 100644
index 00000000000..98736472b62
--- /dev/null
+++ b/app/serializers/deployment_cluster_entity.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class DeploymentClusterEntity < Grape::Entity
+ include RequestAwareEntity
+
+ # Until data is copied over from deployments.cluster_id, this entity must represent Deployment instead of DeploymentCluster
+ # https://gitlab.com/gitlab-org/gitlab/issues/202628
+
+ expose :name do |deployment|
+ deployment.cluster.name
+ end
+
+ expose :path, if: -> (deployment) { can?(request.current_user, :read_cluster, deployment.cluster) } do |deployment|
+ deployment.cluster.present(current_user: request.current_user).show_path
+ end
+
+ expose :kubernetes_namespace, if: -> (deployment) { can?(request.current_user, :read_cluster, deployment.cluster) } do |deployment|
+ deployment.kubernetes_namespace
+ end
+end
diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb
index 94773eeebd0..dc7c4654208 100644
--- a/app/serializers/deployment_entity.rb
+++ b/app/serializers/deployment_entity.rb
@@ -41,7 +41,11 @@ class DeploymentEntity < Grape::Entity
JobEntity.represent(deployment.playable_build, options.merge(only: [:play_path, :retry_path]))
end
- expose :cluster, using: ClusterBasicEntity
+ expose :cluster do |deployment, options|
+ # Until data is copied over from deployments.cluster_id, this entity must represent Deployment instead of DeploymentCluster
+ # https://gitlab.com/gitlab-org/gitlab/issues/202628
+ DeploymentClusterEntity.represent(deployment, options) unless deployment.cluster.nil?
+ end
private
diff --git a/app/views/admin/application_settings/_usage.html.haml b/app/views/admin/application_settings/_usage.html.haml
index b4fe1bbf028..9421585b70c 100644
--- a/app/views/admin/application_settings/_usage.html.haml
+++ b/app/views/admin/application_settings/_usage.html.haml
@@ -26,7 +26,7 @@
%p.mb-2= s_('%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc.').html_safe % { usage_ping_link_start: usage_ping_link_start, usage_ping_link_end: '</a>'.html_safe }
%button.btn.js-usage-ping-payload-trigger{ type: 'button' }
- .js-spinner.d-none= icon('spinner spin')
+ .spinner.js-spinner.d-none
.js-text.d-inline= _('Preview payload')
%pre.usage-data.js-usage-ping-payload.js-syntax-highlight.code.highlight.mt-2.d-none{ data: { endpoint: usage_data_admin_application_settings_path(format: :html) } }
- else
diff --git a/app/views/shared/milestones/_delete_button.html.haml b/app/views/shared/milestones/_delete_button.html.haml
index e236c24b088..e00a10398d3 100644
--- a/app/views/shared/milestones/_delete_button.html.haml
+++ b/app/views/shared/milestones/_delete_button.html.haml
@@ -9,6 +9,6 @@
milestone_merge_request_count: @milestone.merge_requests.count },
disabled: true }
= _('Delete')
- = icon('spin spinner', class: 'js-loading-icon hidden' )
+ .spinner.js-loading-icon.hidden
#delete-milestone-modal
diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml
index fbbcc4f3e68..a6fb8e6d4fc 100644
--- a/app/views/shared/milestones/_sidebar.html.haml
+++ b/app/views/shared/milestones/_sidebar.html.haml
@@ -98,10 +98,6 @@
human_time_estimate: @milestone.human_total_issue_time_estimate,
human_time_spent: @milestone.human_total_issue_time_spent,
limit_to_hours: Gitlab::CurrentSettings.time_tracking_limit_to_hours.to_s } }
- // Fallback while content is loading
- .title.hide-collapsed
- = _('Time tracking')
- = icon('spinner spin')
= render_if_exists 'shared/milestones/weight', milestone: milestone
diff --git a/app/views/shared/milestones/_tab_loading.html.haml b/app/views/shared/milestones/_tab_loading.html.haml
index 68458c2d0aa..dfca6a184be 100644
--- a/app/views/shared/milestones/_tab_loading.html.haml
+++ b/app/views/shared/milestones/_tab_loading.html.haml
@@ -1,2 +1,2 @@
.text-center.prepend-top-default
- = icon('spin spinner 2x', 'aria-hidden': 'true', 'aria-label': 'Loading tab content')
+ .spinner.spinner-md
diff --git a/changelogs/unreleased/196678-replace-_-with-lodash.yml b/changelogs/unreleased/196678-replace-_-with-lodash.yml
new file mode 100644
index 00000000000..fec7b254e9d
--- /dev/null
+++ b/changelogs/unreleased/196678-replace-_-with-lodash.yml
@@ -0,0 +1,5 @@
+---
+title: Replaced underscore with lodash for spec/javascripts/badges
+merge_request: 25135
+author: Shubham Pandey
+type: other
diff --git a/changelogs/unreleased/an-sidekiq-query.yml b/changelogs/unreleased/an-sidekiq-query.yml
new file mode 100644
index 00000000000..b0e5239984a
--- /dev/null
+++ b/changelogs/unreleased/an-sidekiq-query.yml
@@ -0,0 +1,5 @@
+---
+title: Add experimental --queue-selector option to sidekiq-cluster
+merge_request: 18877
+author:
+type: changed
diff --git a/changelogs/unreleased/leipert-fix-user-label-bug.yml b/changelogs/unreleased/leipert-fix-user-label-bug.yml
new file mode 100644
index 00000000000..0e1e23bbf0c
--- /dev/null
+++ b/changelogs/unreleased/leipert-fix-user-label-bug.yml
@@ -0,0 +1,5 @@
+---
+title: Fix autocomplete limitation bug
+merge_request: 25167
+author:
+type: fixed
diff --git a/changelogs/unreleased/refactoring-entities-file-31.yml b/changelogs/unreleased/refactoring-entities-file-31.yml
new file mode 100644
index 00000000000..83d0eff2758
--- /dev/null
+++ b/changelogs/unreleased/refactoring-entities-file-31.yml
@@ -0,0 +1,5 @@
+---
+title: Separate provider, platform and post receive entities into own class files
+merge_request: 25119
+author: Rajendra Kadam
+type: added
diff --git a/changelogs/unreleased/show-kubernetes-namespace-on-job-show-page.yml b/changelogs/unreleased/show-kubernetes-namespace-on-job-show-page.yml
new file mode 100644
index 00000000000..10fbb7b7d13
--- /dev/null
+++ b/changelogs/unreleased/show-kubernetes-namespace-on-job-show-page.yml
@@ -0,0 +1,5 @@
+---
+title: Show Kubernetes namespace on job show page
+merge_request: 20983
+author:
+type: added
diff --git a/config/webpack.vendor.config.js b/config/webpack.vendor.config.js
index bddbf067d7c..8da4938191c 100644
--- a/config/webpack.vendor.config.js
+++ b/config/webpack.vendor.config.js
@@ -29,7 +29,7 @@ module.exports = {
'vuex',
'pikaday',
'vue/dist/vue.esm.js',
- 'at.js',
+ '@gitlab/at.js',
'jed',
'mermaid',
'katex',
diff --git a/doc/administration/operations/extra_sidekiq_processes.md b/doc/administration/operations/extra_sidekiq_processes.md
index 5cdd33ba507..d70e9d1baa5 100644
--- a/doc/administration/operations/extra_sidekiq_processes.md
+++ b/doc/administration/operations/extra_sidekiq_processes.md
@@ -82,6 +82,93 @@ you list:
sudo gitlab-ctl reconfigure
```
+## Queue selector (experimental)
+
+> [Introduced](https://gitlab.com/gitlab-com/gl-infra/scalability/issues/45) in [GitLab Starter](https://about.gitlab.com/pricing/) 12.8.
+
+CAUTION: **Caution:**
+As this is marked as **experimental**, it is subject to change at any
+time, including **breaking backwards compatibility**. This is so that we
+can react to changes we need for our GitLab.com deployment. We have a
+tracking issue open to [remove the experimental
+designation](https://gitlab.com/gitlab-com/gl-infra/scalability/issues/147)
+from this feature; please comment there if you are interested in using
+this in your own deployment.
+
+In addition to selecting queues by name, as above, the
+`experimental_queue_selector` option allows queue groups to be selected
+in a more general way using the following components:
+
+- Attributes that can be selected.
+- Operators used to construct a query.
+
+### Available attributes
+
+From the [list of all available
+attributes](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/workers/all_queues.yml),
+`experimental_queue_selector` allows selecting of queues by the
+following attributes:
+
+- `feature_category` - the [GitLab feature
+ category](https://about.gitlab.com/direction/maturity/#category-maturity) the
+ queue belongs to. For example, the `merge` queue belongs to the
+ `source_code_management` category.
+- `has_external_dependencies` - whether or not the queue connects to external
+ services. For example, all importers have this set to `true`.
+- `latency_sensitive` - whether or not the queue is particularly sensitive to
+ latency, which also means that its jobs should run quickly. For example, the
+ `authorized_projects` queue is used to refresh user permissions, and is
+ latency sensitive.
+- `name` - the queue name. The other attributes are typically more useful as
+ they are more general, but this is available in case a particular queue needs
+ to be selected.
+- `resource_boundary` - if the worker is bound by `cpu`, `memory`, or
+ `unknown`. For example, the `project_export` queue is memory bound as it has
+ to load data in memory before saving it for export.
+
+Both `has_external_dependencies` and `latency_sensitive` are boolean attributes:
+only the exact string `true` is considered true, and everything else is
+considered false.
+
+### Available operators
+
+`experimental_queue_selector` supports the following operators, listed
+from highest to lowest precedence:
+
+- `|` - the logical OR operator. For example, `query_a|query_b` (where `query_a`
+ and `query_b` are queries made up of the other operators here) will include
+ queues that match either query.
+- `&` - the logical AND operator. For example, `query_a&query_b` (where
+ `query_a` and `query_b` are queries made up of the other operators here) will
+ only include queues that match both queries.
+- `!=` - the NOT IN operator. For example, `feature_category!=issue_tracking`
+ excludes all queues from the `issue_tracking` feature category.
+- `=` - the IN operator. For example, `resource_boundary=cpu` includes all
+ queues that are CPU bound.
+- `,` - the concatenate set operator. For example,
+ `feature_category=continuous_integration,pages` includes all queues from
+ either the `continuous_integration` category or the `pages` category. This
+ example is also possible using the OR operator, but allows greater brevity, as
+ well as being lower precedence.
+
+The operator precedence for this syntax is fixed: it's not possible to make AND
+have higher precedence than OR.
+
+### Example queries
+
+In `/etc/gitlab/gitlab.rb`:
+
+```ruby
+sidekiq_cluster['enable'] = true
+sidekiq_cluster['experimental_queue_selector'] = true
+sidekiq_cluster['queue_groups'] = [
+ # Run all non-CPU-bound queues that are latency sensitive
+ 'resource_boundary!=cpu&latency_sensitive=true',
+ # Run all continuous integration and pages queues that are not latency sensitive
+ 'feature_category=continuous_integration,pages&latency_sensitive=false'
+]
+```
+
## Ignore all GitHub import queues
When [importing from GitHub](../../user/project/import/github.md), Sidekiq might
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index 03e4fbb3b3f..7d02346af67 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -198,11 +198,11 @@ The Windows Shared Runners are currently in
[beta](https://about.gitlab.com/handbook/product/#beta) and should not be used
for production workloads.
-During the beta period for groups and private projects the use of
-Windows Shared Runners will count towards the [shared runner pipeline
-quota](https://docs.gitlab.com/ee/user/admin_area/settings/continuous_integration.html#shared-runners-pipeline-minutes-quota-starter-only)
-as if they are Linux Runners, we do have plans to change this in
-[#30835](https://gitlab.com/gitlab-org/gitlab/issues/30834).
+During the beta period, the
+[shared runner pipeline quota](../admin_area/settings/continuous_integration.md#shared-runners-pipeline-minutes-quota-starter-only)
+will apply for groups and projects in the same way as Linux Runners.
+This may change when the beta period ends, as discussed in this
+[related issue](https://gitlab.com/gitlab-org/gitlab/issues/30834).
Windows Shared Runners on GitLab.com automatically autoscale by
launching virtual machines on the Google Cloud Platform. This solution uses
@@ -321,18 +321,17 @@ test:
- All the limitations mentioned in our [beta
definition](https://about.gitlab.com/handbook/product/#beta).
- The average provisioning time for a new Windows VM is 5 minutes.
- This means that for the beta you will notice slower build start times
- on the Windows Shared Runner fleet compared to Linux. In a future
- release we will add the ability to the autoscaler which will enable
- the pre-warming of virtual machines. This will significantly reduce
+ This means that you may notice slower build start times
+ on the Windows Shared Runner fleet during the beta. In a future
+ release we will update the autoscaler to enable
+ the pre-provisioning of virtual machines. This will significantly reduce
the time it takes to provision a VM on the Windows fleet. You can
- follow along in this
- [issue](https://gitlab.com/gitlab-org/ci-cd/custom-executor-drivers/autoscaler/issues/32).
+ follow along in the [related issue](https://gitlab.com/gitlab-org/ci-cd/custom-executor-drivers/autoscaler/issues/32).
- The Windows Shared Runner fleet may be unavailable occasionally
for maintenance or updates.
- The Windows Shared Runner virtual machine instances do not use the
- GitLab Docker executor. This means that unlike the Linux Shared
- Runners, you will not be able to specify `image` and `services` in
+ GitLab Docker executor. This means that you will not be able to specify
+ [`image`](../../ci/yaml/README.md#image) or [`services`](../../ci/yaml/README.md#services) in
your pipeline configuration.
- For the beta release, we have included a set of software packages in
the base VM image. If your CI job requires additional software that's
diff --git a/doc/user/project/issues/design_management.md b/doc/user/project/issues/design_management.md
index 80075efe4ac..627aeac54d6 100644
--- a/doc/user/project/issues/design_management.md
+++ b/doc/user/project/issues/design_management.md
@@ -130,7 +130,7 @@ Once selected, click the **Delete selected** button to confirm the deletion:
![Delete multiple designs](img/delete_multiple_designs_v12_4.png)
-NOTE: **Note:**
+**Note:**
Only the latest version of the designs can be deleted.
Deleted designs are not permanently lost; they can be
viewed by browsing previous versions.
@@ -144,6 +144,9 @@ which you can start a new discussion:
![Starting a new discussion on design](img/adding_note_to_design_1.png)
+From GitLab 12.8 on, when you are starting a new discussion, you can adjust the badge's position by
+dragging it around the image.
+
Different discussions have different badge numbers:
![Discussions on design annotations](img/adding_note_to_design_2.png)
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index b9805973c54..df7c7b15aeb 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -243,39 +243,6 @@ module API
expose :startline
expose :project_id
end
-
- module Platform
- class Kubernetes < Grape::Entity
- expose :api_url
- expose :namespace
- expose :authorization_type
- expose :ca_cert
- end
- end
-
- module Provider
- class Gcp < Grape::Entity
- expose :cluster_id
- expose :status_name
- expose :gcp_project_id
- expose :zone
- expose :machine_type
- expose :num_nodes
- expose :endpoint
- end
- end
-
- module InternalPostReceive
- class Message < Grape::Entity
- expose :message
- expose :type
- end
-
- class Response < Grape::Entity
- expose :messages, using: Message
- expose :reference_counter_decreased
- end
- end
end
end
diff --git a/lib/api/entities/internal_post_receive/message.rb b/lib/api/entities/internal_post_receive/message.rb
new file mode 100644
index 00000000000..3cfefa84d9b
--- /dev/null
+++ b/lib/api/entities/internal_post_receive/message.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module InternalPostReceive
+ class Message < Grape::Entity
+ expose :message
+ expose :type
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/internal_post_receive/response.rb b/lib/api/entities/internal_post_receive/response.rb
new file mode 100644
index 00000000000..c33418ed658
--- /dev/null
+++ b/lib/api/entities/internal_post_receive/response.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module InternalPostReceive
+ class Response < Grape::Entity
+ expose :messages, using: Entities::InternalPostReceive::Message
+ expose :reference_counter_decreased
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/platform/kubernetes.rb b/lib/api/entities/platform/kubernetes.rb
new file mode 100644
index 00000000000..eeb6d57bb8f
--- /dev/null
+++ b/lib/api/entities/platform/kubernetes.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Platform
+ class Kubernetes < Grape::Entity
+ expose :api_url
+ expose :namespace
+ expose :authorization_type
+ expose :ca_cert
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/provider/gcp.rb b/lib/api/entities/provider/gcp.rb
new file mode 100644
index 00000000000..85f56a9ac1e
--- /dev/null
+++ b/lib/api/entities/provider/gcp.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Provider
+ class Gcp < Grape::Entity
+ expose :cluster_id
+ expose :status_name
+ expose :gcp_project_id
+ expose :zone
+ expose :machine_type
+ expose :num_nodes
+ expose :endpoint
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_config/cli_methods.rb b/lib/gitlab/sidekiq_config/cli_methods.rb
index 0676e9df9c5..8f19b557d24 100644
--- a/lib/gitlab/sidekiq_config/cli_methods.rb
+++ b/lib/gitlab/sidekiq_config/cli_methods.rb
@@ -18,17 +18,39 @@ module Gitlab
result
end.freeze
- def worker_queues(rails_path = Rails.root.to_s)
+ QUERY_OR_OPERATOR = '|'
+ QUERY_AND_OPERATOR = '&'
+ QUERY_CONCATENATE_OPERATOR = ','
+ QUERY_TERM_REGEX = %r{^(\w+)(!?=)([\w#{QUERY_CONCATENATE_OPERATOR}]+)}.freeze
+
+ QUERY_PREDICATES = {
+ feature_category: :to_sym,
+ has_external_dependencies: lambda { |value| value == 'true' },
+ latency_sensitive: lambda { |value| value == 'true' },
+ name: :to_s,
+ resource_boundary: :to_sym
+ }.freeze
+
+ QueryError = Class.new(StandardError)
+ InvalidTerm = Class.new(QueryError)
+ UnknownOperator = Class.new(QueryError)
+ UnknownPredicate = Class.new(QueryError)
+
+ def all_queues(rails_path = Rails.root.to_s)
@worker_queues ||= {}
@worker_queues[rails_path] ||= QUEUE_CONFIG_PATHS.flat_map do |path|
full_path = File.join(rails_path, path)
- queues = File.exist?(full_path) ? YAML.load_file(full_path) : []
- # https://gitlab.com/gitlab-org/gitlab/issues/199230
- queues.map { |queue| queue.is_a?(Hash) ? queue[:name] : queue }
+ File.exist?(full_path) ? YAML.load_file(full_path) : []
end
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
+ def worker_queues(rails_path = Rails.root.to_s)
+ # https://gitlab.com/gitlab-org/gitlab/issues/199230
+ worker_names(all_queues(rails_path))
+ end
def expand_queues(queues, all_queues = self.worker_queues)
return [] if queues.empty?
@@ -40,12 +62,64 @@ module Gitlab
end
end
+ def query_workers(query_string, queues)
+ worker_names(queues.select(&query_string_to_lambda(query_string)))
+ end
+
def clear_memoization!
if instance_variable_defined?('@worker_queues')
remove_instance_variable('@worker_queues')
end
end
- # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
+ private
+
+ def worker_names(workers)
+ workers.map { |queue| queue.is_a?(Hash) ? queue[:name] : queue }
+ end
+
+ def query_string_to_lambda(query_string)
+ or_clauses = query_string.split(QUERY_OR_OPERATOR).map do |and_clauses_string|
+ and_clauses_predicates = and_clauses_string.split(QUERY_AND_OPERATOR).map do |term|
+ predicate_for_term(term)
+ end
+
+ lambda { |worker| and_clauses_predicates.all? { |predicate| predicate.call(worker) } }
+ end
+
+ lambda { |worker| or_clauses.any? { |predicate| predicate.call(worker) } }
+ end
+
+ def predicate_for_term(term)
+ match = term.match(QUERY_TERM_REGEX)
+
+ raise InvalidTerm.new("Invalid term: #{term}") unless match
+
+ _, lhs, op, rhs = *match
+
+ predicate_for_op(op, predicate_factory(lhs, rhs.split(QUERY_CONCATENATE_OPERATOR)))
+ end
+
+ def predicate_for_op(op, predicate)
+ case op
+ when '='
+ predicate
+ when '!='
+ lambda { |worker| !predicate.call(worker) }
+ else
+ # This is unreachable because InvalidTerm will be raised instead, but
+ # keeping it allows to guard against that changing in future.
+ raise UnknownOperator.new("Unknown operator: #{op}")
+ end
+ end
+
+ def predicate_factory(lhs, values)
+ values_block = QUERY_PREDICATES[lhs.to_sym]
+
+ raise UnknownPredicate.new("Unknown predicate: #{lhs}") unless values_block
+
+ lambda { |queue| values.map(&values_block).include?(queue[lhs.to_sym]) }
+ end
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 8b695e1b127..cfb1e7d8ca2 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1958,6 +1958,9 @@ msgstr ""
msgid "Any Milestone"
msgstr ""
+msgid "Any branch"
+msgstr ""
+
msgid "Any eligible user"
msgstr ""
@@ -2033,6 +2036,9 @@ msgstr ""
msgid "Apply template"
msgstr ""
+msgid "Apply this approval rule to any branch or a specific protected branch."
+msgstr ""
+
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr ""
@@ -2086,6 +2092,9 @@ msgstr ""
msgid "ApprovalRule|Rule name"
msgstr ""
+msgid "ApprovalRule|Target branch"
+msgstr ""
+
msgid "ApprovalRule|e.g. QA, Security, etc."
msgstr ""
@@ -13999,6 +14008,9 @@ msgstr ""
msgid "Please select a group."
msgstr ""
+msgid "Please select a valid target branch"
+msgstr ""
+
msgid "Please select and add a member"
msgstr ""
@@ -19587,6 +19599,12 @@ msgstr ""
msgid "This job has not started yet"
msgstr ""
+msgid "This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}."
+msgstr ""
+
+msgid "This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}. View the %{deploymentLink}."
+msgstr ""
+
msgid "This job is an out-of-date deployment to %{environmentLink} using cluster %{clusterNameOrLink}."
msgstr ""
@@ -19602,6 +19620,15 @@ msgstr ""
msgid "This job is archived. Only the complete pipeline can be retried."
msgstr ""
+msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}. This will overwrite the %{deploymentLink}."
+msgstr ""
+
+msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink}."
+msgstr ""
+
msgid "This job is creating a deployment to %{environmentLink} using cluster %{clusterNameOrLink}. This will overwrite the %{deploymentLink}."
msgstr ""
@@ -19611,6 +19638,9 @@ msgstr ""
msgid "This job is creating a deployment to %{environmentLink}. This will overwrite the %{deploymentLink}."
msgstr ""
+msgid "This job is deployed to %{environmentLink} using cluster %{clusterNameOrLink} and namespace %{kubernetesNamespace}."
+msgstr ""
+
msgid "This job is deployed to %{environmentLink} using cluster %{clusterNameOrLink}."
msgstr ""
diff --git a/package.json b/package.json
index 3a5f4edb4ef..4a0d2cde754 100644
--- a/package.json
+++ b/package.json
@@ -39,8 +39,9 @@
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-syntax-import-meta": "^7.2.0",
"@babel/preset-env": "^7.6.2",
+ "@gitlab/at.js": "^1.5.5",
"@gitlab/svgs": "^1.96.0",
- "@gitlab/ui": "^9.6.0",
+ "@gitlab/ui": "^9.8.0",
"@gitlab/visual-review-tools": "1.5.1",
"@sentry/browser": "^5.10.2",
"@sourcegraph/code-host-integration": "0.0.30",
@@ -49,7 +50,6 @@
"apollo-link": "^1.2.11",
"apollo-link-batch-http": "^1.2.11",
"apollo-upload-client": "^10.0.0",
- "at.js": "^1.5.4",
"autosize": "^4.0.0",
"aws-sdk": "^2.526.0",
"axios": "^0.19.0",
diff --git a/spec/factories/deployment_clusters.rb b/spec/factories/deployment_clusters.rb
new file mode 100644
index 00000000000..1bdfff79aaf
--- /dev/null
+++ b/spec/factories/deployment_clusters.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :deployment_cluster, class: 'DeploymentCluster' do
+ cluster
+ deployment
+ kubernetes_namespace { 'the-namespace' }
+ end
+end
diff --git a/spec/fixtures/api/schemas/deployment.json b/spec/fixtures/api/schemas/deployment.json
index 0cfeadfe548..ac37dd084d3 100644
--- a/spec/fixtures/api/schemas/deployment.json
+++ b/spec/fixtures/api/schemas/deployment.json
@@ -50,7 +50,7 @@
"cluster": {
"oneOf": [
{ "type": "null" },
- { "$ref": "cluster_basic.json" }
+ { "$ref": "deployment_cluster.json" }
]
},
"manual_actions": {
diff --git a/spec/fixtures/api/schemas/cluster_basic.json b/spec/fixtures/api/schemas/deployment_cluster.json
index 6f0e77997f0..86497f98dcb 100644
--- a/spec/fixtures/api/schemas/cluster_basic.json
+++ b/spec/fixtures/api/schemas/deployment_cluster.json
@@ -10,6 +10,12 @@
{ "type": "null" },
{ "type": "string" }
]
+ },
+ "kubernetes_namespace": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "string" }
+ ]
}
},
"additionalProperties": false
diff --git a/spec/fixtures/api/schemas/job/job_details.json b/spec/fixtures/api/schemas/job/job_details.json
index cdf7b049ab6..ae05ecea9ef 100644
--- a/spec/fixtures/api/schemas/job/job_details.json
+++ b/spec/fixtures/api/schemas/job/job_details.json
@@ -15,6 +15,12 @@
"terminal_path": { "type": "string" },
"trigger": { "$ref": "trigger.json" },
"deployment_status": { "$ref": "deployment_status.json" },
+ "deployment_cluster": {
+ "oneOf": [
+ { "$ref": "../deployment_cluster.json" },
+ { "type": "null" }
+ ]
+ },
"runner": { "$ref": "runner.json" },
"runners": { "$ref": "runners.json" },
"has_trace": { "type": "boolean" },
diff --git a/spec/frontend/gfm_auto_complete_spec.js b/spec/frontend/gfm_auto_complete_spec.js
index 99869c46f3f..f58615000ee 100644
--- a/spec/frontend/gfm_auto_complete_spec.js
+++ b/spec/frontend/gfm_auto_complete_spec.js
@@ -4,7 +4,7 @@ import $ from 'jquery';
import GfmAutoComplete, { membersBeforeSave } from 'ee_else_ce/gfm_auto_complete';
import 'jquery.caret';
-import 'at.js';
+import '@gitlab/at.js';
import { TEST_HOST } from 'helpers/test_constants';
import { getJSONFixture } from 'helpers/fixtures';
diff --git a/spec/javascripts/badges/dummy_badge.js b/spec/javascripts/badges/dummy_badge.js
index e8a460cdc76..a0dee89736e 100644
--- a/spec/javascripts/badges/dummy_badge.js
+++ b/spec/javascripts/badges/dummy_badge.js
@@ -1,9 +1,9 @@
-import _ from 'underscore';
+import { uniqueId } from 'lodash';
import { DUMMY_IMAGE_URL, TEST_HOST } from 'spec/test_constants';
import { PROJECT_BADGE } from '~/badges/constants';
export const createDummyBadge = () => {
- const id = _.uniqueId();
+ const id = uniqueId();
return {
id,
name: 'TestBadge',
diff --git a/spec/javascripts/jobs/components/environments_block_spec.js b/spec/javascripts/jobs/components/environments_block_spec.js
index 64a59d659a7..4f2359e83b6 100644
--- a/spec/javascripts/jobs/components/environments_block_spec.js
+++ b/spec/javascripts/jobs/components/environments_block_spec.js
@@ -4,6 +4,7 @@ import mountComponent from '../../helpers/vue_mount_component_helper';
const TEST_CLUSTER_NAME = 'test_cluster';
const TEST_CLUSTER_PATH = 'path/to/test_cluster';
+const TEST_KUBERNETES_NAMESPACE = 'this-is-a-kubernetes-namespace';
describe('Environments block', () => {
const Component = Vue.extend(component);
@@ -28,17 +29,18 @@ describe('Environments block', () => {
last_deployment: { ...lastDeployment },
});
- const createEnvironmentWithCluster = () => ({
- ...environment,
- last_deployment: {
- ...lastDeployment,
- cluster: { name: TEST_CLUSTER_NAME, path: TEST_CLUSTER_PATH },
- },
+ const createDeploymentWithCluster = () => ({ name: TEST_CLUSTER_NAME, path: TEST_CLUSTER_PATH });
+
+ const createDeploymentWithClusterAndKubernetesNamespace = () => ({
+ name: TEST_CLUSTER_NAME,
+ path: TEST_CLUSTER_PATH,
+ kubernetes_namespace: TEST_KUBERNETES_NAMESPACE,
});
- const createComponent = (deploymentStatus = {}) => {
+ const createComponent = (deploymentStatus = {}, deploymentCluster = {}) => {
vm = mountComponent(Component, {
deploymentStatus,
+ deploymentCluster,
iconStatus: status,
});
};
@@ -62,15 +64,36 @@ describe('Environments block', () => {
expect(findText()).toEqual('This job is deployed to environment.');
});
- it('renders info with cluster', () => {
- createComponent({
- status: 'last',
- environment: createEnvironmentWithCluster(),
+ describe('when there is a cluster', () => {
+ it('renders info with cluster', () => {
+ createComponent(
+ {
+ status: 'last',
+ environment: createEnvironmentWithLastDeployment(),
+ },
+ createDeploymentWithCluster(),
+ );
+
+ expect(findText()).toEqual(
+ `This job is deployed to environment using cluster ${TEST_CLUSTER_NAME}.`,
+ );
});
- expect(findText()).toEqual(
- `This job is deployed to environment using cluster ${TEST_CLUSTER_NAME}.`,
- );
+ describe('when there is a kubernetes namespace', () => {
+ it('renders info with cluster', () => {
+ createComponent(
+ {
+ status: 'last',
+ environment: createEnvironmentWithLastDeployment(),
+ },
+ createDeploymentWithClusterAndKubernetesNamespace(),
+ );
+
+ expect(findText()).toEqual(
+ `This job is deployed to environment using cluster ${TEST_CLUSTER_NAME} and namespace ${TEST_KUBERNETES_NAMESPACE}.`,
+ );
+ });
+ });
});
});
@@ -89,15 +112,36 @@ describe('Environments block', () => {
expect(findJobDeploymentLink().getAttribute('href')).toEqual('bar');
});
- it('renders info with cluster', () => {
- createComponent({
- status: 'out_of_date',
- environment: createEnvironmentWithCluster(),
+ describe('when there is a cluster', () => {
+ it('renders info with cluster', () => {
+ createComponent(
+ {
+ status: 'out_of_date',
+ environment: createEnvironmentWithLastDeployment(),
+ },
+ createDeploymentWithCluster(),
+ );
+
+ expect(findText()).toEqual(
+ `This job is an out-of-date deployment to environment using cluster ${TEST_CLUSTER_NAME}. View the most recent deployment.`,
+ );
});
- expect(findText()).toEqual(
- `This job is an out-of-date deployment to environment using cluster ${TEST_CLUSTER_NAME}. View the most recent deployment.`,
- );
+ describe('when there is a kubernetes namespace', () => {
+ it('renders info with cluster', () => {
+ createComponent(
+ {
+ status: 'out_of_date',
+ environment: createEnvironmentWithLastDeployment(),
+ },
+ createDeploymentWithClusterAndKubernetesNamespace(),
+ );
+
+ expect(findText()).toEqual(
+ `This job is an out-of-date deployment to environment using cluster ${TEST_CLUSTER_NAME} and namespace ${TEST_KUBERNETES_NAMESPACE}. View the most recent deployment.`,
+ );
+ });
+ });
});
});
@@ -143,7 +187,7 @@ describe('Environments block', () => {
});
describe('without last deployment', () => {
- it('renders info about failed deployment', () => {
+ it('renders info about deployment being created', () => {
createComponent({
status: 'creating',
environment,
@@ -151,6 +195,22 @@ describe('Environments block', () => {
expect(findText()).toEqual('This job is creating a deployment to environment.');
});
+
+ describe('when there is a cluster', () => {
+ it('inclues information about the cluster', () => {
+ createComponent(
+ {
+ status: 'creating',
+ environment,
+ },
+ createDeploymentWithCluster(),
+ );
+
+ expect(findText()).toEqual(
+ `This job is creating a deployment to environment using cluster ${TEST_CLUSTER_NAME}.`,
+ );
+ });
+ });
});
describe('without environment', () => {
@@ -167,10 +227,13 @@ describe('Environments block', () => {
describe('with a cluster', () => {
it('renders the cluster link', () => {
- createComponent({
- status: 'last',
- environment: createEnvironmentWithCluster(),
- });
+ createComponent(
+ {
+ status: 'last',
+ environment: createEnvironmentWithLastDeployment(),
+ },
+ createDeploymentWithCluster(),
+ );
expect(findText()).toEqual(
`This job is deployed to environment using cluster ${TEST_CLUSTER_NAME}.`,
@@ -181,18 +244,13 @@ describe('Environments block', () => {
describe('when the cluster is missing the path', () => {
it('renders the name without a link', () => {
- const cluster = {
- name: 'the-cluster',
- };
- createComponent({
- status: 'last',
- environment: Object.assign({}, environment, {
- last_deployment: {
- ...lastDeployment,
- cluster,
- },
- }),
- });
+ createComponent(
+ {
+ status: 'last',
+ environment: createEnvironmentWithLastDeployment(),
+ },
+ { name: 'the-cluster' },
+ );
expect(findText()).toContain('using cluster the-cluster.');
diff --git a/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb b/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb
index 60d946db744..e6d0055df64 100644
--- a/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb
+++ b/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'fast_spec_helper'
+require 'rspec-parameterized'
describe Gitlab::SidekiqConfig::CliMethods do
let(:dummy_root) { '/tmp/' }
@@ -82,7 +83,7 @@ describe Gitlab::SidekiqConfig::CliMethods do
end
describe '.expand_queues' do
- let(:all_queues) do
+ let(:worker_queues) do
['cronjob:stuck_import_jobs', 'cronjob:stuck_merge_jobs', 'post_receive']
end
@@ -92,25 +93,125 @@ describe Gitlab::SidekiqConfig::CliMethods do
expect(described_class.expand_queues(['cronjob']))
.to contain_exactly('cronjob')
- allow(described_class).to receive(:worker_queues).and_return(all_queues)
+ allow(described_class).to receive(:worker_queues).and_return(worker_queues)
expect(described_class.expand_queues(['cronjob']))
.to contain_exactly('cronjob', 'cronjob:stuck_import_jobs', 'cronjob:stuck_merge_jobs')
end
it 'expands queue namespaces to concrete queue names' do
- expect(described_class.expand_queues(['cronjob'], all_queues))
+ expect(described_class.expand_queues(['cronjob'], worker_queues))
.to contain_exactly('cronjob', 'cronjob:stuck_import_jobs', 'cronjob:stuck_merge_jobs')
end
it 'lets concrete queue names pass through' do
- expect(described_class.expand_queues(['post_receive'], all_queues))
+ expect(described_class.expand_queues(['post_receive'], worker_queues))
.to contain_exactly('post_receive')
end
it 'lets unknown queues pass through' do
- expect(described_class.expand_queues(['unknown'], all_queues))
+ expect(described_class.expand_queues(['unknown'], worker_queues))
.to contain_exactly('unknown')
end
end
+
+ describe '.query_workers' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:queues) do
+ [
+ {
+ name: 'a',
+ feature_category: :category_a,
+ has_external_dependencies: false,
+ latency_sensitive: false,
+ resource_boundary: :cpu
+ },
+ {
+ name: 'a_2',
+ feature_category: :category_a,
+ has_external_dependencies: false,
+ latency_sensitive: true,
+ resource_boundary: :none
+ },
+ {
+ name: 'b',
+ feature_category: :category_b,
+ has_external_dependencies: true,
+ latency_sensitive: true,
+ resource_boundary: :memory
+ },
+ {
+ name: 'c',
+ feature_category: :category_c,
+ has_external_dependencies: false,
+ latency_sensitive: false,
+ resource_boundary: :memory
+ }
+ ]
+ end
+
+ context 'with valid input' do
+ where(:query, :selected_queues) do
+ # feature_category
+ 'feature_category=category_a' | %w(a a_2)
+ 'feature_category=category_a,category_c' | %w(a a_2 c)
+ 'feature_category=category_a|feature_category=category_c' | %w(a a_2 c)
+ 'feature_category!=category_a' | %w(b c)
+
+ # has_external_dependencies
+ 'has_external_dependencies=true' | %w(b)
+ 'has_external_dependencies=false' | %w(a a_2 c)
+ 'has_external_dependencies=true,false' | %w(a a_2 b c)
+ 'has_external_dependencies=true|has_external_dependencies=false' | %w(a a_2 b c)
+ 'has_external_dependencies!=true' | %w(a a_2 c)
+
+ # latency_sensitive
+ 'latency_sensitive=true' | %w(a_2 b)
+ 'latency_sensitive=false' | %w(a c)
+ 'latency_sensitive=true,false' | %w(a a_2 b c)
+ 'latency_sensitive=true|latency_sensitive=false' | %w(a a_2 b c)
+ 'latency_sensitive!=true' | %w(a c)
+
+ # name
+ 'name=a' | %w(a)
+ 'name=a,b' | %w(a b)
+ 'name=a,a_2|name=b' | %w(a a_2 b)
+ 'name!=a,a_2' | %w(b c)
+
+ # resource_boundary
+ 'resource_boundary=memory' | %w(b c)
+ 'resource_boundary=memory,cpu' | %w(a b c)
+ 'resource_boundary=memory|resource_boundary=cpu' | %w(a b c)
+ 'resource_boundary!=memory,cpu' | %w(a_2)
+
+ # combinations
+ 'feature_category=category_a&latency_sensitive=true' | %w(a_2)
+ 'feature_category=category_a&latency_sensitive=true|feature_category=category_c' | %w(a_2 c)
+ end
+
+ with_them do
+ it do
+ expect(described_class.query_workers(query, queues))
+ .to match_array(selected_queues)
+ end
+ end
+ end
+
+ context 'with invalid input' do
+ where(:query, :error) do
+ 'feature_category="category_a"' | described_class::InvalidTerm
+ 'feature_category=' | described_class::InvalidTerm
+ 'feature_category~category_a' | described_class::InvalidTerm
+ 'worker_name=a' | described_class::UnknownPredicate
+ end
+
+ with_them do
+ it do
+ expect { described_class.query_workers(query, queues) }
+ .to raise_error(error)
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index 89fb4eb3ff2..257f699a459 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -18,6 +18,7 @@ describe Deployment do
it { is_expected.to delegate_method(:commit).to(:project) }
it { is_expected.to delegate_method(:commit_title).to(:commit).as(:try) }
it { is_expected.to delegate_method(:manual_actions).to(:deployable).as(:try) }
+ it { is_expected.to delegate_method(:kubernetes_namespace).to(:deployment_cluster).as(:kubernetes_namespace) }
it { is_expected.to validate_presence_of(:ref) }
it { is_expected.to validate_presence_of(:sha) }
diff --git a/spec/serializers/cluster_basic_entity_spec.rb b/spec/serializers/deployment_cluster_entity_spec.rb
index 8c3307a1837..b22a93fcec7 100644
--- a/spec/serializers/cluster_basic_entity_spec.rb
+++ b/spec/serializers/deployment_cluster_entity_spec.rb
@@ -2,9 +2,9 @@
require 'spec_helper'
-describe ClusterBasicEntity do
+describe DeploymentClusterEntity do
describe '#as_json' do
- subject { described_class.new(cluster, request: request).as_json }
+ subject { described_class.new(deployment, request: request).as_json }
let(:maintainer) { create(:user) }
let(:developer) { create(:user) }
@@ -12,26 +12,30 @@ describe ClusterBasicEntity do
let(:request) { double(:request, current_user: current_user) }
let(:project) { create(:project) }
let(:cluster) { create(:cluster, name: 'the-cluster', projects: [project]) }
+ let(:deployment) { create(:deployment, cluster: cluster) }
+ let!(:deployment_cluster) { create(:deployment_cluster, cluster: cluster, deployment: deployment) }
before do
project.add_maintainer(maintainer)
project.add_developer(developer)
end
- it 'matches cluster_basic entity schema' do
- expect(subject.as_json).to match_schema('cluster_basic')
+ it 'matches deployment_cluster entity schema' do
+ expect(subject.as_json).to match_schema('deployment_cluster')
end
it 'exposes the cluster details' do
expect(subject[:name]).to eq('the-cluster')
expect(subject[:path]).to eq("/#{project.full_path}/-/clusters/#{cluster.id}")
+ expect(subject[:kubernetes_namespace]).to eq(deployment_cluster.kubernetes_namespace)
end
context 'when the user does not have permission to view the cluster' do
let(:current_user) { developer }
- it 'does not include the path' do
+ it 'does not include the path nor the namespace' do
expect(subject[:path]).to be_nil
+ expect(subject[:kubernetes_namespace]).to be_nil
end
end
end
diff --git a/yarn.lock b/yarn.lock
index ce6a0e94fc1..c667eac957b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -705,6 +705,11 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
+"@gitlab/at.js@^1.5.5":
+ version "1.5.5"
+ resolved "https://registry.yarnpkg.com/@gitlab/at.js/-/at.js-1.5.5.tgz#5f6bfe6baaef360daa9b038fa78798d7a6a916b4"
+ integrity sha512-282Dn3SPVsUHVDhMsXgfnv+Rzog0uxecjttxGRQvxh25es1+xvkGQFsvJfkSKJ3X1kHVkSjKf+Tt5Rra+Jhp9g==
+
"@gitlab/eslint-config@^2.1.2":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@gitlab/eslint-config/-/eslint-config-2.1.2.tgz#9f4011d3bf15f3e2668a1faa754f0b9804f23f8f"
@@ -740,10 +745,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.96.0.tgz#1d32730389e94358dc245e8336912523446d1269"
integrity sha512-mhg6kndxDhwjWChKhs5utO6PowlOyFdaCXUrkkxxe2H3cd8DYa40QOEcJeUrSIhkmgIMVesUawesx5tt4Bnnnw==
-"@gitlab/ui@^9.6.0":
- version "9.6.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-9.6.0.tgz#13119a56a34be34fd07e761cab0af3c00462159d"
- integrity sha512-R0pUa30l/JX/+1K/rZGAjDvCLLoQuodwCxBNzQ5U1ylnnfGclVrM2rBlZT3UlWnMkb9BRhTPn6uoC/HBOAo37g==
+"@gitlab/ui@^9.8.0":
+ version "9.8.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-9.8.0.tgz#b1a0b5f1f6ac9fdb19b64d74f0f729e3ec182495"
+ integrity sha512-0VjSTjCCtevdoeByxf5o/OimzV3zt1MMH5DlZSqakML38uoOM0WpgXI/4xAipzfYwiKUW+IWbuyZGJ3ucaJnhQ==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"
@@ -1774,11 +1779,6 @@ asynckit@^0.4.0:
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
-at.js@^1.5.4:
- version "1.5.4"
- resolved "https://registry.yarnpkg.com/at.js/-/at.js-1.5.4.tgz#8fc60cc80eadbe4874449b166a818e7ae1d784c1"
- integrity sha512-G8mgUb/PqShPoH8AyjuxsTGvIr1o716BtQUKDM44C8qN2W615y7KGJ68MlTGamd0J0D/m28emUkzagaHTdrGZw==
-
atob@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"