diff options
Diffstat (limited to 'app')
122 files changed, 906 insertions, 545 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 25fe2ae553e..cd800d75f7a 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -15,6 +15,7 @@ const Api = { mergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes', mergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions', groupLabelsPath: '/groups/:namespace_path/-/labels', + templatesPath: '/api/:version/templates/:key', licensePath: '/api/:version/templates/licenses/:key', gitignorePath: '/api/:version/templates/gitignores/:key', gitlabCiYmlPath: '/api/:version/templates/gitlab_ci_ymls/:key', @@ -265,6 +266,12 @@ const Api = { }); }, + templates(key, params = {}) { + const url = Api.buildUrl(this.templatesPath).replace(':key', key); + + return axios.get(url, { params }); + }, + buildUrl(url) { let urlRoot = ''; if (gon.relative_url_root != null) { diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index e34db893989..5b0c4285339 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -109,8 +109,6 @@ export class AwardsHandler { } const $menu = $(`.${this.menuClass}`); - const $thumbsBtn = $menu.find('[data-name="thumbsup"], [data-name="thumbsdown"]').parent(); - const $userAuthored = this.isUserAuthored($addBtn); if ($menu.length) { if ($menu.is('.is-visible')) { $addBtn.removeClass('is-active'); @@ -134,9 +132,6 @@ export class AwardsHandler { }, 200); }); } - - $thumbsBtn.toggleClass('disabled', $userAuthored); - $thumbsBtn.prop('disabled', $userAuthored); } // Create the emoji menu with the first category of emojis. @@ -364,10 +359,6 @@ export class AwardsHandler { return $emojiButton.hasClass('active'); } - isUserAuthored($button) { - return $button.hasClass('js-user-authored'); - } - decrementCounter($emojiButton, emoji) { const counter = $('.js-counter', $emojiButton); const counterNumber = parseInt(counter.text(), 10); @@ -474,20 +465,16 @@ export class AwardsHandler { } postEmoji($emojiButton, awardUrl, emoji, callback) { - if (this.isUserAuthored($emojiButton)) { - this.userAuthored($emojiButton); - } else { - axios - .post(awardUrl, { - name: emoji, - }) - .then(({ data }) => { - if (data.ok) { - callback(); - } - }) - .catch(() => flash(__('Something went wrong on our end.'))); - } + axios + .post(awardUrl, { + name: emoji, + }) + .then(({ data }) => { + if (data.ok) { + callback(); + } + }) + .catch(() => flash(__('Something went wrong on our end.'))); } findEmojiIcon(votesBlock, emoji) { diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/actions.js b/app/assets/javascripts/ide/stores/modules/file_templates/actions.js new file mode 100644 index 00000000000..43237a29466 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/file_templates/actions.js @@ -0,0 +1,82 @@ +import Api from '~/api'; +import { __ } from '~/locale'; +import * as types from './mutation_types'; + +export const requestTemplateTypes = ({ commit }) => commit(types.REQUEST_TEMPLATE_TYPES); +export const receiveTemplateTypesError = ({ commit, dispatch }) => { + commit(types.RECEIVE_TEMPLATE_TYPES_ERROR); + dispatch( + 'setErrorMessage', + { + text: __('Error loading template types.'), + action: () => + dispatch('fetchTemplateTypes').then(() => + dispatch('setErrorMessage', null, { root: true }), + ), + actionText: __('Please try again'), + }, + { root: true }, + ); +}; +export const receiveTemplateTypesSuccess = ({ commit }, templates) => + commit(types.RECEIVE_TEMPLATE_TYPES_SUCCESS, templates); + +export const fetchTemplateTypes = ({ dispatch, state }) => { + if (!Object.keys(state.selectedTemplateType).length) return Promise.reject(); + + dispatch('requestTemplateTypes'); + + return Api.templates(state.selectedTemplateType.key) + .then(({ data }) => dispatch('receiveTemplateTypesSuccess', data)) + .catch(() => dispatch('receiveTemplateTypesError')); +}; + +export const setSelectedTemplateType = ({ commit }, type) => + commit(types.SET_SELECTED_TEMPLATE_TYPE, type); + +export const receiveTemplateError = ({ dispatch }, template) => { + dispatch( + 'setErrorMessage', + { + text: __('Error loading template.'), + action: payload => + dispatch('fetchTemplateTypes', payload).then(() => + dispatch('setErrorMessage', null, { root: true }), + ), + actionText: __('Please try again'), + actionPayload: template, + }, + { root: true }, + ); +}; + +export const fetchTemplate = ({ dispatch, state }, template) => { + if (template.content) { + return dispatch('setFileTemplate', template); + } + + return Api.templates(`${state.selectedTemplateType.key}/${template.key || template.name}`) + .then(({ data }) => { + dispatch('setFileTemplate', data); + }) + .catch(() => dispatch('receiveTemplateError', template)); +}; + +export const setFileTemplate = ({ dispatch, commit, rootGetters }, template) => { + dispatch( + 'changeFileContent', + { path: rootGetters.activeFile.path, content: template.content }, + { root: true }, + ); + commit(types.SET_UPDATE_SUCCESS, true); +}; + +export const undoFileTemplate = ({ dispatch, commit, rootGetters }) => { + const file = rootGetters.activeFile; + + dispatch('changeFileContent', { path: file.path, content: file.raw }, { root: true }); + commit(types.SET_UPDATE_SUCCESS, false); +}; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/getters.js b/app/assets/javascripts/ide/stores/modules/file_templates/getters.js new file mode 100644 index 00000000000..38318fd49bf --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/file_templates/getters.js @@ -0,0 +1,23 @@ +export const templateTypes = () => [ + { + name: '.gitlab-ci.yml', + key: 'gitlab_ci_ymls', + }, + { + name: '.gitignore', + key: 'gitignores', + }, + { + name: 'LICENSE', + key: 'licenses', + }, + { + name: 'Dockerfile', + key: 'dockerfiles', + }, +]; + +export const showFileTemplatesBar = (_, getters) => name => + getters.templateTypes.find(t => t.name === name); + +export default () => {}; diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/index.js b/app/assets/javascripts/ide/stores/modules/file_templates/index.js new file mode 100644 index 00000000000..dfa5ef54413 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/file_templates/index.js @@ -0,0 +1,12 @@ +import createState from './state'; +import * as actions from './actions'; +import * as getters from './getters'; +import mutations from './mutations'; + +export default { + namespaced: true, + actions, + state: createState(), + getters, + mutations, +}; diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/mutation_types.js b/app/assets/javascripts/ide/stores/modules/file_templates/mutation_types.js new file mode 100644 index 00000000000..cf4499c0264 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/file_templates/mutation_types.js @@ -0,0 +1,7 @@ +export const REQUEST_TEMPLATE_TYPES = 'REQUEST_TEMPLATE_TYPES'; +export const RECEIVE_TEMPLATE_TYPES_ERROR = 'RECEIVE_TEMPLATE_TYPES_ERROR'; +export const RECEIVE_TEMPLATE_TYPES_SUCCESS = 'RECEIVE_TEMPLATE_TYPES_SUCCESS'; + +export const SET_SELECTED_TEMPLATE_TYPE = 'SET_SELECTED_TEMPLATE_TYPE'; + +export const SET_UPDATE_SUCCESS = 'SET_UPDATE_SUCCESS'; diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js b/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js new file mode 100644 index 00000000000..e413e61eaaa --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js @@ -0,0 +1,21 @@ +/* eslint-disable no-param-reassign */ +import * as types from './mutation_types'; + +export default { + [types.REQUEST_TEMPLATE_TYPES](state) { + state.isLoading = true; + }, + [types.RECEIVE_TEMPLATE_TYPES_ERROR](state) { + state.isLoading = false; + }, + [types.RECEIVE_TEMPLATE_TYPES_SUCCESS](state, templates) { + state.isLoading = false; + state.templates = templates; + }, + [types.SET_SELECTED_TEMPLATE_TYPE](state, type) { + state.selectedTemplateType = type; + }, + [types.SET_UPDATE_SUCCESS](state, success) { + state.updateSuccess = success; + }, +}; diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/state.js b/app/assets/javascripts/ide/stores/modules/file_templates/state.js new file mode 100644 index 00000000000..bd4b7d7bc52 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/file_templates/state.js @@ -0,0 +1,6 @@ +export default () => ({ + isLoading: false, + templates: [], + selectedTemplateType: {}, + updateSuccess: false, +}); diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js index 56a8d9430c7..0347f803757 100644 --- a/app/assets/javascripts/ide/stores/mutations.js +++ b/app/assets/javascripts/ide/stores/mutations.js @@ -146,13 +146,7 @@ export default { staged: false, prevPath: '', moved: false, - lastCommit: Object.assign(state.entries[file.path].lastCommit, { - id: lastCommit.commit.id, - url: lastCommit.commit_path, - message: lastCommit.commit.message, - author: lastCommit.commit.author_name, - updatedAt: lastCommit.commit.authored_date, - }), + lastCommitSha: lastCommit.commit.id, }); if (prevPath) { diff --git a/app/assets/javascripts/jobs/components/environments_block.vue b/app/assets/javascripts/jobs/components/environments_block.vue new file mode 100644 index 00000000000..ca6386595c7 --- /dev/null +++ b/app/assets/javascripts/jobs/components/environments_block.vue @@ -0,0 +1,118 @@ +<script> + import _ from 'underscore'; + import CiIcon from '~/vue_shared/components/ci_icon.vue'; + import { sprintf, __ } from '../../locale'; + + export default { + components: { + CiIcon, + }, + props: { + deploymentStatus: { + type: Object, + required: true, + }, + }, + computed: { + environment() { + let environmentText; + switch (this.deploymentStatus.status) { + case 'latest': + environmentText = sprintf( + __('This job is the most recent deployment to %{link}.'), + { link: this.environmentLink }, + false, + ); + break; + case 'out_of_date': + if (this.hasLastDeployment) { + environmentText = sprintf( + __( + 'This job is an out-of-date deployment to %{environmentLink}. View the most recent deployment %{deploymentLink}.', + ), + { + environmentLink: this.environmentLink, + deploymentLink: this.deploymentLink, + }, + false, + ); + } else { + environmentText = sprintf( + __('This job is an out-of-date deployment to %{environmentLink}.'), + { environmentLink: this.environmentLink }, + false, + ); + } + + break; + case 'failed': + environmentText = sprintf( + __('The deployment of this job to %{environmentLink} did not succeed.'), + { environmentLink: this.environmentLink }, + false, + ); + break; + case 'creating': + if (this.hasLastDeployment) { + environmentText = sprintf( + __( + 'This job is creating a deployment to %{environmentLink} and will overwrite the last %{deploymentLink}.', + ), + { + environmentLink: this.environmentLink, + deploymentLink: this.deploymentLink, + }, + false, + ); + } else { + environmentText = sprintf( + __('This job is creating a deployment to %{environmentLink}.'), + { environmentLink: this.environmentLink }, + false, + ); + } + break; + default: + break; + } + return environmentText; + }, + environmentLink() { + return sprintf( + '%{startLink}%{name}%{endLink}', + { + startLink: `<a href="${this.deploymentStatus.environment.path}">`, + name: _.escape(this.deploymentStatus.environment.name), + endLink: '</a>', + }, + false, + ); + }, + deploymentLink() { + return sprintf( + '%{startLink}%{name}%{endLink}', + { + startLink: `<a href="${this.lastDeployment.path}">`, + name: _.escape(this.lastDeployment.name), + endLink: '</a>', + }, + false, + ); + }, + hasLastDeployment() { + return this.deploymentStatus.environment.last_deployment; + }, + lastDeployment() { + return this.deploymentStatus.environment.last_deployment; + }, + }, + }; +</script> +<template> + <div class="prepend-top-default js-environment-container"> + <div class="environment-information"> + <ci-icon :status="deploymentStatus.icon" /> + <p v-html="environment"></p> + </div> + </div> +</template> diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue index 225d9f18612..e111d3b9ac2 100644 --- a/app/assets/javascripts/notes/components/note_awards_list.vue +++ b/app/assets/javascripts/notes/components/note_awards_list.vue @@ -82,29 +82,17 @@ export default { getAwardHTML(name) { return glEmojiTag(name); }, - getAwardClassBindings(awardList, awardName) { + getAwardClassBindings(awardList) { return { active: this.hasReactionByCurrentUser(awardList), - disabled: !this.canInteractWithEmoji(awardList, awardName), + disabled: !this.canInteractWithEmoji(), }; }, - canInteractWithEmoji(awardList, awardName) { - let isAllowed = true; - const restrictedEmojis = ['thumbsup', 'thumbsdown']; - - // Users can not add :+1: and :-1: to their own notes - if ( - this.getUserData.id === this.noteAuthorId && - restrictedEmojis.indexOf(awardName) > -1 - ) { - isAllowed = false; - } - - return this.getUserData.id && isAllowed; + canInteractWithEmoji() { + return this.getUserData.id; }, hasReactionByCurrentUser(awardList) { - return awardList.filter(award => award.user.id === this.getUserData.id) - .length; + return awardList.filter(award => award.user.id === this.getUserData.id).length; }, awardTitle(awardsList) { const hasReactionByCurrentUser = this.hasReactionByCurrentUser( @@ -197,7 +185,7 @@ export default { v-tooltip v-for="(awardList, awardName, index) in groupedAwards" :key="index" - :class="getAwardClassBindings(awardList, awardName)" + :class="getAwardClassBindings(awardList)" :title="awardTitle(awardList)" class="btn award-control" data-boundary="viewport" diff --git a/app/assets/javascripts/pages/groups/milestones/show/index.js b/app/assets/javascripts/pages/groups/milestones/show/index.js index 74cc4ba42c1..ebaea5ef3dc 100644 --- a/app/assets/javascripts/pages/groups/milestones/show/index.js +++ b/app/assets/javascripts/pages/groups/milestones/show/index.js @@ -1,8 +1,10 @@ import initMilestonesShow from '~/pages/milestones/shared/init_milestones_show'; +import initDeleteMilestoneModal from '~/pages/milestones/shared/delete_milestone_modal_init'; + import Milestone from '~/milestone'; document.addEventListener('DOMContentLoaded', () => { initMilestonesShow(); - + initDeleteMilestoneModal(); Milestone.initDeprecationMessage(); }); diff --git a/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue b/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue index 4061c11ba8f..48668562f09 100644 --- a/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue +++ b/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue @@ -40,8 +40,8 @@ if (this.issueCount === 0 && this.mergeRequestCount === 0) { return sprintf( s__(`Milestones| -You’re about to permanently delete the milestone %{milestoneTitle} from this project. -%{milestoneTitle} is not currently used in any issues or merge requests.`), +You’re about to permanently delete the milestone %{milestoneTitle}. +This milestone is not currently used in any issues or merge requests.`), { milestoneTitle, }, @@ -51,7 +51,7 @@ You’re about to permanently delete the milestone %{milestoneTitle} from this p return sprintf( s__(`Milestones| -You’re about to permanently delete the milestone %{milestoneTitle} from this project and remove it from %{issuesWithCount} and %{mergeRequestsWithCount}. +You’re about to permanently delete the milestone %{milestoneTitle} and remove it from %{issuesWithCount} and %{mergeRequestsWithCount}. Once deleted, it cannot be undone or recovered.`), { milestoneTitle, diff --git a/app/assets/javascripts/pages/profiles/show/index.js b/app/assets/javascripts/pages/profiles/show/index.js index 949219a0837..aea7b649c20 100644 --- a/app/assets/javascripts/pages/profiles/show/index.js +++ b/app/assets/javascripts/pages/profiles/show/index.js @@ -3,15 +3,22 @@ import createFlash from '~/flash'; import GfmAutoComplete from '~/gfm_auto_complete'; import EmojiMenu from './emoji_menu'; +const defaultStatusEmoji = 'speech_balloon'; + document.addEventListener('DOMContentLoaded', () => { const toggleEmojiMenuButtonSelector = '.js-toggle-emoji-menu'; const toggleEmojiMenuButton = document.querySelector(toggleEmojiMenuButtonSelector); const statusEmojiField = document.getElementById('js-status-emoji-field'); const statusMessageField = document.getElementById('js-status-message-field'); - const findNoEmojiPlaceholder = () => document.getElementById('js-no-emoji-placeholder'); + const toggleNoEmojiPlaceholder = (isVisible) => { + const placeholderElement = document.getElementById('js-no-emoji-placeholder'); + placeholderElement.classList.toggle('hidden', !isVisible); + }; + + const findStatusEmoji = () => toggleEmojiMenuButton.querySelector('gl-emoji'); const removeStatusEmoji = () => { - const statusEmoji = toggleEmojiMenuButton.querySelector('gl-emoji'); + const statusEmoji = findStatusEmoji(); if (statusEmoji) { statusEmoji.remove(); } @@ -19,7 +26,7 @@ document.addEventListener('DOMContentLoaded', () => { const selectEmojiCallback = (emoji, emojiTag) => { statusEmojiField.value = emoji; - findNoEmojiPlaceholder().classList.add('hidden'); + toggleNoEmojiPlaceholder(false); removeStatusEmoji(); toggleEmojiMenuButton.innerHTML += emojiTag; }; @@ -29,7 +36,7 @@ document.addEventListener('DOMContentLoaded', () => { statusEmojiField.value = ''; statusMessageField.value = ''; removeStatusEmoji(); - findNoEmojiPlaceholder().classList.remove('hidden'); + toggleNoEmojiPlaceholder(true); }); const emojiAutocomplete = new GfmAutoComplete(); @@ -44,6 +51,23 @@ document.addEventListener('DOMContentLoaded', () => { selectEmojiCallback, ); emojiMenu.bindEvents(); + + const defaultEmojiTag = Emoji.glEmojiTag(defaultStatusEmoji); + statusMessageField.addEventListener('input', () => { + const hasStatusMessage = statusMessageField.value.trim() !== ''; + const statusEmoji = findStatusEmoji(); + if (hasStatusMessage && statusEmoji) { + return; + } + + if (hasStatusMessage) { + toggleNoEmojiPlaceholder(false); + toggleEmojiMenuButton.innerHTML += defaultEmojiTag; + } else if (statusEmoji.dataset.name === defaultStatusEmoji) { + toggleNoEmojiPlaceholder(true); + removeStatusEmoji(); + } + }); }) .catch(() => createFlash('Failed to load emoji list!')); }); diff --git a/app/assets/javascripts/performance_bar/index.js b/app/assets/javascripts/performance_bar/index.js index 41880d27516..6e5ef0ac0b2 100644 --- a/app/assets/javascripts/performance_bar/index.js +++ b/app/assets/javascripts/performance_bar/index.js @@ -1,5 +1,4 @@ import Vue from 'vue'; -import Flash from '../flash'; import PerformanceBarService from './services/performance_bar_service'; import PerformanceBarStore from './stores/performance_bar_store'; @@ -46,7 +45,8 @@ export default ({ container }) => this.store.addRequestDetails(requestId, res.data.data); }) .catch(() => - Flash(`Error getting performance bar results for ${requestId}`), + // eslint-disable-next-line no-console + console.warn(`Error getting performance bar results for ${requestId}`), ); }, }, diff --git a/app/assets/javascripts/performance_bar/services/performance_bar_service.js b/app/assets/javascripts/performance_bar/services/performance_bar_service.js index bc71911ae35..60d9ba62570 100644 --- a/app/assets/javascripts/performance_bar/services/performance_bar_service.js +++ b/app/assets/javascripts/performance_bar/services/performance_bar_service.js @@ -11,13 +11,10 @@ export default class PerformanceBarService { static registerInterceptor(peekUrl, callback) { const interceptor = response => { - const requestId = response.headers['x-request-id']; - // Get the request URL from response.config for Axios, and response for - // Vue Resource. - const requestUrl = (response.config || response).url; - const cachedResponse = response.headers['x-gitlab-from-cache'] === 'true'; + const [fireCallback, requestId, requestUrl] = + PerformanceBarService.callbackParams(response, peekUrl); - if (requestUrl !== peekUrl && requestId && !cachedResponse) { + if (fireCallback) { callback(requestId, requestUrl); } @@ -38,4 +35,16 @@ export default class PerformanceBarService { vueResourceInterceptor, ); } + + static callbackParams(response, peekUrl) { + const requestId = response.headers && response.headers['x-request-id']; + // Get the request URL from response.config for Axios, and response for + // Vue Resource. + const requestUrl = (response.config || response).url; + const apiRequest = requestUrl && requestUrl.match(/^\/api\//); + const cachedResponse = response.headers && response.headers['x-gitlab-from-cache'] === 'true'; + const fireCallback = requestUrl !== peekUrl && requestId && !apiRequest && !cachedResponse; + + return [fireCallback, requestId, requestUrl]; + } } diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue index 8487c8036ee..2ad66f4fe86 100644 --- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue @@ -1,6 +1,5 @@ <script> import $ from 'jquery'; -import _ from 'underscore'; import JobNameComponent from './job_name_component.vue'; import JobComponent from './job_component.vue'; import tooltip from '../../../vue_shared/directives/tooltip'; @@ -47,7 +46,7 @@ export default { computed: { tooltipText() { - return _.escape(`${this.job.name} - ${this.job.status.label}`); + return `${this.job.name} - ${this.job.status.label}`; }, }, diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue index 66f95147193..9ac16b7e541 100644 --- a/app/assets/javascripts/pipelines/components/graph/job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue @@ -1,5 +1,4 @@ <script> -import _ from 'underscore'; import ActionComponent from './action_component.vue'; import JobNameComponent from './job_name_component.vue'; import tooltip from '../../../vue_shared/directives/tooltip'; @@ -62,7 +61,7 @@ export default { const textBuilder = []; if (this.job.name) { - textBuilder.push(_.escape(this.job.name)); + textBuilder.push(this.job.name); } if (this.job.name && this.status.tooltip) { @@ -106,7 +105,6 @@ export default { :class="cssClassJobName" :data-boundary="tooltipBoundary" data-container="body" - data-html="true" class="js-pipeline-graph-job-link" > @@ -122,7 +120,6 @@ export default { :title="tooltipText" :class="cssClassJobName" class="js-job-component-tooltip non-details-job-component" - data-html="true" data-container="body" > diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue index 804822a3ea8..29b347824de 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue @@ -330,7 +330,7 @@ export default { <pipelines-artifacts-component v-if="pipeline.details.artifacts.length" :artifacts="pipeline.details.artifacts" - class="d-none d-sm-none d-md-block" + class="d-md-block" /> <loading-button diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue index 21f21232596..d530ab2767b 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue @@ -1,5 +1,6 @@ <script> import Icon from '~/vue_shared/components/icon.vue'; +import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; import timeagoMixin from '../../vue_shared/mixins/timeago'; import tooltip from '../../vue_shared/directives/tooltip'; import LoadingButton from '../../vue_shared/components/loading_button.vue'; @@ -16,6 +17,7 @@ export default { MemoryUsage, StatusIcon, Icon, + TooltipOnTruncate, }, directives: { tooltip, @@ -88,14 +90,20 @@ export default { <span> Deployed to </span> - <a - :href="deployment.url" - target="_blank" - rel="noopener noreferrer nofollow" - class="deploy-link js-deploy-meta" + <tooltip-on-truncate + :title="deployment.name" + truncate-target="child" + class="deploy-link label-truncate" > - {{ deployment.name }} - </a> + <a + :href="deployment.url" + target="_blank" + rel="noopener noreferrer nofollow" + class="js-deploy-meta" + > + {{ deployment.name }} + </a> + </tooltip-on-truncate> </template> <span v-tooltip diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue index a4c2289c590..72bd28ae03f 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue @@ -1,18 +1,17 @@ <script> -import tooltip from '~/vue_shared/directives/tooltip'; -import { n__ } from '~/locale'; +import _ from 'underscore'; +import { n__, s__, sprintf } from '~/locale'; import { mergeUrlParams, webIDEUrl } from '~/lib/utils/url_utility'; import Icon from '~/vue_shared/components/icon.vue'; import clipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; export default { name: 'MRWidgetHeader', - directives: { - tooltip, - }, components: { Icon, clipboardButton, + TooltipOnTruncate, }, props: { mr: { @@ -24,8 +23,12 @@ export default { shouldShowCommitsBehindText() { return this.mr.divergedCommitsCount > 0; }, - commitsText() { - return n__('%d commit behind', '%d commits behind', this.mr.divergedCommitsCount); + commitsBehindText() { + return sprintf(s__('mrWidget|The source branch is %{commitsBehindLinkStart}%{commitsBehind}%{commitsBehindLinkEnd} the target branch'), { + commitsBehindLinkStart: `<a href="${_.escape(this.mr.targetBranchPath)}">`, + commitsBehind: n__('%d commit behind', '%d commits behind', this.mr.divergedCommitsCount), + commitsBehindLinkEnd: '</a>', + }, false); }, branchNameClipboardData() { // This supports code in app/assets/javascripts/copy_to_clipboard.js that @@ -36,12 +39,6 @@ export default { gfm: `\`${this.mr.sourceBranch}\``, }); }, - isSourceBranchLong() { - return this.isBranchTitleLong(this.mr.sourceBranch); - }, - isTargetBranchLong() { - return this.isBranchTitleLong(this.mr.targetBranch); - }, webIdePath() { return mergeUrlParams({ target_project: this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath ? @@ -49,11 +46,6 @@ export default { }, webIDEUrl(`/${this.mr.sourceProjectFullPath}/merge_requests/${this.mr.iid}`)); }, }, - methods: { - isBranchTitleLong(branchTitle) { - return branchTitle.length > 32; - }, - }, }; </script> <template> @@ -65,30 +57,21 @@ export default { <div class="normal"> <strong> {{ s__("mrWidget|Request to merge") }} - <span - :class="{ 'label-truncated': isSourceBranchLong }" - :title="isSourceBranchLong ? mr.sourceBranch : ''" - :v-tooltip="isSourceBranchLong" - class="label-branch js-source-branch" - data-placement="bottom" + <tooltip-on-truncate + :title="mr.sourceBranch" + truncate-target="child" + class="label-branch label-truncate js-source-branch" v-html="mr.sourceBranchLink" - > - </span> - - <clipboard-button + /><clipboard-button :text="branchNameClipboardData" :title="__('Copy branch name to clipboard')" css-class="btn-default btn-transparent btn-clipboard" /> - {{ s__("mrWidget|into") }} - - <span - :v-tooltip="isTargetBranchLong" - :class="{ 'label-truncatedtooltip': isTargetBranchLong }" - :title="isTargetBranchLong ? mr.targetBranch : ''" - class="label-branch" - data-placement="bottom" + <tooltip-on-truncate + :title="mr.targetBranch" + truncate-target="child" + class="label-branch label-truncate" > <a :href="mr.targetBranchTreePath" @@ -96,15 +79,13 @@ export default { > {{ mr.targetBranch }} </a> - </span> + </tooltip-on-truncate> </strong> <div v-if="shouldShowCommitsBehindText" class="diverged-commits-count" + v-html="commitsBehindText" > - <span class="monospace">{{ mr.sourceBranch }}</span> - is {{ commitsText }} - <span class="monospace">{{ mr.targetBranch }}</span> </div> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue index 4a3fd01fa39..fee41b239e8 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue @@ -3,6 +3,7 @@ import PipelineStage from '~/pipelines/components/stage.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; import Icon from '~/vue_shared/components/icon.vue'; +import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; export default { name: 'MRWidgetPipeline', @@ -10,6 +11,7 @@ export default { PipelineStage, CiIcon, Icon, + TooltipOnTruncate, }, props: { pipeline: { @@ -30,6 +32,10 @@ export default { type: String, required: false, }, + sourceBranch: { + type: String, + required: false, + }, }, computed: { hasPipeline() { @@ -107,11 +113,12 @@ export default { > {{ pipeline.commit.short_id }}</a> on - <span - class="label-branch" + <tooltip-on-truncate + :title="sourceBranch" + truncate-target="child" + class="label-branch label-truncate" v-html="sourceBranchLink" - > - </span> + /> </template> </div> <div diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue index 80593d1f34a..dc6be025f11 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue @@ -254,6 +254,7 @@ export default { :pipeline="mr.pipeline" :ci-status="mr.ciStatus" :has-ci="mr.hasCI" + :source-branch="mr.sourceBranch" :source-branch-link="mr.sourceBranchLink" /> <deployment diff --git a/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue b/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue index f44d361c47e..78fde463507 100644 --- a/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue +++ b/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue @@ -71,7 +71,11 @@ export default { }, methods: { getPercent(count) { - return roundOffFloat((count / this.totalCount) * 100, 1); + const percent = roundOffFloat((count / this.totalCount) * 100, 1); + if (percent > 0 && percent < 1) { + return '< 1'; + } + return percent; }, barStyle(percent) { return `width: ${percent}%;`; diff --git a/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue b/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue new file mode 100644 index 00000000000..125826da6c3 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue @@ -0,0 +1,67 @@ +<script> +import _ from 'underscore'; +import tooltip from '../directives/tooltip'; + +export default { + directives: { + tooltip, + }, + props: { + title: { + type: String, + required: false, + default: '', + }, + placement: { + type: String, + required: false, + default: 'top', + }, + truncateTarget: { + type: [String, Function], + required: false, + default: '', + }, + }, + data() { + return { + showTooltip: false, + }; + }, + mounted() { + const target = this.selectTarget(); + + if (target && target.scrollWidth > target.offsetWidth) { + this.showTooltip = true; + } + }, + methods: { + selectTarget() { + if (_.isFunction(this.truncateTarget)) { + return this.truncateTarget(this.$el); + } else if (this.truncateTarget === 'child') { + return this.$el.childNodes[0]; + } + + return this.$el; + }, + }, +}; +</script> + +<template> + <span + v-tooltip + v-if="showTooltip" + :title="title" + :data-placement="placement" + class="js-show-tooltip" + > + <slot></slot> + </span> + <span + v-else + > + <slot></slot> + </span> +</template> diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss index e8e707cf90c..c91f5e279ea 100644 --- a/app/assets/stylesheets/bootstrap_migration.scss +++ b/app/assets/stylesheets/bootstrap_migration.scss @@ -4,11 +4,11 @@ $text-color: $gl-text-color; -$brand-primary: $gl-primary; -$brand-success: $gl-success; -$brand-info: $gl-info; -$brand-warning: $gl-warning; -$brand-danger: $gl-danger; +$brand-primary: $blue-500; +$brand-success: $green-500; +$brand-info: $blue-500; +$brand-warning: $orange-500; +$brand-danger: $red-500; $border-radius-base: 3px !default; diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index 4c7c399a3ca..9dd0384a228 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -8,7 +8,7 @@ float: left; margin-right: 15px; border-radius: $avatar-radius; - border: 1px solid $avatar-border; + border: 1px solid $gray-normal; &.s16 { @include avatar-size(16px, 6px); } &.s18 { @include avatar-size(18px, 6px); } &.s19 { @include avatar-size(19px, 6px); } @@ -36,7 +36,7 @@ width: 40px; height: 40px; padding: 0; - background: $avatar-background; + background: $gray-lightest; overflow: hidden; &.avatar-inline { @@ -62,7 +62,7 @@ } &:not([href]):hover { - border-color: darken($avatar-border, 10%); + border-color: darken($gray-normal, 10%); } } @@ -70,7 +70,7 @@ text-align: center; vertical-align: top; color: $identicon-fg-color; - background-color: $identicon-gray; + background-color: $gray-darker; // Sizes &.s16 { font-size: 12px; line-height: 1.33; } @@ -94,7 +94,7 @@ &.bg4 { background-color: $identicon-blue; } &.bg5 { background-color: $identicon-teal; } &.bg6 { background-color: $identicon-orange; } - &.bg7 { background-color: $identicon-gray; } + &.bg7 { background-color: $gray-darker; } } .avatar-container { @@ -122,7 +122,7 @@ .avatar-counter { background-color: $gray-darkest; color: $white-light; - border: 1px solid $avatar-border; + border: 1px solid $gray-normal; border-radius: 1em; font-family: $regular-font; font-size: 9px; diff --git a/app/assets/stylesheets/framework/badges.scss b/app/assets/stylesheets/framework/badges.scss index 57df9b969c3..c6060161dec 100644 --- a/app/assets/stylesheets/framework/badges.scss +++ b/app/assets/stylesheets/framework/badges.scss @@ -1,6 +1,6 @@ .badge.badge-pill { font-weight: $gl-font-weight-normal; background-color: $badge-bg; - color: $badge-color; + color: $gl-text-color-secondary; vertical-align: baseline; } diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 0dc7aa4ef68..72b4ed0ac33 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -452,14 +452,14 @@ } .btn-missing { - color: $notes-light-color; + color: $gl-text-color-secondary; border: 1px dashed $border-gray-normal-dashed; border-radius: $border-radius-default; &:hover, &:active, &:focus { - color: $notes-light-color; + color: $gl-text-color-secondary; background-color: $white-normal; } } diff --git a/app/assets/stylesheets/framework/callout.scss b/app/assets/stylesheets/framework/callout.scss index 1bd94c0acba..bdd7f09d926 100644 --- a/app/assets/stylesheets/framework/callout.scss +++ b/app/assets/stylesheets/framework/callout.scss @@ -25,25 +25,25 @@ /* Variations */ .bs-callout-danger { - background-color: $callout-danger-bg; - border-color: $callout-danger-border; - color: $callout-danger-color; + background-color: $red-100; + border-color: $red-200; + color: $red-700; } .bs-callout-warning { - background-color: $callout-warning-bg; - border-color: $callout-warning-border; - color: $callout-warning-color; + background-color: $orange-100; + border-color: $orange-200; + color: $orange-700; } .bs-callout-info { - background-color: $callout-info-bg; - border-color: $callout-info-border; - color: $callout-info-color; + background-color: $blue-100; + border-color: $blue-200; + color: $blue-700; } .bs-callout-success { - background-color: $callout-success-bg; - border-color: $callout-success-border; - color: $callout-success-color; + background-color: $green-100; + border-color: $green-200; + color: $green-700; } diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 79ca6e61e9a..72e27f9ad16 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -1,8 +1,8 @@ /** COLORS **/ -.cgray { color: $common-gray; } +.cgray { color: $gl-text-color; } .clgray { color: $common-gray-light; } -.cred { color: $common-red; } -.cgreen { color: $common-green; } +.cred { color: $red-500; } +.cgreen { color: $green-600; } .cdark { color: $common-gray-dark; } .text-plain, @@ -44,10 +44,10 @@ } .hint { font-style: italic; color: $hint-color; } -.light { color: $common-gray; } +.light { color: $gl-text-color; } .slead { - color: $common-gray; + color: $gl-text-color; font-size: 14px; margin-bottom: 12px; font-weight: $gl-font-weight-normal; @@ -352,7 +352,7 @@ img.emoji { border-color: $border-color !important; .dz-upload { - background: $gl-success !important; + background: $green-500 !important; } } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 83bc3776178..8a224dc517e 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -147,7 +147,7 @@ } @mixin dropdown-item-hover { - background-color: $dropdown-item-hover-bg; + background-color: $gray-darker; color: $gl-text-color; outline: 0; @@ -195,7 +195,7 @@ text-decoration: none; .badge.badge-pill { - background-color: darken($dropdown-link-hover-bg, 5%); + background-color: darken($blue-50, 5%); } } @@ -233,7 +233,7 @@ font-weight: $gl-font-weight-normal; padding: 8px 0; background-color: $white-light; - border: 1px solid $dropdown-border-color; + border: 1px solid $border-color; border-radius: $border-radius-base; box-shadow: 0 2px 4px $dropdown-shadow-color; @@ -874,7 +874,7 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu { overflow-y: auto; li.section-empty.section-failure { - color: $callout-danger-color; + color: $red-700; } .frequent-items-list-item-container a { diff --git a/app/assets/stylesheets/framework/feature_highlight.scss b/app/assets/stylesheets/framework/feature_highlight.scss index cad915bc86f..85cabf43e9e 100644 --- a/app/assets/stylesheets/framework/feature_highlight.scss +++ b/app/assets/stylesheets/framework/feature_highlight.scss @@ -72,11 +72,11 @@ .feature-highlight-popover { width: 240px; padding: 0; - border: 1px solid $dropdown-border-color; + border: 1px solid $border-color; box-shadow: 0 2px 4px $dropdown-shadow-color; &.right > .arrow { - border-right-color: $dropdown-border-color; + border-right-color: $border-color; } .popover-body { diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 3bdf5bfc93a..1d3512bbb4c 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -312,11 +312,11 @@ span.idiff { text-decoration: none; .new-file { - color: $notify-new-file; + color: $green-600; } .deleted-file { - color: $notify-deleted-file; + color: $red-700; } } } diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 9b09ed0ed0a..abfe350677e 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -206,7 +206,7 @@ &.focus, &.focus:hover { border-color: $blue-300; - box-shadow: 0 0 4px $search-input-focus-shadow-color; + box-shadow: 0 0 4px $dropdown-input-focus-shadow; } gl-emoji { diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index a70eece8f68..afd888af672 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -8,7 +8,7 @@ input { input[type='text'].danger { background: $input-danger-bg !important; - border-color: $input-danger-border; + border-color: $red-400; text-shadow: 0 1px 1px $white-light; } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index e7e13d35d8e..11a30d83f03 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -554,7 +554,7 @@ float: left; margin-right: 5px; border-radius: 50%; - border: 1px solid $avatar-border; + border: 1px solid $gray-normal; } .with-performance-bar .navbar-gitlab { diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss index d1f7ff4438b..f002edced8a 100644 --- a/app/assets/stylesheets/framework/icons.scss +++ b/app/assets/stylesheets/framework/icons.scss @@ -11,7 +11,7 @@ .ci-status-icon-failed { svg { - fill: $gl-danger; + fill: $red-500; } &.add-border { diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss index da5f80d9d37..2d672e62e08 100644 --- a/app/assets/stylesheets/framework/issue_box.scss +++ b/app/assets/stylesheets/framework/issue_box.scss @@ -26,12 +26,12 @@ &.status-box-closed, &.status-box-mr-closed { - background-color: $gl-danger; + background-color: $red-500; } &.status-box-issue-closed, &.status-box-mr-merged { - background-color: $gl-primary; + background-color: $blue-500; } &.status-box-open { diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index 88d2f0aaf85..3ae2c7078d6 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -58,7 +58,7 @@ .select2-drop.select2-drop-above { box-shadow: 0 2px 4px $dropdown-shadow-color; border-radius: $border-radius-base; - border: 1px solid $dropdown-border-color; + border: 1px solid $border-color; min-width: 175px; color: $gl-text-color; z-index: 999; @@ -69,7 +69,7 @@ } .select2-drop.select2-drop-above.select2-drop-active { - border-top: 1px solid $dropdown-border-color; + border-top: 1px solid $border-color; margin-top: -6px; } @@ -193,7 +193,7 @@ color: $gl-text-color; .select2-result-label { - background: $dropdown-item-hover-bg; + background: $gray-darker; } } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 96508a71bd8..2781d910b8d 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -297,19 +297,9 @@ $tanuki-yellow: #fca326; /* * State colors: */ -$gl-primary: $blue-500; -$gl-success: $green-500; -$gl-success-focus: rgba($gl-success, 0.4); -$gl-info: $blue-500; -$gl-warning: $orange-500; -$gl-danger: $red-500; +$green-500-focus: rgba($green-500, 0.4); $gl-btn-active-background: rgba(0, 0, 0, 0.16); $gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background; -// Bootstrap override states -$success: $gl-success; -$info: $gl-info; -$warning: $gl-warning; -$danger: $gl-danger; /* * Commit Diff Colors @@ -329,10 +319,9 @@ $line-select-yellow-dark: #f0e2bd; $dark-diff-match-bg: rgba(255, 255, 255, 0.3); $dark-diff-match-color: rgba(255, 255, 255, 0.1); $file-mode-changed: #777; -$file-mode-changed: #777; -$diff-image-info-color: grey; +$diff-image-info-color: gray; $diff-swipe-border: #999; -$diff-view-modes-color: grey; +$diff-view-modes-color: gray; $diff-view-modes-border: #c1c1c1; $diff-jagged-border-gradient-color: darken($white-normal, 8%); @@ -352,9 +341,7 @@ $dropdown-min-height: 40px; $dropdown-max-height: 312px; $dropdown-vertical-offset: 4px; $dropdown-link-color: #555; -$dropdown-link-hover-bg: $blue-50; $dropdown-empty-row-bg: rgba(#000, 0.04); -$dropdown-border-color: $border-color; $dropdown-shadow-color: rgba(#000, 0.1); $dropdown-divider-color: rgba(#000, 0.1); $dropdown-title-btn-color: #bfbfbf; @@ -364,7 +351,6 @@ $dropdown-input-focus-shadow: rgba($blue-300, 0.4); $dropdown-loading-bg: rgba(#fff, 0.6); $dropdown-chevron-size: 10px; $dropdown-toggle-active-border-color: darken($border-color, 14%); -$dropdown-item-hover-bg: $gray-darker; $dropdown-fade-mask-height: 32px; $dropdown-member-form-control-width: 163px; @@ -372,7 +358,6 @@ $dropdown-member-form-control-width: 163px; * Filtered Search */ $filtered-search-term-shadow-color: rgba(0, 0, 0, 0.09); -$dropdown-hover-color: $blue-400; /* * Contextual Sidebar @@ -387,7 +372,7 @@ $sidebar-milestone-toggle-bottom-margin: 10px; * Buttons */ $btn-active-gray: #ececec; -$btn-active-gray-light: e4e7ed; +$btn-active-gray-light: #e4e7ed; $btn-white-active: #848484; $gl-btn-padding: 10px; $gl-btn-line-height: 16px; @@ -398,7 +383,6 @@ $gl-btn-horz-padding: 12px; * Badges */ $badge-bg: rgba(0, 0, 0, 0.07); -$badge-color: $gl-text-color-secondary; /* * Pagination @@ -406,21 +390,12 @@ $badge-color: $gl-text-color-secondary; $pagination-padding-y: 6px; $pagination-padding-x: 16px; $pagination-line-height: 20px; -$pagination-border-color: $border-color; -$pagination-active-bg: $blue-600; -$pagination-active-border-color: $blue-600; -$pagination-hover-bg: $blue-50; -$pagination-hover-border-color: $border-color; -$pagination-hover-color: $gl-text-color; $pagination-disabled-color: #cdcdcd; -$pagination-disabled-bg: $gray-light; -$pagination-disabled-border-color: $border-color; /* * Status icons */ $status-icon-size: 22px; -$status-icon-margin: $gl-btn-padding; /* * Award emoji @@ -433,16 +408,13 @@ $award-emoji-positive-add-lines: #bb9c13; * Search Box */ $search-input-border-color: rgba($blue-400, 0.8); -$search-input-focus-shadow-color: $dropdown-input-focus-shadow; $search-input-width: 240px; $search-input-active-width: 320px; -$location-badge-active-bg: $blue-500; $location-icon-color: #e7e9ed; /* * Notes */ -$notes-light-color: $gl-text-color-secondary; $note-disabled-comment-color: #b2b2b2; $note-targe3-outside: #fffff0; $note-targe3-inside: #ffffd3; @@ -463,7 +435,6 @@ $identicon-indigo: #e8eaf6; $identicon-blue: #e3f2fd; $identicon-teal: #e0f2f1; $identicon-orange: #fbe9e7; -$identicon-gray: $gray-darker; $identicon-fg-color: #555555; /* @@ -479,7 +450,6 @@ $calendar-user-contrib-text: #959494; $cycle-analytics-box-padding: 30px; $cycle-analytics-box-text-color: #8c8c8c; $cycle-analytics-big-font: 19px; -$cycle-analytics-dark-text: $gl-text-color; $cycle-analytics-light-gray: #bfbfbf; $cycle-analytics-dismiss-icon-color: #b2b2b2; @@ -507,9 +477,6 @@ $issue-board-list-difference-md: $issue-board-list-difference-sm + $issue-boards * Avatar */ $avatar-radius: 50%; -$avatar-border: $gray-normal; -$avatar-border-hover: $gray-darker; -$avatar-background: $gray-lightest; $gl-avatar-size: 40px; /* @@ -525,22 +492,6 @@ $blame-blue: #254e77; $builds-trace-bg: #111; /* -* Callout -*/ -$callout-danger-bg: $red-100; -$callout-danger-border: $red-200; -$callout-danger-color: $red-700; -$callout-warning-bg: $orange-100; -$callout-warning-border: $orange-200; -$callout-warning-color: $orange-700; -$callout-info-bg: $blue-100; -$callout-info-border: $blue-200; -$callout-info-color: $blue-700; -$callout-success-bg: $green-100; -$callout-success-border: $green-200; -$callout-success-color: $green-700; - -/* * Commit Page */ $commit-max-width-marker-color: rgba(0, 0, 0, 0); @@ -549,16 +500,8 @@ $commit-message-text-area-bg: rgba(0, 0, 0, 0); /* * Common */ -$common-gray: $gl-text-color; $common-gray-light: #bbb; $common-gray-dark: #444; -$common-red: $red-500; -$common-green: $green-600; - -/* -* Editor -*/ -$editor-cancel-color: $red-600; /* * Events @@ -579,7 +522,6 @@ $logs-p-color: #333; */ $input-height: 34px; $input-danger-bg: #f2dede; -$input-danger-border: $red-400; $input-group-addon-bg: #f7f8fa; $gl-field-focus-shadow: rgba(0, 0, 0, 0.075); $gl-field-focus-shadow-error: rgba($red-500, 0.6); @@ -626,16 +568,9 @@ $fade-mask-transition-duration: 0.1s; $fade-mask-transition-curve: ease-in-out; /* -* Lint -*/ -$lint-incorrect-color: $red-500; -$lint-correct-color: $green-500; - -/* * Login */ $login-brand-holder-color: #888; -$login-devise-error-color: $red-700; /* * Nav @@ -648,15 +583,12 @@ $nav-toggle-gray: #666; */ $notify-details: #777; $notify-footer: #777; -$notify-new-file: $green-600; -$notify-deleted-file: $red-700; /* * Projects */ $project-option-descr-color: #54565b; $project-breadcrumb-color: #999; -$project-private-forks-notice-odd: $green-600; $project-network-controls-color: #888; $feature-toggle-color: #fff; @@ -665,21 +597,10 @@ $feature-toggle-color-disabled: #999; $feature-toggle-color-enabled: #4a8bee; /* -* Runners -*/ -$runner-state-shared-bg: $green-400; -$runner-state-specific-bg: $blue-400; -$runner-status-online-color: $green-600; -$runner-status-offline-color: $gray-darkest; -$runner-status-paused-color: $red-500; - -/* Stat Graph */ $stat-graph-common-bg: #f3f3f3; -$stat-graph-area-fill: $green-500; $stat-graph-axis-fill: #aaa; -$stat-graph-orange-fill: $orange-500; $stat-graph-selection-fill: #333; $stat-graph-selection-stroke: #333; @@ -692,7 +613,6 @@ $select2-drop-shadow2: rgba(31, 37, 50, 0.317647); /* * Todo */ -$todo-alert-blue: $blue-500; $todo-body-pre-color: #777; $todo-body-border: #ddd; @@ -715,7 +635,6 @@ $ui-dev-kit-example-border: #ddd; /* Pipeline Graph */ -$stage-hover-bg: $gray-darker; $ci-action-icon-size: 22px; $ci-action-icon-size-lg: 24px; $pipeline-dropdown-line-height: 20px; @@ -743,13 +662,6 @@ Animation Functions $dropdown-animation-timing: cubic-bezier(0.23, 1, 0.32, 1); /* -Convdev Index -*/ -$color-high-score: $green-400; -$color-average-score: $orange-400; -$color-low-score: $red-400; - -/* Performance Bar */ $perf-bar-text: #999; @@ -790,9 +702,5 @@ Modals */ $modal-body-height: 134px; -/* -Prometheus -*/ -$prometheus-table-row-highlight-color: $theme-gray-100; $priority-label-empty-state-width: 114px; diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss index b9c343fa2e9..7d90452e1f4 100644 --- a/app/assets/stylesheets/framework/variables_overrides.scss +++ b/app/assets/stylesheets/framework/variables_overrides.scss @@ -14,3 +14,7 @@ $btn-line-height: 20px; $table-accent-bg: $gray-light; $card-border-color: $border-color; $card-cap-bg: $gray-light; +$success: $green-500; +$info: $blue-500; +$warning: $orange-500; +$danger: $red-500; diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 1696d18584d..14ba8b1df83 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -397,7 +397,7 @@ } &:hover { - background-color: $dropdown-item-hover-bg; + background-color: $gray-darker; } .icon-retry { diff --git a/app/assets/stylesheets/pages/convdev_index.scss b/app/assets/stylesheets/pages/convdev_index.scss index bd338326154..52fcdf4a405 100644 --- a/app/assets/stylesheets/pages/convdev_index.scss +++ b/app/assets/stylesheets/pages/convdev_index.scss @@ -80,7 +80,7 @@ $space-between-cards: 8px; } .convdev-card-low { - border-top-color: $color-low-score; + border-top-color: $red-400; .board-card-score-big { background-color: $red-50; @@ -88,7 +88,7 @@ $space-between-cards: 8px; } .convdev-card-average { - border-top-color: $color-average-score; + border-top-color: $orange-400; .board-card-score-big { background-color: $orange-50; @@ -96,7 +96,7 @@ $space-between-cards: 8px; } .convdev-card-high { - border-top-color: $color-high-score; + border-top-color: $green-400; .board-card-score-big { background-color: $green-50; @@ -243,13 +243,13 @@ $space-between-cards: 8px; } .convdev-high-score { - color: $color-high-score; + color: $green-400; } .convdev-average-score { - color: $color-average-score; + color: $orange-400; } .convdev-low-score { - color: $color-low-score; + color: $red-400; } diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss index bc4c90711d7..f0228768b5a 100644 --- a/app/assets/stylesheets/pages/cycle_analytics.scss +++ b/app/assets/stylesheets/pages/cycle_analytics.scss @@ -285,7 +285,7 @@ .total-time { font-size: $cycle-analytics-big-font; - color: $cycle-analytics-dark-text; + color: $gl-text-color; span { color: $gl-text-color; diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index 892da152b5f..04570c057d1 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -23,10 +23,10 @@ } .cancel-btn { - color: $editor-cancel-color; + color: $red-600; &:hover { - color: $editor-cancel-color; + color: $red-600; } } diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 179c0964567..196f6ae6d8c 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -501,5 +501,5 @@ } .prometheus-table-row-highlight { - background-color: $prometheus-table-row-highlight-color; + background-color: $theme-gray-100; } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 6f0f82964c8..9ac47a771a5 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -144,7 +144,7 @@ color: $blue-800; .avatar { - border-color: rgba($avatar-border, .2); + border-color: rgba($gray-normal, .2); } } @@ -231,7 +231,7 @@ } a.edit-link:not([href]):hover { - color: rgba($avatar-border, .2); + color: rgba($gray-normal, .2); } .lock-edit, // uses same style, different js behaviour diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index d32943fceec..d2b9470be69 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -67,7 +67,7 @@ .dropdown-labels-error { padding: 5px 10px; margin-bottom: 10px; - background-color: $gl-danger; + background-color: $red-500; color: $white-light; } @@ -117,7 +117,7 @@ color: $blue-600; &.remove-row { - color: $gl-danger; + color: $red-500; } } } diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 8a4a2caa6c9..c9e5fb9c579 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -186,7 +186,7 @@ h2 { margin-top: 0; font-size: 14px; - color: $login-devise-error-color; + color: $red-700; } } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 621321101cd..7b8cad254c7 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -195,12 +195,13 @@ .ci-widget-content { display: flex; align-items: center; + flex: 1; } } .mr-widget-icon { font-size: 22px; - margin-right: $status-icon-margin; + margin-right: $gl-btn-padding; } .ci-status-icon svg { @@ -222,6 +223,7 @@ .normal { flex: 1; + flex-basis: auto; } .capitalize { @@ -235,22 +237,23 @@ font-weight: normal; overflow: hidden; word-break: break-all; + } - &.label-truncated { - position: relative; - display: inline-block; - width: 250px; - margin-bottom: -3px; - white-space: nowrap; - text-overflow: clip; - line-height: 14px; - - &::after { - position: absolute; - content: '...'; - right: 0; - font-family: $regular-font; - background-color: $gray-light; + .deploy-link, + .label-branch { + &.label-truncate { + // NOTE: This selector targets its children because some of the HTML comes from + // 'source_branch_link'. Once this external HTML is no longer used, we could + // simplify this. + > a, + > span { + display: inline-block; + max-width: 12.5em; + margin-bottom: -3px; + white-space: nowrap; + text-overflow: ellipsis; + line-height: 14px; + overflow: hidden; } } } @@ -281,7 +284,7 @@ margin-bottom: 0; &.has-conflicts .fa-exclamation-triangle { - color: $gl-warning; + color: $orange-500; } time { @@ -313,7 +316,7 @@ } .danger { - color: $gl-danger; + color: $red-500; } .spacing, @@ -514,7 +517,7 @@ } .mr-links { - padding-left: $status-icon-size + $status-icon-margin; + padding-left: $status-icon-size + $gl-btn-padding; } .mr-info-list { @@ -582,7 +585,7 @@ @include media-breakpoint-down(md) { flex-direction: column; - align-items: flex-start; + align-items: stretch; .branch-actions { margin-top: 16px; @@ -593,13 +596,13 @@ .branch-actions { align-self: center; margin-left: $gl-padding; + white-space: nowrap; } } } .diverged-commits-count { color: $gl-text-color-secondary; - font-size: 12px; } } @@ -918,7 +921,7 @@ flex: 1; flex-direction: row; - @include media-breakpoint-down(md) { + @include media-breakpoint-down(sm) { flex-direction: column; .stage-cell .stage-container { diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 4f861d43f55..ac7b701c2e2 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -74,13 +74,13 @@ } &.is-dropzone-hover { - border-color: $gl-success; + border-color: $green-500; box-shadow: 0 0 2px $black-transparent, - 0 0 4px $gl-success-focus; + 0 0 4px $green-500-focus; .comment-toolbar, .nav-links { - border-color: $gl-success; + border-color: $green-500; } } } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 2e1b2126887..fce04c58c24 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -443,7 +443,7 @@ ul.notes { .note-headline-light, .discussion-headline-light { - color: $notes-light-color; + color: $gl-text-color-secondary; } .discussion-headline-light { diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index ad057ed3c83..8bb8b83dc5e 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -19,7 +19,7 @@ background-color: $white-light; &:hover { - background-color: $stage-hover-bg; + background-color: $gray-darker; border: 1px solid $dropdown-toggle-active-border-color; color: $gl-text-color; } @@ -595,7 +595,7 @@ a.build-content:hover, button.build-content:hover { - background-color: $stage-hover-bg; + background-color: $gray-darker; border: 1px solid $dropdown-toggle-active-border-color; } @@ -668,7 +668,7 @@ display: block; &:hover { - background-color: $stage-hover-bg; + background-color: $gray-darker; border: 1px solid $dropdown-toggle-active-border-color; svg { @@ -835,7 +835,7 @@ button.mini-pipeline-graph-dropdown-toggle { display: block; &:hover { - background-color: $stage-hover-bg; + background-color: $gray-darker; border: 1px solid $dropdown-toggle-active-border-color; svg { @@ -934,7 +934,7 @@ button.mini-pipeline-graph-dropdown-toggle { &:focus { outline: none; text-decoration: none; - background-color: $stage-hover-bg; + background-color: $gray-darker; } } } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index c11916454c8..a95e78931b1 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -394,23 +394,23 @@ } .vs-public { - color: $gl-primary; + color: $blue-500; } .vs-internal { - color: $gl-warning; + color: $orange-500; } .vs-private { - color: $gl-success; + color: $green-500; } .lfs-enabled { - color: $gl-success; + color: $green-500; } .lfs-disabled { - color: $gl-warning; + color: $orange-500; } .breadcrumb.repo-breadcrumb { @@ -731,7 +731,7 @@ background-color: transparent; font-size: $gl-font-size; line-height: $gl-btn-line-height; - color: $notes-light-color; + color: $gl-text-color-secondary; } .stat-link { @@ -900,7 +900,7 @@ pre.light-well { .private-forks-notice .private-fork-icon { i:nth-child(1) { - color: $project-private-forks-notice-odd; + color: $green-600; } i:nth-child(2) { @@ -1128,12 +1128,12 @@ pre.light-well { .project-ci-body { .incorrect-syntax { font-size: 18px; - color: $lint-incorrect-color; + color: $red-500; } .correct-syntax { font-size: 18px; - color: $lint-correct-color; + color: $green-500; } } diff --git a/app/assets/stylesheets/pages/runners.scss b/app/assets/stylesheets/pages/runners.scss index 2734faec558..59f01f3e958 100644 --- a/app/assets/stylesheets/pages/runners.scss +++ b/app/assets/stylesheets/pages/runners.scss @@ -4,24 +4,24 @@ color: $white-light; &.runner-state-shared { - background: $runner-state-shared-bg; + background: $green-400; } &.runner-state-specific { - background: $runner-state-specific-bg; + background: $blue-400; } } .runner-status-online { - color: $runner-status-online-color; + color: $green-600; } .runner-status-offline { - color: $runner-status-offline-color; + color: $gray-darkest; } .runner-status-paused { - color: $runner-status-paused-color; + color: $red-500; } .runner { diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index 5b3a468cd1c..77119aea9e2 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -24,12 +24,12 @@ $search-avatar-size: 16px; .form-control:hover, :not[readonly] { border-color: lighten($blue-300, 20%); - box-shadow: 0 0 4px lighten($search-input-focus-shadow-color, 20%); + box-shadow: 0 0 4px lighten($dropdown-input-focus-shadow, 20%); } input[type='checkbox']:hover { - box-shadow: 0 0 2px 2px lighten($search-input-focus-shadow-color, 20%), - 0 0 0 1px lighten($search-input-focus-shadow-color, 20%); + box-shadow: 0 0 2px 2px lighten($dropdown-input-focus-shadow, 20%), + 0 0 0 1px lighten($dropdown-input-focus-shadow, 20%); } .search { @@ -181,7 +181,7 @@ input[type='checkbox']:hover { width: $search-avatar-size; height: $search-avatar-size; border-radius: 50%; - border: 1px solid $avatar-border; + border: 1px solid $gray-normal; } } diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index 5aa4cdec9c3..e351dd7c0bb 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -120,11 +120,11 @@ } .warning-title { - color: $gl-warning; + color: $orange-500; } .danger-title { - color: $gl-danger; + color: $red-500; } .integration-settings-form { diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss index 3f6f5f06075..d331edaa302 100644 --- a/app/assets/stylesheets/pages/stat_graph.scss +++ b/app/assets/stylesheets/pages/stat_graph.scss @@ -5,7 +5,7 @@ } .area { - fill: $stat-graph-area-fill; + fill: $green-500; fill-opacity: 0.5; } @@ -54,7 +54,7 @@ } .area-contributor { - fill: $stat-graph-orange-fill; + fill: $orange-500; } } } diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index 2ef2ee76855..22b39f47bf0 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -107,11 +107,15 @@ module IssuableCollections end def set_sort_order_from_cookie - key = 'issuable_sort' + cookies[remember_sorting_key] = params[:sort] if params[:sort].present? + # fallback to legacy cookie value for backward compatibility + cookies[remember_sorting_key] ||= cookies['issuable_sort'] + cookies[remember_sorting_key] = update_cookie_value(cookies[remember_sorting_key]) + params[:sort] = cookies[remember_sorting_key] + end - cookies[key] = params[:sort] if params[:sort].present? - cookies[key] = update_cookie_value(cookies[key]) - params[:sort] = cookies[key] + def remember_sorting_key + @remember_sorting_key ||= "#{collection_type.downcase}_sort" end def default_sort_order @@ -140,16 +144,14 @@ module IssuableCollections end def finder - strong_memoize(:finder) do - issuable_finder_for(finder_type) - end + @finder ||= issuable_finder_for(finder_type) end def collection_type - @collection_type ||= case finder - when IssuesFinder + @collection_type ||= case finder_type.name + when 'IssuesFinder' 'Issue' - when MergeRequestsFinder + when 'MergeRequestsFinder' 'MergeRequest' end end diff --git a/app/controllers/concerns/renders_commits.rb b/app/controllers/concerns/renders_commits.rb index fb41dc1e8a8..b1c9b1e532f 100644 --- a/app/controllers/concerns/renders_commits.rb +++ b/app/controllers/concerns/renders_commits.rb @@ -1,4 +1,24 @@ module RendersCommits + def limited_commits(commits) + if commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE + [ + commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE), + commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE + ] + else + [commits, 0] + end + end + + # This is used as a helper method in a controller. + # rubocop: disable Gitlab/ModuleWithInstanceVariables + def set_commits_for_rendering(commits) + @total_commit_count = commits.size + limited, @hidden_commit_count = limited_commits(commits) + prepare_commits_for_rendering(limited) + end + # rubocop: enable Gitlab/ModuleWithInstanceVariables + def prepare_commits_for_rendering(commits) Banzai::CommitRenderer.render(commits, @project, current_user) # rubocop:disable Gitlab/ModuleWithInstanceVariables diff --git a/app/controllers/concerns/toggle_award_emoji.rb b/app/controllers/concerns/toggle_award_emoji.rb index ba5b7d33f87..ae0b815f85e 100644 --- a/app/controllers/concerns/toggle_award_emoji.rb +++ b/app/controllers/concerns/toggle_award_emoji.rb @@ -5,7 +5,7 @@ module ToggleAwardEmoji authenticate_user! name = params.require(:name) - if awardable.user_can_award?(current_user, name) + if awardable.user_can_award?(current_user) awardable.toggle_award_emoji(name, current_user) todoable = to_todoable(awardable) diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 9bd51de7e97..6bdc0f79ef2 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -2,8 +2,8 @@ class Groups::MilestonesController < Groups::ApplicationController include MilestoneActions before_action :group_projects - before_action :milestone, only: [:edit, :show, :update, :merge_requests, :participants, :labels] - before_action :authorize_admin_milestones!, only: [:edit, :new, :create, :update] + before_action :milestone, only: [:edit, :show, :update, :merge_requests, :participants, :labels, :destroy] + before_action :authorize_admin_milestones!, only: [:edit, :new, :create, :update, :destroy] def index respond_to do |format| @@ -56,10 +56,21 @@ class Groups::MilestonesController < Groups::ApplicationController redirect_to milestone_path end + def destroy + return render_404 if @milestone.legacy_group_milestone? + + Milestones::DestroyService.new(group, current_user).execute(@milestone) + + respond_to do |format| + format.html { redirect_to group_milestones_path(group), status: :see_other } + format.js { head :ok } + end + end + private def authorize_admin_milestones! - return render_404 unless can?(current_user, :admin_milestones, group) + return render_404 unless can?(current_user, :admin_milestone, group) end def milestone_params diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 44b176d304e..53637780a07 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -101,7 +101,7 @@ class Projects::CommitController < Projects::ApplicationController @branch_name = create_new_branch? ? @commit.cherry_pick_branch_name : @start_branch - create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully cherry-picked.", + create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully cherry-picked into #{@branch_name}.", success_path: -> { successful_change_path }, failure_path: failed_change_path) end diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index 36faea8056e..5546bef850b 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -63,7 +63,7 @@ class Projects::CommitsController < Projects::ApplicationController end @commits = @commits.with_pipeline_status - @commits = prepare_commits_for_rendering(@commits) + @commits = set_commits_for_rendering(@commits) end # Rails 5 sets request.format from the extension. diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index f93e500a07a..a1e12821caf 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -78,7 +78,7 @@ class Projects::CompareController < Projects::ApplicationController end def define_commits - @commits = compare.present? ? prepare_commits_for_rendering(compare.commits) : [] + @commits = compare.present? ? set_commits_for_rendering(@compare.commits) : [] end def define_diffs diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index ef8159aa553..c3ac8e107fb 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -113,7 +113,7 @@ class Projects::IssuesController < Projects::ApplicationController end def referenced_merge_requests - @merge_requests, @closed_by_merge_requests = ::Issues::FetchReferencedMergeRequestsService.new(project, current_user).execute(issue) + @merge_requests, @closed_by_merge_requests = ::Issues::ReferencedMergeRequestsService.new(project, current_user).execute(issue) respond_to do |format| format.json do diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb index 81129456ad8..03d0290ac1d 100644 --- a/app/controllers/projects/merge_requests/creations_controller.rb +++ b/app/controllers/projects/merge_requests/creations_controller.rb @@ -101,7 +101,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap @target_project = @merge_request.target_project @source_project = @merge_request.source_project - @commits = prepare_commits_for_rendering(@merge_request.commits) + @commits = set_commits_for_rendering(@merge_request.commits) @commit = @merge_request.diff_head_commit @labels = LabelsFinder.new(current_user, project_id: @project.id).execute diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 1b069fe507b..d31b58972ca 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -79,7 +79,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo # Get commits from repository # or from cache if already merged @commits = - prepare_commits_for_rendering(@merge_request.commits.with_pipeline_status) + set_commits_for_rendering(@merge_request.commits.with_pipeline_status) render json: { html: view_to_html_string('projects/merge_requests/_commits') } end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 89fe90fd801..7a942c44ac4 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -210,17 +210,6 @@ module CommitsHelper Sanitize.clean(string, remove_contents: true) end - def limited_commits(commits) - if commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE - [ - commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE), - commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE - ] - else - [commits, 0] - end - end - def commit_path(project, commit, merge_request: nil) if merge_request&.persisted? diffs_project_merge_request_path(project, merge_request, commit_id: commit.id) diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb index 4664b1728c4..c65f1565425 100644 --- a/app/helpers/import_helper.rb +++ b/app/helpers/import_helper.rb @@ -5,6 +5,10 @@ module ImportHelper false end + def sanitize_project_name(name) + name.gsub(/[^\w\-]/, '-') + end + def import_project_target(owner, name) namespace = current_user.can_create_group? ? owner : current_user.namespace_path "#{namespace}/#{name}" diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 96dc7ae1185..5b27d1d9404 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -92,14 +92,6 @@ module IssuesHelper end end - def award_user_authored_class(award) - if award == 'thumbsdown' || award == 'thumbsup' - 'user-authored js-user-authored' - else - '' - end - end - def awards_sort(awards) awards.sort_by do |award, award_emojis| if award == "thumbsup" diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 6b4079b4113..18b3badda8d 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -447,7 +447,7 @@ module ProjectsHelper end def project_permissions_panel_data(project) - data = { + { currentSettings: project_permissions_settings(project), canChangeVisibilityLevel: can_change_visibility_level?(project, current_user), allowedVisibilityOptions: project_allowed_visibility_levels(project), @@ -457,8 +457,10 @@ module ProjectsHelper lfsAvailable: Gitlab.config.lfs.enabled, lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') } + end - data.to_json.html_safe + def project_permissions_panel_data_json(project) + project_permissions_panel_data(project).to_json.html_safe end def project_allowed_visibility_levels(project) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index e4aed76f611..526bf7af99b 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -649,8 +649,7 @@ module Ci def keep_around_commits return unless project - project.repository.keep_around(self.sha) - project.repository.keep_around(self.before_sha) + project.repository.keep_around(self.sha, self.before_sha) end def valid_source diff --git a/app/models/commit.rb b/app/models/commit.rb index 27fbdc3e386..594972ad344 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -193,6 +193,7 @@ class Commit # otherwise returns commit message without first line def description return safe_message if full_title.length >= 100 + return no_commit_message if safe_message.blank? safe_message.split("\n", 2)[1].try(:chomp) end diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb index 49981db0d80..4200253053a 100644 --- a/app/models/concerns/awardable.rb +++ b/app/models/concerns/awardable.rb @@ -76,12 +76,8 @@ module Awardable true end - def awardable_votes?(name) - AwardEmoji::UPVOTE_NAME == name || AwardEmoji::DOWNVOTE_NAME == name - end - - def user_can_award?(current_user, name) - awardable_by_user?(current_user, name) && Ability.allowed?(current_user, :award_emoji, self) + def user_can_award?(current_user) + Ability.allowed?(current_user, :award_emoji, self) end def user_authored?(current_user) @@ -117,12 +113,4 @@ module Awardable def normalize_name(name) Gitlab::Emoji.normalize_emoji_name(name) end - - def awardable_by_user?(current_user, name) - if user_authored?(current_user) - !awardable_votes?(normalize_name(name)) - else - true - end - end end diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 58d949315e0..716cf6574d3 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -191,14 +191,18 @@ class DiffNote < Note end def keep_around_commits - project.repository.keep_around(self.original_position.base_sha) - project.repository.keep_around(self.original_position.start_sha) - project.repository.keep_around(self.original_position.head_sha) + shas = [ + self.original_position.base_sha, + self.original_position.start_sha, + self.original_position.head_sha + ] if self.position != self.original_position - project.repository.keep_around(self.position.base_sha) - project.repository.keep_around(self.position.start_sha) - project.repository.keep_around(self.position.head_sha) + shas << self.position.base_sha + shas << self.position.start_sha + shas << self.position.head_sha end + + project.repository.keep_around(*shas) end end diff --git a/app/models/issue.rb b/app/models/issue.rb index 94cf12f3c2b..d0cd7461daa 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -170,27 +170,6 @@ class Issue < ActiveRecord::Base "#{project.to_reference(from, full: full)}#{reference}" end - def referenced_merge_requests(current_user = nil) - ext = all_references(current_user) - - notes_with_associations.each do |object| - object.all_references(current_user, extractor: ext) - end - - merge_requests = ext.merge_requests.sort_by(&:iid) - - cross_project_filter = -> (merge_requests) do - merge_requests.select { |mr| mr.target_project == project } - end - - Ability.merge_requests_readable_by_user( - merge_requests, current_user, - filters: { - read_cross_project: cross_project_filter - } - ) - end - # All branches containing the current issue's ID, except for # those with a merge request open referencing the current issue. def related_branches(current_user) @@ -198,7 +177,11 @@ class Issue < ActiveRecord::Base branch =~ /\A#{iid}-(?!\d+-stable)/i end - branches_with_merge_request = self.referenced_merge_requests(current_user).map(&:source_branch) + branches_with_merge_request = + Issues::ReferencedMergeRequestsService + .new(project, current_user) + .referenced_merge_requests(self) + .map(&:source_branch) branches_with_iid - branches_with_merge_request end @@ -225,26 +208,6 @@ class Issue < ActiveRecord::Base project end - # From all notes on this issue, we'll select the system notes about linked - # merge requests. Of those, the MRs closing `self` are returned. - def closed_by_merge_requests(current_user = nil) - return [] unless open? - - ext = all_references(current_user) - - notes.system.each do |note| - note.all_references(current_user, extractor: ext) - end - - merge_requests = ext.merge_requests.select(&:open?) - if merge_requests.any? - ids = MergeRequestsClosingIssues.where(merge_request_id: merge_requests.map(&:id), issue_id: id).pluck(:merge_request_id) - merge_requests.select { |mr| mr.id.in?(ids) } - else - [] - end - end - def moved? !moved_to.nil? end diff --git a/app/models/member.rb b/app/models/member.rb index 05c0bc8cb97..d9b4e8d2ac6 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -103,7 +103,7 @@ class Member < ActiveRecord::Base def filter_by_2fa(value) case value when 'enabled' - left_join_users.merge(User.with_two_factor_indistinct) + left_join_users.merge(User.with_two_factor) when 'disabled' left_join_users.merge(User.without_two_factor) else diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index d9393b4e545..bbe4f6f7969 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -314,9 +314,7 @@ class MergeRequestDiff < ActiveRecord::Base def keep_around_commits [repository, merge_request.source_project.repository].uniq.each do |repo| - repo.keep_around(start_commit_sha) - repo.keep_around(head_commit_sha) - repo.keep_around(base_commit_sha) + repo.keep_around(start_commit_sha, head_commit_sha, base_commit_sha) end end end diff --git a/app/models/project.rb b/app/models/project.rb index 8f631d7f0ed..67593c9b2fe 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -85,8 +85,7 @@ class Project < ActiveRecord::Base after_create :create_project_feature, unless: :project_feature after_create -> { SiteStatistic.track(STATISTICS_ATTRIBUTE) } - before_destroy ->(project) { project.project_feature.untrack_statistics_for_deletion! } - after_destroy -> { SiteStatistic.untrack(STATISTICS_ATTRIBUTE) } + before_destroy :untrack_site_statistics after_create :create_ci_cd_settings, unless: :ci_cd_settings, @@ -2072,13 +2071,19 @@ class Project < ActiveRecord::Base private def rename_or_migrate_repository! - if Gitlab::CurrentSettings.hashed_storage_enabled? && storage_version != LATEST_STORAGE_VERSION + if Gitlab::CurrentSettings.hashed_storage_enabled? && + storage_upgradable? && + Feature.disabled?(:skip_hashed_storage_upgrade) # kill switch in case we need to disable upgrade behavior ::Projects::HashedStorageMigrationService.new(self, full_path_was).execute else storage.rename_repo end end + def storage_upgradable? + storage_version != LATEST_STORAGE_VERSION + end + def after_rename_repository(full_path_before, path_before) execute_rename_repository_hooks!(full_path_before) @@ -2093,6 +2098,11 @@ class Project < ActiveRecord::Base Gitlab::PagesTransfer.new.rename_project(path_before, self.path, namespace.full_path) end + def untrack_site_statistics + SiteStatistic.untrack(STATISTICS_ATTRIBUTE) + self.project_feature.untrack_statistics_for_deletion! + end + def execute_rename_repository_hooks!(full_path_before) # When we import a project overwriting the original project, there # is a move operation. In that case we don't want to send the instructions. diff --git a/app/models/protected_tag.rb b/app/models/protected_tag.rb index a36f0d36262..94746141945 100644 --- a/app/models/protected_tag.rb +++ b/app/models/protected_tag.rb @@ -4,6 +4,8 @@ class ProtectedTag < ActiveRecord::Base include Gitlab::ShellAdapter include ProtectedRef + validates :name, uniqueness: { scope: :project_id } + protected_ref_access_levels :create def self.protected?(project, ref_name) diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb index 833faf3bc82..c1f53b5da4f 100644 --- a/app/models/remote_mirror.rb +++ b/app/models/remote_mirror.rb @@ -150,6 +150,15 @@ class RemoteMirror < ActiveRecord::Base result.to_s end + def ensure_remote! + return unless project + return unless remote_name && url + + # If this fails or the remote already exists, we won't know due to + # https://gitlab.com/gitlab-org/gitaly/issues/1317 + project.repository.add_remote(remote_name, url) + end + private def raw diff --git a/app/models/repository.rb b/app/models/repository.rb index 69f375dc6f3..cf255c8951f 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -247,15 +247,22 @@ class Repository # Git GC will delete commits from the repository that are no longer in any # branches or tags, but we want to keep some of these commits around, for # example if they have comments or CI builds. - def keep_around(sha) - return unless sha.present? && commit_by(oid: sha) + # + # For Geo's sake, pass in multiple shas rather than calling it multiple times, + # to avoid unnecessary syncing. + def keep_around(*shas) + shas.each do |sha| + begin + next unless sha.present? && commit_by(oid: sha) - return if kept_around?(sha) + next if kept_around?(sha) - # This will still fail if the file is corrupted (e.g. 0 bytes) - raw_repository.write_ref(keep_around_ref_name(sha), sha, shell: false) - rescue Gitlab::Git::CommandError => ex - Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}" + # This will still fail if the file is corrupted (e.g. 0 bytes) + raw_repository.write_ref(keep_around_ref_name(sha), sha, shell: false) + rescue Gitlab::Git::CommandError => ex + Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}" + end + end end def kept_around?(sha) diff --git a/app/models/user.rb b/app/models/user.rb index a6ba90794d6..f21ca1c569f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -289,13 +289,16 @@ class User < ActiveRecord::Base end end - def self.with_two_factor_indistinct - joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id") - .where("u2f.id IS NOT NULL OR users.otp_required_for_login = ?", true) - end - def self.with_two_factor - with_two_factor_indistinct.distinct(arel_table[:id]) + with_u2f_registrations = <<-SQL + EXISTS ( + SELECT * + FROM u2f_registrations AS u2f + WHERE u2f.user_id = users.id + ) OR users.otp_required_for_login = ? + SQL + + where(with_u2f_registrations, true) end def self.without_two_factor diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index a8d7a05f509..73c93b22c95 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -53,7 +53,7 @@ class GroupPolicy < BasePolicy rule { has_access }.enable :read_namespace - rule { developer }.enable :admin_milestones + rule { developer }.enable :admin_milestone rule { reporter }.policy do enable :admin_label @@ -72,6 +72,8 @@ class GroupPolicy < BasePolicy enable :admin_namespace enable :admin_group_member enable :change_visibility_level + + enable :set_note_created_at end rule { can?(:read_nested_project_resources) }.policy do diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 00c58f15013..fd6cc504a3b 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -143,6 +143,10 @@ class ProjectPolicy < BasePolicy enable :destroy_merge_request enable :destroy_issue enable :remove_pages + + enable :set_issue_iid + enable :set_issue_created_at + enable :set_note_created_at end rule { can?(:guest_access) }.policy do diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 637c1df4ad9..26e90e8cf8c 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -140,7 +140,6 @@ class GitPushService < BaseService EventCreateService.new.push(project, current_user, build_push_data) Ci::CreatePipelineService.new(project, current_user, build_push_data).execute(:push) - SystemHookPushWorker.perform_async(build_push_data.dup, :push_hooks) project.execute_hooks(build_push_data.dup, :push_hooks) project.execute_services(build_push_data.dup, :push_hooks) @@ -159,7 +158,7 @@ class GitPushService < BaseService end def process_default_branch - offset = [push_commits_count - PROCESS_COMMIT_LIMIT, 0].max + offset = [push_commits_count_for_ref - PROCESS_COMMIT_LIMIT, 0].max @push_commits = project.repository.commits(params[:newrev], offset: offset, limit: PROCESS_COMMIT_LIMIT) project.after_create_default_branch @@ -173,7 +172,7 @@ class GitPushService < BaseService params[:newrev], params[:ref], @push_commits, - commits_count: push_commits_count) + commits_count: commits_count) end def push_to_existing_branch? @@ -214,8 +213,14 @@ class GitPushService < BaseService end end - def push_commits_count - strong_memoize(:push_commits_count) do + def commits_count + return push_commits_count_for_ref if default_branch? && push_to_new_branch? + + Array(@push_commits).size + end + + def push_commits_count_for_ref + strong_memoize(:push_commits_count_for_ref) do project.repository.commit_count_for_ref(params[:ref]) end end diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb index 12aeba4af71..93d84bd8a9c 100644 --- a/app/services/groups/destroy_service.rb +++ b/app/services/groups/destroy_service.rb @@ -12,12 +12,15 @@ module Groups def execute group.prepare_for_destroy - group.projects.each do |project| + group.projects.includes(:project_feature).each do |project| # Execute the destruction of the models immediately to ensure atomic cleanup. success = ::Projects::DestroyService.new(project, current_user).execute raise DestroyError, "Project #{project.id} can't be deleted" unless success end + # reload the relation to prevent triggering destroy hooks on the projects again + group.projects.reload + group.children.each do |group| # This needs to be synchronous since the namespace gets destroyed below DestroyService.new(group, current_user).execute diff --git a/app/services/issues/fetch_referenced_merge_requests_service.rb b/app/services/issues/fetch_referenced_merge_requests_service.rb deleted file mode 100644 index 5e84f3c81c9..00000000000 --- a/app/services/issues/fetch_referenced_merge_requests_service.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module Issues - class FetchReferencedMergeRequestsService < Issues::BaseService - def execute(issue) - referenced_merge_requests = issue.referenced_merge_requests(current_user) - referenced_merge_requests = Gitlab::IssuableSorter.sort(project, referenced_merge_requests) { |i| i.iid.to_s } - closed_by_merge_requests = issue.closed_by_merge_requests(current_user) - closed_by_merge_requests = Gitlab::IssuableSorter.sort(project, closed_by_merge_requests) { |i| i.iid.to_s } - - [referenced_merge_requests, closed_by_merge_requests] - end - end -end diff --git a/app/services/issues/referenced_merge_requests_service.rb b/app/services/issues/referenced_merge_requests_service.rb new file mode 100644 index 00000000000..40d78502697 --- /dev/null +++ b/app/services/issues/referenced_merge_requests_service.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module Issues + class ReferencedMergeRequestsService < Issues::BaseService + def execute(issue) + referenced = referenced_merge_requests(issue) + closed_by = closed_by_merge_requests(issue) + preloader = ActiveRecord::Associations::Preloader.new + + preloader.preload(referenced + closed_by, + head_pipeline: { project: [:route, { namespace: :route }] }) + + [sort_by_iid(referenced), sort_by_iid(closed_by)] + end + + def referenced_merge_requests(issue) + merge_requests = extract_merge_requests(issue) + + cross_project_filter = -> (merge_requests) do + merge_requests.select { |mr| mr.target_project == project } + end + + Ability.merge_requests_readable_by_user( + merge_requests, + current_user, + filters: { + read_cross_project: cross_project_filter + } + ) + end + + def closed_by_merge_requests(issue) + return [] unless issue.open? + + merge_requests = extract_merge_requests(issue, filter: :system).select(&:open?) + + return [] if merge_requests.empty? + + ids = MergeRequestsClosingIssues.where(merge_request_id: merge_requests.map(&:id), issue_id: issue.id).pluck(:merge_request_id) + merge_requests.select { |mr| mr.id.in?(ids) } + end + + private + + def extract_merge_requests(issue, filter: nil) + ext = issue.all_references(current_user) + notes = issue_notes(issue) + notes = notes.select(&filter) if filter + + notes.each do |note| + note.all_references(current_user, extractor: ext) + end + + ext.merge_requests + end + + def issue_notes(issue) + @issue_notes ||= {} + @issue_notes[issue] ||= issue.notes.includes(:author) + end + + def sort_by_iid(merge_requests) + Gitlab::IssuableSorter.sort(project, merge_requests) { |mr| mr.iid.to_s } + end + end +end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index bc988eb2a26..55750269bb4 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -128,8 +128,7 @@ module MergeRequests # def assign_title_and_description assign_title_and_description_from_single_commit - assign_title_from_issue if target_project.issues_enabled? || target_project.external_issue_tracker - + merge_request.title ||= title_from_issue if target_project.issues_enabled? || target_project.external_issue_tracker merge_request.title ||= source_branch.titleize.humanize merge_request.title = wip_title if compare_commits.empty? @@ -159,20 +158,18 @@ module MergeRequests merge_request.description ||= commit.description.try(:strip) end - def assign_title_from_issue + def title_from_issue return unless issue - merge_request.title = "Resolve \"#{issue.title}\"" if issue.is_a?(Issue) + return "Resolve \"#{issue.title}\"" if issue.is_a?(Issue) - return if merge_request.title.present? + return if issue_iid.blank? - if issue_iid.present? - title_parts = ["Resolve #{issue.to_reference}"] - branch_title = source_branch.downcase.remove(issue_iid.downcase).titleize.humanize + title_parts = ["Resolve #{issue.to_reference}"] + branch_title = source_branch.downcase.remove(issue_iid.downcase).titleize.humanize - title_parts << "\"#{branch_title}\"" if branch_title.present? - merge_request.title = title_parts.join(' ') - end + title_parts << "\"#{branch_title}\"" if branch_title.present? + title_parts.join(' ') end def issue_iid diff --git a/app/services/milestones/destroy_service.rb b/app/services/milestones/destroy_service.rb index 15c04525075..7cda802c120 100644 --- a/app/services/milestones/destroy_service.rb +++ b/app/services/milestones/destroy_service.rb @@ -3,8 +3,6 @@ module Milestones class DestroyService < Milestones::BaseService def execute(milestone) - return unless milestone.project_milestone? - Milestone.transaction do update_params = { milestone: nil } @@ -16,15 +14,21 @@ module Milestones MergeRequests::UpdateService.new(parent, current_user, update_params).execute(merge_request) end - event_service.destroy_milestone(milestone, current_user) - - Event.for_milestone_id(milestone.id).each do |event| - event.target_id = nil - event.save - end + log_destroy_event_for(milestone) milestone.destroy end end + + def log_destroy_event_for(milestone) + return if milestone.group_milestone? + + event_service.destroy_milestone(milestone, current_user) + + Event.for_milestone_id(milestone.id).each do |event| + event.target_id = nil + event.save + end + end end end diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb index 33ad2120a75..cbbb88a9410 100644 --- a/app/services/projects/fork_service.rb +++ b/app/services/projects/fork_service.rb @@ -17,6 +17,14 @@ module Projects link_fork_network(fork_to_project) + # A forked project stores its LFS objects in the `forked_from_project`. + # So the LFS objects become inaccessible, and therefore delete them from + # the database so they'll get cleaned up. + # + # TODO: refactor this to get the correct lfs objects when implementing + # https://gitlab.com/gitlab-org/gitlab-ce/issues/39769 + fork_to_project.lfs_objects_projects.delete_all + fork_to_project end diff --git a/app/services/projects/update_remote_mirror_service.rb b/app/services/projects/update_remote_mirror_service.rb index 4651f7c4f8f..591b38b8151 100644 --- a/app/services/projects/update_remote_mirror_service.rb +++ b/app/services/projects/update_remote_mirror_service.rb @@ -10,6 +10,7 @@ module Projects return success unless remote_mirror.enabled? begin + remote_mirror.ensure_remote! repository.fetch_remote(remote_mirror.remote_name, no_tags: true) opts = {} diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb index 8838ed06324..a4c4c9e4812 100644 --- a/app/services/quick_actions/interpret_service.rb +++ b/app/services/quick_actions/interpret_service.rb @@ -402,7 +402,7 @@ module QuickActions match[1] if match end command :award do |name| - if name && issuable.user_can_award?(current_user, name) + if name && issuable.user_can_award?(current_user) @updates[:emoji_award] = name end end diff --git a/app/views/admin/hook_logs/show.html.haml b/app/views/admin/hook_logs/show.html.haml index 2eb3ac85722..86729dbe7bc 100644 --- a/app/views/admin/hook_logs/show.html.haml +++ b/app/views/admin/hook_logs/show.html.haml @@ -4,7 +4,6 @@ %hr -= link_to 'Resend Request', retry_admin_hook_hook_log_path(@hook, @hook_log), class: "btn btn-default float-right prepend-left-10" += link_to 'Resend Request', retry_admin_hook_hook_log_path(@hook, @hook_log), method: :post, class: "btn btn-default float-right prepend-left-10" = render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log } - diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 029efadd75d..a74e052707f 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -39,6 +39,10 @@ %strong= email.email = link_to remove_email_admin_user_path(@user, email), data: { confirm: "Are you sure you want to remove #{email.email}?" }, method: :delete, class: "btn-sm btn btn-remove float-right", title: 'Remove secondary email', id: "remove_email_#{email.id}" do %i.fa.fa-times + %li + %span.light ID: + %strong + = @user.id %li.two-factor-status %span.light Two-factor Authentication: diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml index 8ca9fb4512e..30d7b21b1b8 100644 --- a/app/views/award_emoji/_awards_block.html.haml +++ b/app/views/award_emoji/_awards_block.html.haml @@ -3,7 +3,7 @@ .awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } } - awards_sort(grouped_emojis).each do |emoji, awards| %button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button", - class: [(award_state_class(awardable, awards, current_user)), (award_user_authored_class(emoji) if user_authored)], + class: [(award_state_class(awardable, awards, current_user))], data: { placement: "bottom", title: award_user_list(awards, current_user) } } = emoji_icon(emoji) %span.award-control-text.js-counter @@ -13,7 +13,6 @@ .award-menu-holder.js-award-holder %button.btn.award-control.has-tooltip.js-add-award{ type: 'button', 'aria-label': _('Add reaction'), - class: ("js-user-authored" if user_authored), data: { title: _('Add reaction'), placement: "bottom" } } %span{ class: "award-control-icon award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face') %span{ class: "award-control-icon award-control-icon-positive" }= custom_icon('emoji_smiley') diff --git a/app/views/ci/runner/_how_to_setup_runner.html.haml b/app/views/ci/runner/_how_to_setup_runner.html.haml index 13f96b9747c..c26eb873718 100644 --- a/app/views/ci/runner/_how_to_setup_runner.html.haml +++ b/app/views/ci/runner/_how_to_setup_runner.html.haml @@ -1,6 +1,6 @@ - link = link_to _("Install GitLab Runner"), 'https://docs.gitlab.com/runner/install/', target: '_blank' .append-bottom-10 - %h4= _("Setup a #{type} Runner manually") + %h4= _("Setup a %{type} Runner manually") % { type: type } %ol %li diff --git a/app/views/ci/status/_dropdown_graph_badge.html.haml b/app/views/ci/status/_dropdown_graph_badge.html.haml index 8b0463db000..9de9143e8b1 100644 --- a/app/views/ci/status/_dropdown_graph_badge.html.haml +++ b/app/views/ci/status/_dropdown_graph_badge.html.haml @@ -6,12 +6,12 @@ - tooltip = "#{subject.name} - #{status.status_tooltip}" - if status.has_details? - = link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip, html: 'true', container: 'body' } do + = link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip, container: 'body' } do %span{ class: klass }= sprite_icon(status.icon) %span.ci-build-text= subject.name - else - .menu-item.mini-pipeline-graph-dropdown-item{ data: { toggle: 'tooltip', html: 'true', title: tooltip, container: 'body' } } + .menu-item.mini-pipeline-graph-dropdown-item{ data: { toggle: 'tooltip', title: tooltip, container: 'body' } } %span{ class: klass }= sprite_icon(status.icon) %span.ci-build-text= subject.name diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index f5f621507b8..b6424df55cd 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -5,7 +5,7 @@ .nav-controls = render 'shared/milestones_sort_dropdown' - - if can?(current_user, :admin_milestones, @group) + - if can?(current_user, :admin_milestone, @group) = link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new" .milestones diff --git a/app/views/import/_githubish_status.html.haml b/app/views/import/_githubish_status.html.haml index f0d1e837317..f4a29ed18dc 100644 --- a/app/views/import/_githubish_status.html.haml +++ b/app/views/import/_githubish_status.html.haml @@ -45,7 +45,7 @@ = text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true %span.input-group-prepend .input-group-text / - = text_field_tag :path, repo.name, class: "input-mini form-control", tabindex: 2, autofocus: true, required: true + = text_field_tag :path, sanitize_project_name(repo.name), class: "input-mini form-control", tabindex: 2, autofocus: true, required: true %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do = has_ci_cd_only_params? ? _('Connect') : _('Import') diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml index a75b7aa9dd2..3b1b5e55302 100644 --- a/app/views/import/bitbucket/status.html.haml +++ b/app/views/import/bitbucket/status.html.haml @@ -63,7 +63,7 @@ = text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true %span.input-group-prepend .input-group-text / - = text_field_tag :path, repo.name, class: "input-mini form-control", tabindex: 2, autofocus: true, required: true + = text_field_tag :path, sanitize_project_name(repo.slug), class: "input-mini form-control", tabindex: 2, autofocus: true, required: true %td.import-actions.job-status = button_tag class: 'btn btn-import js-add-to-import' do = _('Import') diff --git a/app/views/import/bitbucket_server/status.html.haml b/app/views/import/bitbucket_server/status.html.haml index 3d05a5e696f..ae09e0dfa18 100644 --- a/app/views/import/bitbucket_server/status.html.haml +++ b/app/views/import/bitbucket_server/status.html.haml @@ -61,7 +61,7 @@ = text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true %span.input-group-prepend .input-group-text / - = text_field_tag :path, repo.name, class: "input-mini form-control", tabindex: 2, autofocus: true, required: true + = text_field_tag :path, sanitize_project_name(repo.slug), class: "input-mini form-control", tabindex: 2, autofocus: true, required: true %td.import-actions.job-status = button_tag class: 'btn btn-import js-add-to-import' do Import diff --git a/app/views/projects/_issuable_by_email.html.haml b/app/views/projects/_issuable_by_email.html.haml index 22adf5b4008..d59191a6f87 100644 --- a/app/views/projects/_issuable_by_email.html.haml +++ b/app/views/projects/_issuable_by_email.html.haml @@ -19,9 +19,16 @@ = text_field_tag :issuable_email, email, class: "monospace js-select-on-focus form-control", readonly: true .input-group-append = clipboard_button(target: '#issuable_email', class: 'btn btn-clipboard input-group-text btn-transparent d-none d-sm-block') + + - if issuable_type == 'issue' + - enter_title_text = _('Enter the issue title') + - enter_description_text = _('Enter the issue description') + - else + - enter_title_text = _('Enter the merge request title') + - enter_description_text = _('Enter the merge request description') = mail_to email, class: 'btn btn-clipboard btn-transparent', - subject: _("Enter the #{name} title"), - body: _("Enter the #{name} description"), + subject: enter_title_text, + body: enter_description_text, title: _('Send email'), data: { toggle: 'tooltip', placement: 'bottom' } do = sprite_icon('mail') diff --git a/app/views/projects/commits/_commit_list.html.haml b/app/views/projects/commits/_commit_list.html.haml index 8f8eb2c3d5a..6ed65d07202 100644 --- a/app/views/projects/commits/_commit_list.html.haml +++ b/app/views/projects/commits/_commit_list.html.haml @@ -1,9 +1,10 @@ -- commits, hidden = limited_commits(@commits) +- commits = @commits +- hidden = @hidden_commit_count - commits = Commit.decorate(commits, @project) .card .card-header - Commits (#{@commits.count}) + Commits (#{@total_commit_count}) - if hidden > 0 %ul.content-list - commits.each do |commit| diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml index ac6852751be..ec05ff50f25 100644 --- a/app/views/projects/commits/_commits.html.haml +++ b/app/views/projects/commits/_commits.html.haml @@ -2,7 +2,8 @@ - project = local_assigns.fetch(:project) { merge_request&.project } - ref = local_assigns.fetch(:ref) { merge_request&.source_branch } -- commits, hidden = limited_commits(@commits) +- commits = @commits +- hidden = @hidden_commit_count - commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits| %li.commit-header.js-commit-header{ data: { day: day } } diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml index cd0fb21f8a7..ffdca500abe 100644 --- a/app/views/projects/diffs/_line.html.haml +++ b/app/views/projects/diffs/_line.html.haml @@ -32,9 +32,9 @@ %a{ href: "##{line_code}", data: { linenumber: link_text } } %td.line_content.noteable_line{ class: type }< - if email - %pre= line.text + %pre= line.rich_text - else - = diff_line_content(line.text) + = diff_line_content(line.rich_text) - if line_discussions&.any? - discussion_expanded = local_assigns.fetch(:discussion_expanded, line_discussions.any?(&:expanded?)) diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml index 1f0ca211074..e47361354f3 100644 --- a/app/views/projects/diffs/_parallel_view.html.haml +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -24,7 +24,7 @@ - discussion_left = discussions_left.try(:first) - if discussion_left && discussion_left.resolvable? %diff-note-avatars{ "discussion-id" => discussion_left.id } - %td.line_content.parallel.noteable_line.left-side{ id: left_line_code, class: left.type }= diff_line_content(left.text) + %td.line_content.parallel.noteable_line.left-side{ id: left_line_code, class: left.type }= diff_line_content(left.rich_text) - else %td.old_line.diff-line-num.empty-cell %td.line_content.parallel.left-side @@ -45,7 +45,7 @@ - discussion_right = discussions_right.try(:first) - if discussion_right && discussion_right.resolvable? %diff-note-avatars{ "discussion-id" => discussion_right.id } - %td.line_content.parallel.noteable_line.right-side{ id: right_line_code, class: right.type }= diff_line_content(right.text) + %td.line_content.parallel.noteable_line.right-side{ id: right_line_code, class: right.type }= diff_line_content(right.rich_text) - else %td.old_line.diff-line-num.empty-cell %td.line_content.parallel.right-side diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 30544dde451..e37a444c1c9 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -78,7 +78,7 @@ = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f| %input{ name: 'update_section', type: 'hidden', value: 'js-shared-permissions' } -# haml-lint:disable InlineJavaScript - %script.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data(@project) + %script.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data_json(@project) .js-project-permissions-form = f.submit 'Save changes', class: "btn btn-save" diff --git a/app/views/projects/hook_logs/show.html.haml b/app/views/projects/hook_logs/show.html.haml index e51efa85df0..bd8ca5e7d70 100644 --- a/app/views/projects/hook_logs/show.html.haml +++ b/app/views/projects/hook_logs/show.html.haml @@ -4,6 +4,6 @@ Request details .col-lg-9 - = link_to 'Resend Request', retry_project_hook_hook_log_path(@project, @hook, @hook_log), class: "btn btn-default float-right prepend-left-10" + = link_to 'Resend Request', retry_project_hook_hook_log_path(@project, @hook, @hook_log), method: :post, class: "btn btn-default float-right prepend-left-10" = render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log } diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml index 86b2b8bf2f7..acc1e17b811 100644 --- a/app/views/projects/jobs/_sidebar.html.haml +++ b/app/views/projects/jobs/_sidebar.html.haml @@ -82,7 +82,7 @@ - builds.select{|build| build.status == build_status}.each do |build| .build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } } - tooltip = sanitize(build.tooltip_message.dup) - = link_to(project_job_path(@project, build), data: { toggle: 'tooltip', html: 'true', title: tooltip, container: 'body' }) do + = link_to(project_job_path(@project, build), data: { toggle: 'tooltip', title: tooltip, container: 'body' }) do = sprite_icon('arrow-right', size:16, css_class: 'icon-arrow-right') %span{ class: "ci-status-icon-#{build.status}" } = ci_icon_for_status(build.status) diff --git a/app/views/projects/merge_requests/_how_to_merge.html.haml b/app/views/projects/merge_requests/_how_to_merge.html.haml index 62dd21ef6e0..d3871453b9f 100644 --- a/app/views/projects/merge_requests/_how_to_merge.html.haml +++ b/app/views/projects/merge_requests/_how_to_merge.html.haml @@ -1,5 +1,5 @@ #modal_merge_info.modal{ tabindex: '-1' } - .modal-dialog + .modal-dialog.modal-lg .modal-content .modal-header %h3.modal-title Check out, review, and merge locally diff --git a/app/views/projects/merge_requests/creations/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml index f7a5d85500f..d5c4134dee2 100644 --- a/app/views/projects/merge_requests/creations/_new_submit.html.haml +++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml @@ -33,7 +33,7 @@ %li.commits-tab.new-tab = link_to url_for(safe_params), data: {target: 'div#commits', action: 'new', toggle: 'tabvue'} do Commits - %span.badge.badge-pill= @commits.size + %span.badge.badge-pill= @total_commit_count - if @pipelines.any? %li.builds-tab = link_to url_for(safe_params.merge(action: 'pipelines')), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tabvue'} do diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 2a9e20c2caa..0a684f9016a 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -43,18 +43,7 @@ - else = link_to 'Reopen milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped" - %button.js-delete-milestone-button.btn.btn-grouped.btn-danger{ data: { toggle: 'modal', - target: '#delete-milestone-modal', - milestone_id: @milestone.id, - milestone_title: markdown_field(@milestone, :title), - milestone_url: project_milestone_path(@project, @milestone), - milestone_issue_count: @milestone.issues.count, - milestone_merge_request_count: @milestone.merge_requests.count }, - disabled: true } - = _('Delete') - = icon('spin spinner', class: 'js-loading-icon hidden' ) - - #delete-milestone-modal + = render 'shared/milestones/delete_button' %a.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ href: "#" } = icon('angle-double-left') diff --git a/app/views/projects/mirrors/_instructions.html.haml b/app/views/projects/mirrors/_instructions.html.haml index e051f9e6331..35a6885318a 100644 --- a/app/views/projects/mirrors/_instructions.html.haml +++ b/app/views/projects/mirrors/_instructions.html.haml @@ -4,7 +4,9 @@ = _('The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> and <code>git://</code>.').html_safe %li= _('Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>.').html_safe - %li= _("The update action will time out after #{import_will_timeout_message(Gitlab.config.gitlab_shell.git_timeout)} minutes. For big repositories, use a clone/push combination.") + %li + - minutes = Gitlab.config.gitlab_shell.git_timeout / 60 + = _("The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination.") % { number_of_minutes: minutes } %li= _('The Git LFS objects will <strong>not</strong> be synced.').html_safe %li = _('This user will be the author of all events in the activity feed that are the result of an update, diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml index b4fe1cabdfd..e9008d60098 100644 --- a/app/views/projects/notes/_actions.html.haml +++ b/app/views/projects/notes/_actions.html.haml @@ -40,7 +40,7 @@ - if note.emoji_awardable? - user_authored = note.user_authored?(current_user) .note-actions-item - = button_tag title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored} has-tooltip btn btn-transparent", data: { position: 'right', container: 'body' } do + = button_tag title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji} has-tooltip btn btn-transparent", data: { position: 'right', container: 'body' } do = icon('spinner spin') %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face') %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley') diff --git a/app/views/shared/milestones/_delete_button.html.haml b/app/views/shared/milestones/_delete_button.html.haml new file mode 100644 index 00000000000..e236c24b088 --- /dev/null +++ b/app/views/shared/milestones/_delete_button.html.haml @@ -0,0 +1,14 @@ +- milestone_url = @milestone.project_milestone? ? project_milestone_path(@project, @milestone) : group_milestone_path(@group, @milestone) + +%button.js-delete-milestone-button.btn.btn-grouped.btn-danger{ data: { toggle: 'modal', + target: '#delete-milestone-modal', + milestone_id: @milestone.id, + milestone_title: markdown_field(@milestone, :title), + milestone_url: milestone_url, + milestone_issue_count: @milestone.issues.count, + milestone_merge_request_count: @milestone.merge_requests.count }, + disabled: true } + = _('Delete') + = icon('spin spinner', class: 'js-loading-icon hidden' ) + +#delete-milestone-modal diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml index c559945a9c9..3dd2842be4f 100644 --- a/app/views/shared/milestones/_milestone.html.haml +++ b/app/views/shared/milestones/_milestone.html.haml @@ -16,6 +16,9 @@ = milestone_date_range(milestone) %div = render('shared/milestone_expired', milestone: milestone) + - if milestone.group_milestone? + .label-badge.label-badge-blue.d-inline-block + = milestone.group.full_name - if milestone.legacy_group_milestone? .projects - milestone.milestones.each do |milestone| @@ -49,7 +52,7 @@ - unless milestone.active? = link_to 'Reopen Milestone', project_milestone_path(@project, milestone, {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen" - if @group - - if can?(current_user, :admin_milestones, @group) + - if can?(current_user, :admin_milestone, @group) - if milestone.closed? = link_to 'Reopen Milestone', group_milestone_route(milestone, {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen" - else diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml index 320e3788a0f..0499b04a482 100644 --- a/app/views/shared/milestones/_top.html.haml +++ b/app/views/shared/milestones/_top.html.haml @@ -23,7 +23,7 @@ = milestone_date_range(milestone) - if group .float-right - - if can?(current_user, :admin_milestones, group) + - if can?(current_user, :admin_milestone, group) - if milestone.group_milestone? = link_to edit_group_milestone_path(group, milestone), class: "btn btn btn-grouped" do Edit @@ -32,6 +32,9 @@ - else = link_to 'Reopen Milestone', group_milestone_route(milestone, {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen" + - unless is_dynamic_milestone + = render 'shared/milestones/delete_button' + = render 'shared/milestones/deprecation_message' if is_dynamic_milestone .detail-page-description.milestone-detail diff --git a/app/views/snippets/notes/_actions.html.haml b/app/views/snippets/notes/_actions.html.haml index 3a50324770d..e1f7ee80ebb 100644 --- a/app/views/snippets/notes/_actions.html.haml +++ b/app/views/snippets/notes/_actions.html.haml @@ -2,7 +2,7 @@ - if note.emoji_awardable? - user_authored = note.user_authored?(current_user) .note-actions-item - = link_to '#', title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored} has-tooltip", data: { position: 'right' } do + = link_to '#', title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip", data: { position: 'right' } do = icon('spinner spin') %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face') %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley') |