summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-02-14 09:08:43 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-02-14 09:08:43 +0000
commit733befe96ad19f5a02e442c4a9cc8059d3aabbda (patch)
treedcabd344df040e536a242edc4e3121fb3efca142 /app
parent10213bf3b26c3c21f7683471d35d1cd052c41e9c (diff)
downloadgitlab-ce-733befe96ad19f5a02e442c4a9cc8059d3aabbda.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-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
21 files changed, 208 insertions, 80 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