diff options
author | Luke "Jared" Bennett <lbennett@gitlab.com> | 2017-09-20 17:23:00 +0300 |
---|---|---|
committer | Luke "Jared" Bennett <lbennett@gitlab.com> | 2017-09-20 17:23:00 +0300 |
commit | ff7b545c8e462b909af92a5bea58de779c3f438b (patch) | |
tree | bdd6a8bc3c8527df2925288348c752eb87d1b9ea /app | |
parent | 55a7529621515eaeffa1424a281e639cf74be6cf (diff) | |
parent | ff1deab6388dcb7f241205a1cd64eaddf1672753 (diff) |
Merge remote-tracking branch 'origin/master' into 18608-lock-issues
Diffstat (limited to 'app')
91 files changed, 399 insertions, 645 deletions
diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue index 904f7f64fa8..b41d464475f 100644 --- a/app/assets/javascripts/deploy_keys/components/key.vue +++ b/app/assets/javascripts/deploy_keys/components/key.vue @@ -73,7 +73,7 @@ </span> <a v-if="deployKey.can_edit" - class="btn btn-small" + class="btn btn-sm" :href="editDeployKeyPath" > Edit diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js index 298f737a2bc..497c23f014f 100644 --- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js +++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js @@ -4,6 +4,8 @@ import Vue from 'vue'; +import '../mixins/discussion'; + const JumpToDiscussion = Vue.extend({ mixins: [DiscussionMixins], props: { diff --git a/app/assets/javascripts/diff_notes/components/resolve_count.js b/app/assets/javascripts/diff_notes/components/resolve_count.js index 96e5a440357..fe7cf8f5fc1 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_count.js +++ b/app/assets/javascripts/diff_notes/components/resolve_count.js @@ -4,6 +4,8 @@ import Vue from 'vue'; +import '../mixins/discussion'; + window.ResolveCount = Vue.extend({ mixins: [DiscussionMixins], props: { diff --git a/app/assets/javascripts/feature_highlight/feature_highlight.js b/app/assets/javascripts/feature_highlight/feature_highlight.js deleted file mode 100644 index 800ca05cd11..00000000000 --- a/app/assets/javascripts/feature_highlight/feature_highlight.js +++ /dev/null @@ -1,61 +0,0 @@ -import Cookies from 'js-cookie'; -import _ from 'underscore'; -import { - getCookieName, - getSelector, - hidePopover, - setupDismissButton, - mouseenter, - mouseleave, -} from './feature_highlight_helper'; - -export const setupFeatureHighlightPopover = (id, debounceTimeout = 300) => { - const $selector = $(getSelector(id)); - const $parent = $selector.parent(); - const $popoverContent = $parent.siblings('.feature-highlight-popover-content'); - const hideOnScroll = hidePopover.bind($selector); - const debouncedMouseleave = _.debounce(mouseleave, debounceTimeout); - - $selector - // Setup popover - .data('content', $popoverContent.prop('outerHTML')) - .popover({ - html: true, - // Override the existing template to add custom CSS classes - template: ` - <div class="popover feature-highlight-popover" role="tooltip"> - <div class="arrow"></div> - <div class="popover-content"></div> - </div> - `, - }) - .on('mouseenter', mouseenter) - .on('mouseleave', debouncedMouseleave) - .on('inserted.bs.popover', setupDismissButton) - .on('show.bs.popover', () => { - window.addEventListener('scroll', hideOnScroll); - }) - .on('hide.bs.popover', () => { - window.removeEventListener('scroll', hideOnScroll); - }) - // Display feature highlight - .removeAttr('disabled'); -}; - -export const shouldHighlightFeature = (id) => { - const element = document.querySelector(getSelector(id)); - const previouslyDismissed = Cookies.get(getCookieName(id)) === 'true'; - - return element && !previouslyDismissed; -}; - -export const highlightFeatures = (highlightOrder) => { - const featureId = highlightOrder.find(shouldHighlightFeature); - - if (featureId) { - setupFeatureHighlightPopover(featureId); - return true; - } - - return false; -}; diff --git a/app/assets/javascripts/feature_highlight/feature_highlight_helper.js b/app/assets/javascripts/feature_highlight/feature_highlight_helper.js deleted file mode 100644 index 9f741355cd7..00000000000 --- a/app/assets/javascripts/feature_highlight/feature_highlight_helper.js +++ /dev/null @@ -1,57 +0,0 @@ -import Cookies from 'js-cookie'; - -export const getCookieName = cookieId => `feature-highlighted-${cookieId}`; -export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`; - -export const showPopover = function showPopover() { - if (this.hasClass('js-popover-show')) { - return false; - } - this.popover('show'); - this.addClass('disable-animation js-popover-show'); - - return true; -}; - -export const hidePopover = function hidePopover() { - if (!this.hasClass('js-popover-show')) { - return false; - } - this.popover('hide'); - this.removeClass('disable-animation js-popover-show'); - - return true; -}; - -export const dismiss = function dismiss(cookieId) { - Cookies.set(getCookieName(cookieId), true); - hidePopover.call(this); - this.hide(); -}; - -export const mouseleave = function mouseleave() { - if (!$('.popover:hover').length > 0) { - const $featureHighlight = $(this); - hidePopover.call($featureHighlight); - } -}; - -export const mouseenter = function mouseenter() { - const $featureHighlight = $(this); - - const showedPopover = showPopover.call($featureHighlight); - if (showedPopover) { - $('.popover') - .on('mouseleave', mouseleave.bind($featureHighlight)); - } -}; - -export const setupDismissButton = function setupDismissButton() { - const popoverId = this.getAttribute('aria-describedby'); - const cookieId = this.dataset.highlight; - const $popover = $(this); - const dismissWrapper = dismiss.bind($popover, cookieId); - - $(`#${popoverId} .dismiss-feature-highlight`) - .on('click', dismissWrapper); -}; diff --git a/app/assets/javascripts/feature_highlight/feature_highlight_options.js b/app/assets/javascripts/feature_highlight/feature_highlight_options.js deleted file mode 100644 index fd48f2e87cc..00000000000 --- a/app/assets/javascripts/feature_highlight/feature_highlight_options.js +++ /dev/null @@ -1,12 +0,0 @@ -import { highlightFeatures } from './feature_highlight'; -import bp from '../breakpoints'; - -const highlightOrder = ['issue-boards']; - -export default function domContentLoaded(order) { - if (bp.getBreakpointSize() === 'lg') { - highlightFeatures(order); - } -} - -document.addEventListener('DOMContentLoaded', domContentLoaded.bind(this, highlightOrder)); diff --git a/app/assets/javascripts/lib/utils/pretty_time.js b/app/assets/javascripts/lib/utils/pretty_time.js index 227bf65b560..b1ffd797f7e 100644 --- a/app/assets/javascripts/lib/utils/pretty_time.js +++ b/app/assets/javascripts/lib/utils/pretty_time.js @@ -1,68 +1,61 @@ import _ from 'underscore'; -(() => { - /* - * TODO: Make these methods more configurable (e.g. stringifyTime condensed or - * non-condensed, abbreviateTimelengths) - * */ - - const utils = window.gl.utils = gl.utils || {}; - const prettyTime = utils.prettyTime = { - /* - * Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # } - * Seconds can be negative or positive, zero or non-zero. Can be configured for any day - * or week length. - */ - parseSeconds(seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {}) { - const DAYS_PER_WEEK = daysPerWeek; - const HOURS_PER_DAY = hoursPerDay; - const MINUTES_PER_HOUR = 60; - const MINUTES_PER_WEEK = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR; - const MINUTES_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR; - - const timePeriodConstraints = { - weeks: MINUTES_PER_WEEK, - days: MINUTES_PER_DAY, - hours: MINUTES_PER_HOUR, - minutes: 1, - }; +/* + * TODO: Make these methods more configurable (e.g. stringifyTime condensed or + * non-condensed, abbreviateTimelengths) + * */ + +/* + * Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # } + * Seconds can be negative or positive, zero or non-zero. Can be configured for any day + * or week length. +*/ + +export function parseSeconds(seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {}) { + const DAYS_PER_WEEK = daysPerWeek; + const HOURS_PER_DAY = hoursPerDay; + const MINUTES_PER_HOUR = 60; + const MINUTES_PER_WEEK = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR; + const MINUTES_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR; + + const timePeriodConstraints = { + weeks: MINUTES_PER_WEEK, + days: MINUTES_PER_DAY, + hours: MINUTES_PER_HOUR, + minutes: 1, + }; - let unorderedMinutes = prettyTime.secondsToMinutes(seconds); + let unorderedMinutes = Math.abs(seconds / MINUTES_PER_HOUR); - return _.mapObject(timePeriodConstraints, (minutesPerPeriod) => { - const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod); + return _.mapObject(timePeriodConstraints, (minutesPerPeriod) => { + const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod); - unorderedMinutes -= (periodCount * minutesPerPeriod); + unorderedMinutes -= (periodCount * minutesPerPeriod); - return periodCount; - }); - }, + return periodCount; + }); +} - /* - * Accepts a timeObject and returns a condensed string representation of it - * (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included. - */ +/* +* Accepts a timeObject (see parseSeconds) and returns a condensed string representation of it +* (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included. +*/ - stringifyTime(timeObject) { - const reducedTime = _.reduce(timeObject, (memo, unitValue, unitName) => { - const isNonZero = !!unitValue; - return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo; - }, '').trim(); - return reducedTime.length ? reducedTime : '0m'; - }, +export function stringifyTime(timeObject) { + const reducedTime = _.reduce(timeObject, (memo, unitValue, unitName) => { + const isNonZero = !!unitValue; + return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo; + }, '').trim(); + return reducedTime.length ? reducedTime : '0m'; +} - /* - * Accepts a time string of any size (e.g. '1w 2d 3h 5m' or '1w 2d') and returns - * the first non-zero unit/value pair. - */ +/* +* Accepts a time string of any size (e.g. '1w 2d 3h 5m' or '1w 2d') and returns +* the first non-zero unit/value pair. +*/ - abbreviateTime(timeStr) { - return timeStr.split(' ') - .filter(unitStr => unitStr.charAt(0) !== '0')[0]; - }, +export function abbreviateTime(timeStr) { + return timeStr.split(' ') + .filter(unitStr => unitStr.charAt(0) !== '0')[0]; +} - secondsToMinutes(seconds) { - return Math.abs(seconds / 60); - }, - }; -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 0f84470828a..c2a104df749 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -101,7 +101,6 @@ import './label_manager'; import './labels'; import './labels_select'; import './layout_nav'; -import './feature_highlight/feature_highlight_options'; import LazyLoader from './lazy_loader'; import './line_highlighter'; import './logo'; diff --git a/app/assets/javascripts/new_sidebar.js b/app/assets/javascripts/new_sidebar.js index f2eb2338a1e..997550b37fb 100644 --- a/app/assets/javascripts/new_sidebar.js +++ b/app/assets/javascripts/new_sidebar.js @@ -11,6 +11,7 @@ export default class NewNavSidebar { initDomElements() { this.$page = $('.page-with-sidebar'); this.$sidebar = $('.nav-sidebar'); + this.$innerScroll = $('.nav-sidebar-inner-scroll', this.$sidebar); this.$overlay = $('.mobile-overlay'); this.$openSidebar = $('.toggle-mobile-nav'); this.$closeSidebar = $('.close-nav-button'); @@ -55,6 +56,16 @@ export default class NewNavSidebar { this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed); } NewNavSidebar.setCollapsedCookie(collapsed); + + this.toggleSidebarOverflow(); + } + + toggleSidebarOverflow() { + if (this.$innerScroll.prop('scrollHeight') > this.$innerScroll.prop('offsetHeight')) { + this.$innerScroll.css('overflow-y', 'scroll'); + } else { + this.$innerScroll.css('overflow-y', ''); + } } render() { diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js index e3daa8cf949..e754f6c4460 100644 --- a/app/assets/javascripts/shortcuts.js +++ b/app/assets/javascripts/shortcuts.js @@ -1,6 +1,7 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */ /* global Mousetrap */ import Cookies from 'js-cookie'; +import Mousetrap from 'mousetrap'; import findAndFollowLink from './shortcuts_dashboard_navigation'; diff --git a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.js b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.js index 0da265053bd..a9fbc7f1a2f 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.js +++ b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.js @@ -1,6 +1,5 @@ import stopwatchSvg from 'icons/_icon_stopwatch.svg'; - -import '../../../lib/utils/pretty_time'; +import { abbreviateTime } from '../../../lib/utils/pretty_time'; export default { name: 'time-tracking-collapsed-state', @@ -79,7 +78,7 @@ export default { }, methods: { abbreviateTime(timeStr) { - return gl.utils.prettyTime.abbreviateTime(timeStr); + return abbreviateTime(timeStr); }, }, template: ` diff --git a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js index 40f5c89c5bb..fd0d4570d68 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js +++ b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js @@ -1,6 +1,4 @@ -import '../../../lib/utils/pretty_time'; - -const prettyTime = gl.utils.prettyTime; +import { parseSeconds, stringifyTime } from '../../../lib/utils/pretty_time'; export default { name: 'time-tracking-comparison-pane', @@ -23,12 +21,12 @@ export default { }, }, computed: { - parsedRemaining() { + parsedTimeRemaining() { const diffSeconds = this.timeEstimate - this.timeSpent; - return prettyTime.parseSeconds(diffSeconds); + return parseSeconds(diffSeconds); }, timeRemainingHumanReadable() { - return prettyTime.stringifyTime(this.parsedRemaining); + return stringifyTime(this.parsedTimeRemaining); }, timeRemainingTooltip() { const prefix = this.timeRemainingMinutes < 0 ? 'Over by' : 'Time remaining:'; @@ -44,13 +42,6 @@ export default { timeRemainingStatusClass() { return this.timeEstimate >= this.timeSpent ? 'within_estimate' : 'over_estimate'; }, - /* Parsed time values */ - parsedEstimate() { - return prettyTime.parseSeconds(this.timeEstimate); - }, - parsedSpent() { - return prettyTime.parseSeconds(this.timeSpent); - }, }, template: ` <div class="time-tracking-comparison-pane"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js index aaca42e3ebc..219ff94924e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js @@ -72,12 +72,12 @@ export default { <a href="#modal_merge_info" data-toggle="modal" - class="btn btn-small inline"> + class="btn btn-sm inline"> Check out branch </a> <span class="dropdown prepend-left-10"> <a - class="btn btn-small inline dropdown-toggle" + class="btn btn-sm inline dropdown-toggle" data-toggle="dropdown" aria-label="Download as" role="button"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js index 6c2e9ba1d30..c79b5c720eb 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js @@ -12,6 +12,9 @@ export default { ciIcon, }, computed: { + hasPipeline() { + return this.mr.pipeline && Object.keys(this.mr.pipeline).length > 0; + }, hasCIError() { const { hasCI, ciStatus } = this.mr; @@ -28,7 +31,9 @@ export default { }, }, template: ` - <div class="mr-widget-heading"> + <div + v-if="hasPipeline || hasCIError" + class="mr-widget-heading"> <div class="ci-widget media"> <template v-if="hasCIError"> <div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-10"> @@ -40,7 +45,7 @@ export default { Could not connect to the CI server. Please check your settings and try again </div> </template> - <template v-else> + <template v-else-if="hasPipeline"> <div class="ci-status-icon append-right-10"> <a class="icon-link" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js index b01c923311b..703f3a56a34 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js @@ -27,7 +27,7 @@ export default { <button v-if="showDisabledButton" type="button" - class="btn btn-success btn-small" + class="btn btn-success btn-sm" disabled="true"> Merge </button> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js index 2b16a2d6817..b4e4a6aa161 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.js @@ -11,7 +11,7 @@ export default { <status-icon status="failed" /> <button type="button" - class="btn btn-success btn-small" + class="btn btn-success btn-sm" disabled="true"> Merge </button> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js index 65187754009..ad709da51ee 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js @@ -29,6 +29,9 @@ export default { statusIcon, }, computed: { + shouldShowMergeWhenPipelineSucceedsText() { + return this.mr.isPipelineActive; + }, commitMessageLinkTitle() { const withDesc = 'Include description in commit message'; const withoutDesc = "Don't include description in commit message"; @@ -36,7 +39,7 @@ export default { return this.useCommitMessageWithDescription ? withoutDesc : withDesc; }, mergeButtonClass() { - const defaultClass = 'btn btn-small btn-success accept-merge-request'; + const defaultClass = 'btn btn-sm btn-success accept-merge-request'; const failedClass = `${defaultClass} btn-danger`; const inActionClass = `${defaultClass} btn-info`; const { pipeline, isPipelineActive, isPipelineFailed, hasCI, ciStatus } = this.mr; @@ -56,7 +59,7 @@ export default { mergeButtonText() { if (this.isMergingImmediately) { return 'Merge in progress'; - } else if (this.mr.isPipelineActive) { + } else if (this.shouldShowMergeWhenPipelineSucceedsText) { return 'Merge when pipeline succeeds'; } @@ -68,7 +71,7 @@ export default { isMergeButtonDisabled() { const { commitMessage } = this; return Boolean(!commitMessage.length - || !this.isMergeAllowed() + || !this.shouldShowMergeControls() || this.isMakingRequest || this.mr.preventMerge); }, @@ -82,7 +85,12 @@ export default { }, methods: { isMergeAllowed() { - return !(this.mr.onlyAllowMergeIfPipelineSucceeds && this.mr.isPipelineFailed); + return !this.mr.onlyAllowMergeIfPipelineSucceeds || + this.mr.isPipelinePassing || + this.mr.isPipelineSkipped; + }, + shouldShowMergeControls() { + return this.isMergeAllowed() || this.shouldShowMergeWhenPipelineSucceedsText; }, updateCommitMessage() { const cmwd = this.mr.commitMessageWithDescription; @@ -202,8 +210,8 @@ export default { <div class="mr-widget-body media"> <status-icon status="success" /> <div class="media-body"> - <div class="media space-children"> - <span class="btn-group"> + <div class="mr-widget-body-controls media space-children"> + <span class="btn-group append-bottom-5"> <button @click="handleMergeButtonClick()" :disabled="isMergeButtonDisabled" @@ -219,7 +227,7 @@ export default { v-if="shouldShowMergeOptionsDropdown" :disabled="isMergeButtonDisabled" type="button" - class="btn btn-small btn-info dropdown-toggle js-merge-moment" + class="btn btn-sm btn-info dropdown-toggle js-merge-moment" data-toggle="dropdown" aria-label="Select merge moment"> <i @@ -260,8 +268,8 @@ export default { </li> </ul> </span> - <div class="media-body space-children"> - <template v-if="isMergeAllowed()"> + <div class="media-body-wrap space-children"> + <template v-if="shouldShowMergeControls()"> <label> <input id="remove-source-branch-input" @@ -286,7 +294,7 @@ export default { </template> <template v-else> <span class="bold"> - The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure + The pipeline for this merge request has not succeeded yet </span> </template> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js index 0042c48816f..2f237262028 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js @@ -57,7 +57,7 @@ export default { return stateMaps.statesToShowHelpWidget.indexOf(this.mr.state) > -1; }, shouldRenderPipelines() { - return Object.keys(this.mr.pipeline).length || this.mr.hasCI; + return this.mr.hasCI; }, shouldRenderRelatedLinks() { return this.mr.relatedLinks; diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index fbea764b739..29464662578 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -85,7 +85,9 @@ export default class MergeRequestStore { this.ciEnvironmentsStatusPath = data.ci_environments_status_path; this.hasCI = data.has_ci; this.ciStatus = data.ci_status; - this.isPipelineFailed = this.ciStatus ? (this.ciStatus === 'failed' || this.ciStatus === 'canceled') : false; + this.isPipelineFailed = this.ciStatus === 'failed' || this.ciStatus === 'canceled'; + this.isPipelinePassing = this.ciStatus === 'success' || this.ciStatus === 'success_with_warnings'; + this.isPipelineSkipped = this.ciStatus === 'skipped'; this.pipelineDetailedStatus = pipelineStatus; this.isPipelineActive = data.pipeline ? data.pipeline.active : false; this.isPipelineBlocked = pipelineStatus ? pipelineStatus.group === 'manual' : false; diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 35e7a10379f..923d14f2c3d 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -52,4 +52,3 @@ @import "framework/snippets"; @import "framework/memory_graph"; @import "framework/responsive-tables"; -@import "framework/feature_highlight"; diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index fc1cd81ec67..c77160a678b 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -46,15 +46,6 @@ } } -@mixin btn-svg { - svg { - height: 15px; - width: 15px; - position: relative; - top: 2px; - } -} - @mixin btn-color($light, $border-light, $normal, $border-normal, $dark, $border-dark, $color) { background-color: $light; border-color: $border-light; @@ -132,7 +123,6 @@ .btn { @include btn-default; @include btn-white; - @include btn-svg; color: $gl-text-color; @@ -140,7 +130,6 @@ outline: 0; } - &.btn-small, &.btn-sm { padding: 4px 10px; font-size: 13px; @@ -232,6 +221,13 @@ } } + svg { + height: 15px; + width: 15px; + position: relative; + top: 2px; + } + svg, .fa { &:not(:last-child) { diff --git a/app/assets/stylesheets/framework/feature_highlight.scss b/app/assets/stylesheets/framework/feature_highlight.scss deleted file mode 100644 index ebae473df50..00000000000 --- a/app/assets/stylesheets/framework/feature_highlight.scss +++ /dev/null @@ -1,94 +0,0 @@ -.feature-highlight { - position: relative; - margin-left: $gl-padding; - width: 20px; - height: 20px; - cursor: pointer; - - &::before { - content: ''; - display: block; - position: absolute; - top: 6px; - left: 6px; - width: 8px; - height: 8px; - background-color: $blue-500; - border-radius: 50%; - box-shadow: 0 0 0 rgba($blue-500, 0.4); - animation: pulse-highlight 2s infinite; - } - - &:hover::before, - &.disable-animation::before { - animation: none; - } - - &[disabled]::before { - display: none; - } -} - -.is-showing-fly-out { - .feature-highlight { - display: none; - } -} - -.feature-highlight-popover-content { - display: none; - - hr { - margin: $gl-padding * 0.5 0; - } - - .btn-link { - @include btn-svg; - - svg path { - fill: currentColor; - } - } - - .dismiss-feature-highlight { - padding: 0; - } - - svg:first-child { - width: 100%; - background-color: $indigo-50; - border-top-left-radius: 2px; - border-top-right-radius: 2px; - border-bottom: 1px solid darken($gray-normal, 8%); - } -} - -.popover .feature-highlight-popover-content { - display: block; -} - -.feature-highlight-popover { - padding: 0; - - .popover-content { - padding: 0; - } -} - -.feature-highlight-popover-sub-content { - padding: 9px 14px; -} - -@include keyframes(pulse-highlight) { - 0% { - box-shadow: 0 0 0 0 rgba($blue-200, 0.4); - } - - 70% { - box-shadow: 0 0 0 10px transparent; - } - - 100% { - box-shadow: 0 0 0 0 transparent; - } -} diff --git a/app/assets/stylesheets/framework/media_object.scss b/app/assets/stylesheets/framework/media_object.scss index b573052c14a..89c561479cc 100644 --- a/app/assets/stylesheets/framework/media_object.scss +++ b/app/assets/stylesheets/framework/media_object.scss @@ -6,3 +6,7 @@ .media-body { flex: 1; } + +.media-body-wrap { + flex-grow: 1; +} diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index 8030854e527..4d5e3d1eceb 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -192,7 +192,11 @@ $new-sidebar-collapsed-width: 50px; .nav-sidebar-inner-scroll { height: 100%; width: 100%; - overflow: scroll; + overflow: auto; + + @media (min-width: $screen-sm-min) { + overflow: hidden; + } } .with-performance-bar .nav-sidebar { diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss new file mode 100644 index 00000000000..6c555aee20a --- /dev/null +++ b/app/assets/stylesheets/pages/admin.scss @@ -0,0 +1,6 @@ +.info-well { + .admin-well-statistics, + .admin-well-features { + padding-bottom: 46px; + } +} diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 439636fe026..09a14578dd3 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -356,6 +356,10 @@ } } +.mr-widget-body-controls { + flex-wrap: wrap; +} + .mr_source_commit, .mr_target_commit { margin-bottom: 0; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 94439ed94a6..f6cc3fd7952 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -784,6 +784,7 @@ ul.notes { background-color: transparent; border: none; outline: 0; + color: $gray-darkest; transition: color $general-hover-transition-duration $general-hover-transition-curve; &.is-disabled { @@ -807,7 +808,7 @@ ul.notes { } svg { - fill: $gray-darkest; + fill: currentColor; height: 16px; width: 16px; } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 296b6310552..9d03a042aa3 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -209,6 +209,11 @@ } .stage-cell { + @media (min-width: $screen-md-min) { + min-width: 148px; + margin-right: -4px; + } + .mini-pipeline-graph-dropdown-toggle svg { height: $ci-action-icon-size; width: $ci-action-icon-size; diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 7dfcf7b7d9c..4d4d92f9494 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -56,7 +56,6 @@ .tree-content-holder { display: flex; - max-height: 100vh; min-height: 300px; } @@ -156,7 +155,7 @@ list-style-type: none; background: $gray-normal; display: inline-block; - padding: 10px 18px; + padding: #{$gl-padding / 2} $gl-padding; border-right: 1px solid $white-dark; border-bottom: 1px solid $white-dark; white-space: nowrap; @@ -180,10 +179,9 @@ a { @include str-truncated(100px); color: $black; - width: 100px; - text-align: center; vertical-align: middle; text-decoration: none; + margin-right: 12px; &.close { width: auto; @@ -193,6 +191,10 @@ } } + .close-icon:hover { + color: $hint-color; + } + .close-icon, .unsaved-icon { float: right; diff --git a/app/controllers/admin/labels_controller.rb b/app/controllers/admin/labels_controller.rb index cbc7a14ae83..7eb8f758807 100644 --- a/app/controllers/admin/labels_controller.rb +++ b/app/controllers/admin/labels_controller.rb @@ -29,7 +29,7 @@ class Admin::LabelsController < Admin::ApplicationController @label = Labels::UpdateService.new(label_params).execute(@label) if @label.valid? - redirect_to admin_labels_path, notice: 'label was successfully updated.' + redirect_to admin_labels_path, notice: 'Label was successfully updated.' else render :edit end diff --git a/app/controllers/profiles/personal_access_tokens_controller.rb b/app/controllers/profiles/personal_access_tokens_controller.rb index f748d191ef4..c1cc509a748 100644 --- a/app/controllers/profiles/personal_access_tokens_controller.rb +++ b/app/controllers/profiles/personal_access_tokens_controller.rb @@ -38,7 +38,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController end def set_index_vars - @scopes = Gitlab::Auth::AVAILABLE_SCOPES + @scopes = Gitlab::Auth.available_scopes @personal_access_token = finder.build @inactive_personal_access_tokens = finder(state: 'inactive').execute diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 747768eefb1..a9cce578366 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -15,10 +15,14 @@ class Projects::BranchesController < Projects::ApplicationController respond_to do |format| format.html do @refs_pipelines = @project.pipelines.latest_successful_for_refs(@branches.map(&:name)) + # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37429 + Gitlab::GitalyClient.allow_n_plus_1_calls do + @max_commits = @branches.reduce(0) do |memo, branch| + diverging_commit_counts = repository.diverging_commit_counts(branch) + [memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max + end - @max_commits = @branches.reduce(0) do |memo, branch| - diverging_commit_counts = repository.diverging_commit_counts(branch) - [memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max + render end end format.json do diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 1a775def506..a62f05db7db 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -20,7 +20,12 @@ class Projects::CommitController < Projects::ApplicationController apply_diff_view_cookie! respond_to do |format| - format.html + format.html do + # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37599 + Gitlab::GitalyClient.allow_n_plus_1_calls do + render + end + end format.diff { render text: @commit.to_diff } format.patch { render text: @commit.to_patch } end diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 3c8eaa24080..3cb4eb23981 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -17,6 +17,10 @@ class Projects::CompareController < Projects::ApplicationController def show apply_diff_view_cookie! + # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37430 + Gitlab::GitalyClient.allow_n_plus_1_calls do + render + end end def diff_for_path diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb index 3f83bef2c79..68978f8fdd1 100644 --- a/app/controllers/projects/forks_controller.rb +++ b/app/controllers/projects/forks_controller.rb @@ -9,14 +9,12 @@ class Projects::ForksController < Projects::ApplicationController def index base_query = project.forks.includes(:creator) - @forks = base_query.merge(ProjectsFinder.new(current_user: current_user).execute) + forks = ForkProjectsFinder.new(project, params: params.merge(search: params[:filter_projects]), current_user: current_user).execute @total_forks_count = base_query.size - @private_forks_count = @total_forks_count - @forks.size + @private_forks_count = @total_forks_count - forks.size @public_forks_count = @total_forks_count - @private_forks_count - @sort = params[:sort] || 'id_desc' - @forks = @forks.search(params[:filter_projects]) if params[:filter_projects].present? - @forks = @forks.order_by(@sort).page(params[:page]) + @forks = forks.page(params[:page]) respond_to do |format| format.html diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index ab75a68e56a..fe1334c0cfe 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -71,9 +71,6 @@ class Projects::IssuesController < Projects::ApplicationController @noteable = @issue @note = @project.notes.new(noteable: @issue) - @discussions = @issue.discussions - @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable) - respond_to do |format| format.html format.json do @@ -87,9 +84,9 @@ class Projects::IssuesController < Projects::ApplicationController .inc_relations_for_view .includes(:noteable) .fresh - .reject { |n| n.cross_reference_not_visible_for?(current_user) } - prepare_notes_for_rendering(notes) + notes = prepare_notes_for_rendering(notes) + notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) } discussions = Discussion.build_collection(notes, @issue) diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb index d60a24d3f1d..7d16e77ef66 100644 --- a/app/controllers/projects/merge_requests/diffs_controller.rb +++ b/app/controllers/projects/merge_requests/diffs_controller.rb @@ -10,7 +10,10 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic def show @environment = @merge_request.environments_for(current_user).last - render json: { html: view_to_html_string("projects/merge_requests/diffs/_diffs") } + # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37431 + Gitlab::GitalyClient.allow_n_plus_1_calls do + render json: { html: view_to_html_string("projects/merge_requests/diffs/_diffs") } + end end def diff_for_path diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 3aa5dadb5ca..c5204080333 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -56,6 +56,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo close_merge_request_without_source_project check_if_can_be_merged + # Return if the response has already been rendered + return if response_body + respond_to do |format| format.html do # Build a note object for comment form @@ -70,6 +73,11 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo labels set_pipeline_variables + + # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37432 + Gitlab::GitalyClient.allow_n_plus_1_calls do + render + end end format.json do diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb index dfa5e4f7f46..fb68dd771a1 100644 --- a/app/controllers/projects/network_controller.rb +++ b/app/controllers/projects/network_controller.rb @@ -8,19 +8,24 @@ class Projects::NetworkController < Projects::ApplicationController before_action :assign_commit def show - @url = project_network_path(@project, @ref, @options.merge(format: :json)) - @commit_url = project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s") + # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37602 + Gitlab::GitalyClient.allow_n_plus_1_calls do + @url = project_network_path(@project, @ref, @options.merge(format: :json)) + @commit_url = project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s") - respond_to do |format| - format.html do - if @options[:extended_sha1] && !@commit - flash.now[:alert] = "Git revision '#{@options[:extended_sha1]}' does not exist." + respond_to do |format| + format.html do + if @options[:extended_sha1] && !@commit + flash.now[:alert] = "Git revision '#{@options[:extended_sha1]}' does not exist." + end end - end - format.json do - @graph = Network::Graph.new(project, @ref, @commit, @options[:filter_ref]) + format.json do + @graph = Network::Graph.new(project, @ref, @commit, @options[:filter_ref]) + end end + + render end end diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 1eb78d8b522..2fd015df688 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -51,13 +51,16 @@ class Projects::RefsController < Projects::ApplicationController contents.push(*tree.blobs) contents.push(*tree.submodules) - @logs = contents[@offset, @limit].to_a.map do |content| - file = @path ? File.join(@path, content.name) : content.name - last_commit = @repo.last_commit_for_path(@commit.id, file) - { - file_name: content.name, - commit: last_commit - } + # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37433 + @logs = Gitlab::GitalyClient.allow_n_plus_1_calls do + contents[@offset, @limit].to_a.map do |content| + file = @path ? File.join(@path, content.name) : content.name + last_commit = @repo.last_commit_for_path(@commit.id, file) + { + file_name: content.name, + commit: last_commit + } + end end offset = (@offset + @limit) diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb index 6966a7c5fee..4d2fb17a19b 100644 --- a/app/controllers/projects/uploads_controller.rb +++ b/app/controllers/projects/uploads_controller.rb @@ -28,7 +28,7 @@ class Projects::UploadsController < Projects::ApplicationController end def image_or_video? - uploader && uploader.file.exists? && uploader.image_or_video? + uploader && uploader.exists? && uploader.image_or_video? end def uploader_class diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb index 1b4545e4a49..19e38993038 100644 --- a/app/controllers/root_controller.rb +++ b/app/controllers/root_controller.rb @@ -13,7 +13,10 @@ class RootController < Dashboard::ProjectsController before_action :redirect_logged_user, if: -> { current_user.present? } def index - super + # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37434 + Gitlab::GitalyClient.allow_n_plus_1_calls do + super + end end private diff --git a/app/finders/fork_projects_finder.rb b/app/finders/fork_projects_finder.rb new file mode 100644 index 00000000000..28d1b31868e --- /dev/null +++ b/app/finders/fork_projects_finder.rb @@ -0,0 +1,6 @@ +class ForkProjectsFinder < ProjectsFinder + def initialize(project, params: {}, current_user: nil) + project_ids = project.forks.includes(:creator).select(:id) + super(params: params, current_user: current_user, project_ids_relation: project_ids) + end +end diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb index 88d71b0a87b..0c4c4b10fb6 100644 --- a/app/finders/groups_finder.rb +++ b/app/finders/groups_finder.rb @@ -57,7 +57,7 @@ class GroupsFinder < UnionFinder end def owned_groups - current_user&.groups || Group.none + current_user&.owned_groups || Group.none end def include_public_groups? diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 9848497f258..0a2e3c709d9 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -244,6 +244,8 @@ class IssuableFinder end def by_scope(items) + return items.none if current_user_related? && !current_user + case params[:scope] when 'created-by-me', 'authored' items.where(author_id: current_user.id) diff --git a/app/helpers/auto_devops_helper.rb b/app/helpers/auto_devops_helper.rb index c455d18cff8..483b957decb 100644 --- a/app/helpers/auto_devops_helper.rb +++ b/app/helpers/auto_devops_helper.rb @@ -5,6 +5,25 @@ module AutoDevopsHelper can?(current_user, :admin_pipeline, project) && project.has_auto_devops_implicitly_disabled? && !project.repository.gitlab_ci_yml && - project.ci_services.active.none? + !project.ci_service + end + + def auto_devops_warning_message(project) + missing_domain = !project.auto_devops&.has_domain? + missing_service = !project.kubernetes_service&.active? + + if missing_service + params = { + kubernetes: link_to('Kubernetes service', edit_project_service_path(project, 'kubernetes')) + } + + if missing_domain + _('Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly.') % params + else + _('Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly.') % params + end + elsif missing_domain + _('Auto Review Apps and Auto Deploy need a domain name to work correctly.') + end end end diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index 446a59030a6..be8cb358de2 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -94,6 +94,12 @@ module MilestonesHelper end end + def milestone_tooltip_title(milestone) + if milestone.due_date + [milestone.due_date.to_s(:medium), "(#{milestone_remaining_days(milestone)})"].join(' ') + end + end + def milestone_remaining_days(milestone) if milestone.expired? content_tag(:strong, 'Past due') diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb index 88f7702db1e..40d69e30188 100644 --- a/app/helpers/submodule_helper.rb +++ b/app/helpers/submodule_helper.rb @@ -87,10 +87,14 @@ module SubmoduleHelper namespace = @project.namespace.full_path end - [ - namespace_project_path(namespace, base), - namespace_project_tree_path(namespace, base, commit) - ] + begin + [ + namespace_project_path(namespace, base), + namespace_project_tree_path(namespace, base, commit) + ] + rescue ActionController::UrlGenerationError + [nil, nil] + end end def sanitize_submodule_url(url) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 8d017b9b3b1..acaa028eaa2 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -31,6 +31,7 @@ module Ci has_many :auto_canceled_jobs, class_name: 'CommitStatus', foreign_key: 'auto_canceled_by_id' delegate :id, to: :project, prefix: true + delegate :full_path, to: :project, prefix: true validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create validates :sha, presence: { unless: :importing? } @@ -336,7 +337,7 @@ module Ci return @config_processor if defined?(@config_processor) @config_processor ||= begin - Gitlab::Ci::YamlProcessor.new(ci_yaml_file, project.full_path) + Gitlab::Ci::YamlProcessor.new(ci_yaml_file) rescue Gitlab::Ci::YamlProcessor::ValidationError, Psych::SyntaxError => e self.yaml_errors = e.message nil diff --git a/app/models/environment.rb b/app/models/environment.rb index 44e39e21442..b6868ccbe8f 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -6,9 +6,7 @@ class Environment < ActiveRecord::Base belongs_to :project, required: true, validate: true - has_many :deployments, - -> (env) { where(project_id: env.project_id) }, - dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :deployments, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_one :last_deployment, -> { order('deployments.id DESC') }, class_name: 'Deployment' diff --git a/app/models/issue.rb b/app/models/issue.rb index cd5056aae5e..92a454300af 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -275,8 +275,6 @@ class Issue < ActiveRecord::Base end def update_project_counter_caches - return unless update_project_counter_caches? - Projects::OpenIssuesCountService.new(project).refresh_cache end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 2a56bab48a3..8d9a30397a9 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -415,8 +415,11 @@ class MergeRequest < ActiveRecord::Base end def create_merge_request_diff - merge_request_diffs.create - reload_merge_request_diff + # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37435 + Gitlab::GitalyClient.allow_n_plus_1_calls do + merge_request_diffs.create + reload_merge_request_diff + end end def reload_merge_request_diff @@ -955,8 +958,6 @@ class MergeRequest < ActiveRecord::Base end def update_project_counter_caches - return unless update_project_counter_caches? - Projects::OpenMergeRequestsCountService.new(target_project).refresh_cache end diff --git a/app/models/milestone.rb b/app/models/milestone.rb index a3070a12b7c..47e6b785c39 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -162,9 +162,7 @@ class Milestone < ActiveRecord::Base # Milestone.first.to_reference(cross_namespace_project) # => "gitlab-org/gitlab-ce%1" # Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1" # - def to_reference(from_project = nil, format: :iid, full: false) - return if group_milestone? && format != :name - + def to_reference(from_project = nil, format: :name, full: false) format_reference = milestone_format_reference(format) reference = "#{self.class.reference_prefix}#{format_reference}" @@ -241,6 +239,10 @@ class Milestone < ActiveRecord::Base def milestone_format_reference(format = :iid) raise ArgumentError, 'Unknown format' unless [:iid, :name].include?(format) + if group_milestone? && format == :iid + raise ArgumentError, 'Cannot refer to a group milestone by an internal id!' + end + if format == :name && !name.include?('"') %("#{name}") else diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb index 3845e485413..aec7b01e23a 100644 --- a/app/models/network/graph.rb +++ b/app/models/network/graph.rb @@ -61,8 +61,11 @@ module Network @reserved[i] = [] end - commits_sort_by_ref.each do |commit| - place_chain(commit) + # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37436 + Gitlab::GitalyClient.allow_n_plus_1_calls do + commits_sort_by_ref.each do |commit| + place_chain(commit) + end end # find parent spaces for not overlap lines diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb index ec0ebe4d353..1f9d712ef84 100644 --- a/app/models/personal_access_token.rb +++ b/app/models/personal_access_token.rb @@ -28,7 +28,7 @@ class PersonalAccessToken < ActiveRecord::Base protected def validate_scopes - unless revoked || scopes.all? { |scope| Gitlab::Auth::AVAILABLE_SCOPES.include?(scope.to_sym) } + unless revoked || scopes.all? { |scope| Gitlab::Auth.available_scopes.include?(scope.to_sym) } errors.add :scopes, "can only contain available scopes" end end diff --git a/app/models/project.rb b/app/models/project.rb index 94ae0acbe1a..f7221e4f3b2 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -192,7 +192,7 @@ class Project < ActiveRecord::Base accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :project_feature, update_only: true accepts_nested_attributes_for :import_data - accepts_nested_attributes_for :auto_devops + accepts_nested_attributes_for :auto_devops, update_only: true delegate :name, to: :owner, allow_nil: true, prefix: true delegate :members, to: :team, prefix: true diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb index 7af3b6870e2..9a52edbff8e 100644 --- a/app/models/project_auto_devops.rb +++ b/app/models/project_auto_devops.rb @@ -6,6 +6,10 @@ class ProjectAutoDevops < ActiveRecord::Base validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true } + def has_domain? + domain.present? + end + def variables variables = [] variables << { key: 'AUTO_DEVOPS_DOMAIN', value: domain, public: true } if domain.present? diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 09049824ff7..1d35426050e 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -146,7 +146,7 @@ class ProjectTeam def member?(user, min_access_level = Gitlab::Access::GUEST) return false unless user - user.authorized_project?(project, min_access_level) + max_member_access(user.id) >= min_access_level end def human_max_access(user_id) diff --git a/app/models/repository.rb b/app/models/repository.rb index af9911ea045..f11cf1b065d 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -834,10 +834,6 @@ class Repository } end - def user_to_committer(user) - Gitlab::Git.committer_hash(email: user.email, name: user.name) - end - def can_be_merged?(source_sha, target_branch) our_commit = rugged.branches[target_branch].target their_commit = rugged.lookup(source_sha) @@ -859,54 +855,34 @@ class Repository end def revert( - user, commit, branch_name, + user, commit, branch_name, message, start_branch_name: nil, start_project: project) - with_branch( - user, - branch_name, - start_branch_name: start_branch_name, - start_repository: start_project.repository.raw_repository) do |start_commit| - - revert_tree_id = check_revert_content(commit, start_commit.sha) - unless revert_tree_id - raise Repository::CreateTreeError.new('Failed to revert commit') - end - committer = user_to_committer(user) - - create_commit(message: commit.revert_message(user), - author: committer, - committer: committer, - tree: revert_tree_id, - parents: [start_commit.sha]) + with_cache_hooks do + raw_repository.revert( + user: user, + commit: commit.raw, + branch_name: branch_name, + message: message, + start_branch_name: start_branch_name, + start_repository: start_project.repository.raw_repository + ) end end def cherry_pick( - user, commit, branch_name, + user, commit, branch_name, message, start_branch_name: nil, start_project: project) - with_branch( - user, - branch_name, - start_branch_name: start_branch_name, - start_repository: start_project.repository.raw_repository) do |start_commit| - cherry_pick_tree_id = check_cherry_pick_content(commit, start_commit.sha) - unless cherry_pick_tree_id - raise Repository::CreateTreeError.new('Failed to cherry-pick commit') - end - - committer = user_to_committer(user) - - create_commit(message: commit.cherry_pick_message(user), - author: { - email: commit.author_email, - name: commit.author_name, - time: commit.authored_date - }, - committer: committer, - tree: cherry_pick_tree_id, - parents: [start_commit.sha]) + with_cache_hooks do + raw_repository.cherry_pick( + user: user, + commit: commit.raw, + branch_name: branch_name, + message: message, + start_branch_name: start_branch_name, + start_repository: start_project.repository.raw_repository + ) end end @@ -918,36 +894,6 @@ class Repository end end - def check_revert_content(target_commit, source_sha) - args = [target_commit.sha, source_sha] - args << { mainline: 1 } if target_commit.merge_commit? - - revert_index = rugged.revert_commit(*args) - return false if revert_index.conflicts? - - tree_id = revert_index.write_tree(rugged) - return false unless diff_exists?(source_sha, tree_id) - - tree_id - end - - def check_cherry_pick_content(target_commit, source_sha) - args = [target_commit.sha, source_sha] - args << 1 if target_commit.merge_commit? - - cherry_pick_index = rugged.cherrypick_commit(*args) - return false if cherry_pick_index.conflicts? - - tree_id = cherry_pick_index.write_tree(rugged) - return false unless diff_exists?(source_sha, tree_id) - - tree_id - end - - def diff_exists?(sha1, sha2) - rugged.diff(sha1, sha2).size > 0 - end - def merged_to_root_ref?(branch_name) branch_commit = commit(branch_name) root_ref_commit = commit(root_ref) @@ -983,6 +929,7 @@ class Repository def empty_repo? !exists? || !has_visible_content? end + cache_method :empty_repo?, memoize_only: true def search_files_by_content(query, ref) return [] if empty_repo? || query.blank? diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 420991ff6d6..8af9738d75c 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -9,6 +9,7 @@ class GroupPolicy < BasePolicy condition(:has_access) { access_level != GroupMember::NO_ACCESS } condition(:guest) { access_level >= GroupMember::GUEST } + condition(:developer) { access_level >= GroupMember::DEVELOPER } condition(:owner) { access_level >= GroupMember::OWNER } condition(:master) { access_level >= GroupMember::MASTER } condition(:reporter) { access_level >= GroupMember::REPORTER } @@ -33,11 +34,11 @@ class GroupPolicy < BasePolicy rule { admin } .enable :read_group rule { has_projects } .enable :read_group + rule { developer }.enable :admin_milestones rule { reporter }.enable :admin_label rule { master }.policy do enable :create_projects - enable :admin_milestones enable :admin_pipeline enable :admin_build end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index a925fac7d3e..b7b5bd34189 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -155,6 +155,7 @@ class ProjectPolicy < BasePolicy rule { can?(:developer_access) }.policy do enable :admin_merge_request + enable :admin_milestone enable :update_merge_request enable :create_commit_status enable :update_commit_status @@ -178,7 +179,6 @@ class ProjectPolicy < BasePolicy enable :update_project_snippet enable :update_environment enable :update_deployment - enable :admin_milestone enable :admin_project_snippet enable :admin_project_member enable :admin_note diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index 85c2fcf9ea6..b9d0173a2d0 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -12,14 +12,18 @@ module Commits raise NotImplementedError unless repository.respond_to?(action) # rubocop:disable GitlabSecurity/PublicSend + message = @commit.public_send(:"#{action}_message", current_user) + + # rubocop:disable GitlabSecurity/PublicSend repository.public_send( action, current_user, @commit, @branch_name, + message, start_project: @start_project, start_branch_name: @start_branch) - rescue Repository::CreateTreeError + rescue Gitlab::Git::Repository::CreateTreeError error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title(current_user)} automatically. This #{@commit.change_type_title(current_user)} may already have been #{action.to_s.dasherize}ed, or a more recent commit may have updated some of its content." raise ChangeError, error_msg diff --git a/app/services/delete_merged_branches_service.rb b/app/services/delete_merged_branches_service.rb index ff11bd59d29..077268b2388 100644 --- a/app/services/delete_merged_branches_service.rb +++ b/app/services/delete_merged_branches_service.rb @@ -6,15 +6,18 @@ class DeleteMergedBranchesService < BaseService def execute raise Gitlab::Access::AccessDeniedError unless can?(current_user, :push_code, project) - branches = project.repository.branch_names - branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) } - # Prevent deletion of branches relevant to open merge requests - branches -= merge_request_branch_names - # Prevent deletion of protected branches - branches = branches.reject { |branch| project.protected_for?(branch) } + # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37438 + Gitlab::GitalyClient.allow_n_plus_1_calls do + branches = project.repository.branch_names + branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) } + # Prevent deletion of branches relevant to open merge requests + branches -= merge_request_branch_names + # Prevent deletion of protected branches + branches = branches.reject { |branch| project.protected_for?(branch) } - branches.each do |branch| - DeleteBranchService.new(project, current_user).execute(branch) + branches.each do |branch| + DeleteBranchService.new(project, current_user).execute(branch) + end end end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 157539ee05b..f83ece7098f 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -187,6 +187,7 @@ class IssuableBaseService < BaseService after_create(issuable) execute_hooks(issuable) invalidate_cache_counts(issuable, users: issuable.assignees) + issuable.update_project_counter_caches end issuable @@ -198,8 +199,6 @@ class IssuableBaseService < BaseService def after_create(issuable) # To be overridden by subclasses - - issuable.update_project_counter_caches end def before_update(issuable) @@ -208,8 +207,6 @@ class IssuableBaseService < BaseService def after_update(issuable) # To be overridden by subclasses - - issuable.update_project_counter_caches end def update(issuable) @@ -234,6 +231,10 @@ class IssuableBaseService < BaseService before_update(issuable) + # We have to perform this check before saving the issuable as Rails resets + # the changed fields upon calling #save. + update_project_counters = issuable.update_project_counter_caches? + if issuable.with_transaction_returning_status { issuable.save } # We do not touch as it will affect a update on updated_at field ActiveRecord::Base.no_touching do @@ -255,6 +256,8 @@ class IssuableBaseService < BaseService after_update(issuable) issuable.create_new_cross_references!(current_user) execute_hooks(issuable, 'update') + + issuable.update_project_counter_caches if update_project_counters end end diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb index 74459c3342c..0c5cf2c62ad 100644 --- a/app/services/issues/close_service.rb +++ b/app/services/issues/close_service.rb @@ -29,6 +29,7 @@ module Issues todo_service.close_issue(issue, current_user) execute_hooks(issue, 'close') invalidate_cache_counts(issue, users: issue.assignees) + issue.update_project_counter_caches end issue diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb index c0ce01f7523..40213c99014 100644 --- a/app/services/merge_requests/close_service.rb +++ b/app/services/merge_requests/close_service.rb @@ -14,6 +14,7 @@ module MergeRequests todo_service.close_merge_request(merge_request, current_user) execute_hooks(merge_request, 'close') invalidate_cache_counts(merge_request, users: merge_request.assignees) + merge_request.update_project_counter_caches end merge_request diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index 3d53fe0646b..820709583fa 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -13,7 +13,10 @@ module MergeRequests merge_request.source_branch = params[:source_branch] merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) - create(merge_request) + # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37439 + Gitlab::GitalyClient.allow_n_plus_1_calls do + create(merge_request) + end end def before_create(merge_request) diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 06971483992..9ea28733f5f 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -4,7 +4,13 @@ module Notes merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha) note = Notes::BuildService.new(project, current_user, params).execute - return note unless note.valid? + + # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37440 + note_valid = Gitlab::GitalyClient.allow_n_plus_1_calls do + note.valid? + end + + return note unless note_valid # We execute commands (extracted from `params[:note]`) on the noteable # **before** we save the note because if the note consists of commands diff --git a/app/services/projects/count_service.rb b/app/services/projects/count_service.rb index 5e633c37bf8..aa034315280 100644 --- a/app/services/projects/count_service.rb +++ b/app/services/projects/count_service.rb @@ -2,6 +2,11 @@ module Projects # Base class for the various service classes that count project data (e.g. # issues or forks). class CountService + # The version of the cache format. This should be bumped whenever the + # underlying logic changes. This removes the need for explicitly flushing + # all caches. + VERSION = 1 + def initialize(project) @project = project end @@ -37,7 +42,7 @@ module Projects end def cache_key - ['projects', @project.id, cache_key_name] + ['projects', 'count_service', VERSION, @project.id, cache_key_name] end end end diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb index 66d3bcb998a..cbb79376d5f 100644 --- a/app/uploaders/avatar_uploader.rb +++ b/app/uploaders/avatar_uploader.rb @@ -9,7 +9,7 @@ class AvatarUploader < GitlabUploader end def exists? - model.avatar.file && model.avatar.file.exists? + model.avatar.file && model.avatar.file.present? end # We set move_to_store and move_to_cache to 'false' to prevent stealing diff --git a/app/uploaders/gitlab_uploader.rb b/app/uploaders/gitlab_uploader.rb index 05a2091633a..7f72b3ce471 100644 --- a/app/uploaders/gitlab_uploader.rb +++ b/app/uploaders/gitlab_uploader.rb @@ -51,7 +51,7 @@ class GitlabUploader < CarrierWave::Uploader::Base end def exists? - file.try(:exists?) + file.present? end # Override this if you don't want to save files by default to the Rails.root directory diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml index e403a9da616..935787d1a4a 100644 --- a/app/views/admin/appearances/_form.html.haml +++ b/app/views/admin/appearances/_form.html.haml @@ -21,7 +21,7 @@ = image_tag @appearance.logo_url, class: 'appearance-logo-preview' - if @appearance.persisted? %br - = link_to 'Remove logo', logo_admin_appearances_path, data: { confirm: "Logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-logo" + = link_to 'Remove logo', logo_admin_appearances_path, data: { confirm: "Logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-logo" %hr = f.hidden_field :logo_cache = f.file_field :logo, class: "" @@ -38,7 +38,7 @@ = image_tag @appearance.header_logo_url, class: 'appearance-light-logo-preview' - if @appearance.persisted? %br - = link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-logo" + = link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-logo" %hr = f.hidden_field :header_logo_cache = f.file_field :header_logo, class: "" diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 703f4165128..d212c7ca965 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -7,7 +7,7 @@ .row .col-md-4 .info-well - .well-segment.admin-well + .well-segment.admin-well.admin-well-statistics %h4 Statistics %p Forks @@ -43,7 +43,7 @@ = number_with_delimiter(User.active.count) .col-md-4 .info-well - .well-segment.admin-well + .well-segment.admin-well.admin-well-features %h4 Features - sign_up = "Sign up" %p{ "aria-label" => "#{sign_up}: status " + (signup_enabled? ? "on" : "off") } @@ -111,6 +111,10 @@ GitLab API %span.pull-right = API::API::version + %p + Gitaly + %span.pull-right + = Gitlab::GitalyClient.expected_server_version - if Gitlab.config.pages.enabled %p GitLab Pages diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index fed6002528d..b6e1df5f3ac 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -22,7 +22,7 @@ - @hooks.each do |hook| %li .controls - = render 'shared/web_hooks/test_button', triggers: SystemHook::TRIGGERS, hook: hook, button_class: 'btn-small' + = render 'shared/web_hooks/test_button', triggers: SystemHook::TRIGGERS, hook: hook, button_class: 'btn-sm' = link_to 'Edit', edit_admin_hook_path(hook), class: 'btn btn-sm' = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: 'btn btn-remove btn-sm' .monospace= hook.url diff --git a/app/views/feature_highlight/_issue_boards.svg b/app/views/feature_highlight/_issue_boards.svg deleted file mode 100644 index 1522c9d51c9..00000000000 --- a/app/views/feature_highlight/_issue_boards.svg +++ /dev/null @@ -1,98 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="214" height="102" viewBox="0 0 214 102" xmlns:xlink="http://www.w3.org/1999/xlink"> - <defs> - <path id="b" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,27 C48,28.1045695 47.1045695,29 46,29 L2,29 C0.8954305,29 1.3527075e-16,28.1045695 0,27 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/> - <filter id="a" width="102.1%" height="106.9%" x="-1%" y="-1.7%" filterUnits="objectBoundingBox"> - <feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/> - <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/> - </filter> - <path id="d" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/> - <filter id="c" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox"> - <feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/> - <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/> - </filter> - <path id="e" d="M5,0 L53,0 C55.7614237,-5.07265313e-16 58,2.23857625 58,5 L58,91 C58,93.7614237 55.7614237,96 53,96 L5,96 C2.23857625,96 3.38176876e-16,93.7614237 0,91 L0,5 C-3.38176876e-16,2.23857625 2.23857625,5.07265313e-16 5,0 Z"/> - <path id="h" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/> - <filter id="g" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox"> - <feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/> - <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/> - </filter> - <path id="j" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/> - <filter id="i" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox"> - <feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/> - <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/> - </filter> - <path id="l" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/> - <filter id="k" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox"> - <feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/> - <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/> - </filter> - <path id="n" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/> - <filter id="m" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox"> - <feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/> - <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/> - </filter> - <path id="p" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/> - <filter id="o" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox"> - <feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/> - <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/> - </filter> - </defs> - <g fill="none" fill-rule="evenodd"> - <path fill="#D6D4DE" d="M14,21 L62,21 C64.7614237,21 67,23.2385763 67,26 L67,112 C67,114.761424 64.7614237,117 62,117 L14,117 C11.2385763,117 9,114.761424 9,112 L9,26 C9,23.2385763 11.2385763,21 14,21 Z"/> - <g transform="translate(11 23)"> - <path fill="#FFFFFF" d="M5,0 L53,0 C55.7614237,-5.07265313e-16 58,2.23857625 58,5 L58,91 C58,93.7614237 55.7614237,96 53,96 L5,96 C2.23857625,96 3.38176876e-16,93.7614237 0,91 L0,5 C-3.38176876e-16,2.23857625 2.23857625,5.07265313e-16 5,0 Z"/> - <path fill="#FC6D26" d="M4,0 L54,0 C56.209139,-4.05812251e-16 58,1.790861 58,4 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z"/> - <g transform="translate(5 10)"> - <use fill="black" filter="url(#a)" xlink:href="#b"/> - <use fill="#F9F9F9" xlink:href="#b"/> - </g> - <g transform="translate(5 42)"> - <use fill="black" filter="url(#c)" xlink:href="#d"/> - <use fill="#FEF0E8" xlink:href="#d"/> - <path fill="#FEE1D3" d="M9,8 L33,8 C34.1045695,8 35,8.8954305 35,10 C35,11.1045695 34.1045695,12 33,12 L9,12 C7.8954305,12 7,11.1045695 7,10 C7,8.8954305 7.8954305,8 9,8 Z"/> - <path fill="#FDC4A8" d="M9,17 L17,17 C18.1045695,17 19,17.8954305 19,19 C19,20.1045695 18.1045695,21 17,21 L9,21 C7.8954305,21 7,20.1045695 7,19 C7,17.8954305 7.8954305,17 9,17 Z"/> - <path fill="#FC6D26" d="M24,17 L32,17 C33.1045695,17 34,17.8954305 34,19 C34,20.1045695 33.1045695,21 32,21 L24,21 C22.8954305,21 22,20.1045695 22,19 C22,17.8954305 22.8954305,17 24,17 Z"/> - </g> - </g> - <path fill="#D6D4DE" d="M148,26 L196,26 C198.761424,26 201,28.2385763 201,31 L201,117 C201,119.761424 198.761424,122 196,122 L148,122 C145.238576,122 143,119.761424 143,117 L143,31 C143,28.2385763 145.238576,26 148,26 Z"/> - <g transform="translate(145 28)"> - <mask id="f" fill="white"> - <use xlink:href="#e"/> - </mask> - <use fill="#FFFFFF" xlink:href="#e"/> - <path fill="#FC6D26" d="M4,0 L54,0 C56.209139,-4.05812251e-16 58,1.790861 58,4 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z" mask="url(#f)"/> - <g transform="translate(5 10)"> - <use fill="black" filter="url(#g)" xlink:href="#h"/> - <use fill="#F9F9F9" xlink:href="#h"/> - </g> - <g transform="translate(5 42)"> - <use fill="black" filter="url(#i)" xlink:href="#j"/> - <use fill="#FEF0E8" xlink:href="#j"/> - <path fill="#FEE1D3" d="M9 8L33 8C34.1045695 8 35 8.8954305 35 10 35 11.1045695 34.1045695 12 33 12L9 12C7.8954305 12 7 11.1045695 7 10 7 8.8954305 7.8954305 8 9 8zM9 17L13 17C14.1045695 17 15 17.8954305 15 19 15 20.1045695 14.1045695 21 13 21L9 21C7.8954305 21 7 20.1045695 7 19 7 17.8954305 7.8954305 17 9 17z"/> - <path fill="#FC6D26" d="M20,17 L24,17 C25.1045695,17 26,17.8954305 26,19 C26,20.1045695 25.1045695,21 24,21 L20,21 C18.8954305,21 18,20.1045695 18,19 C18,17.8954305 18.8954305,17 20,17 Z"/> - <path fill="#FDC4A8" d="M31,17 L35,17 C36.1045695,17 37,17.8954305 37,19 C37,20.1045695 36.1045695,21 35,21 L31,21 C29.8954305,21 29,20.1045695 29,19 C29,17.8954305 29.8954305,17 31,17 Z"/> - </g> - </g> - <path fill="#D6D4DE" d="M81,14 L129,14 C131.761424,14 134,16.2385763 134,19 L134,105 C134,107.761424 131.761424,110 129,110 L81,110 C78.2385763,110 76,107.761424 76,105 L76,19 C76,16.2385763 78.2385763,14 81,14 Z"/> - <g transform="translate(78 16)"> - <path fill="#FFFFFF" d="M5,0 L53,0 C55.7614237,-5.07265313e-16 58,2.23857625 58,5 L58,91 C58,93.7614237 55.7614237,96 53,96 L5,96 C2.23857625,96 3.38176876e-16,93.7614237 0,91 L0,5 C-3.38176876e-16,2.23857625 2.23857625,5.07265313e-16 5,0 Z"/> - <g transform="translate(5 10)"> - <use fill="black" filter="url(#k)" xlink:href="#l"/> - <use fill="#EFEDF8" xlink:href="#l"/> - <path fill="#E1DBF1" d="M9,8 L33,8 C34.1045695,8 35,8.8954305 35,10 C35,11.1045695 34.1045695,12 33,12 L9,12 C7.8954305,12 7,11.1045695 7,10 C7,8.8954305 7.8954305,8 9,8 Z"/> - <path fill="#6B4FBB" d="M9,17 L13,17 C14.1045695,17 15,17.8954305 15,19 C15,20.1045695 14.1045695,21 13,21 L9,21 C7.8954305,21 7,20.1045695 7,19 C7,17.8954305 7.8954305,17 9,17 Z"/> - <path fill="#C3B8E3" d="M20,17 L28,17 C29.1045695,17 30,17.8954305 30,19 C30,20.1045695 29.1045695,21 28,21 L20,21 C18.8954305,21 18,20.1045695 18,19 C18,17.8954305 18.8954305,17 20,17 Z"/> - </g> - <g transform="translate(5 42)"> - <use fill="black" filter="url(#m)" xlink:href="#n"/> - <use fill="#F9F9F9" xlink:href="#n"/> - </g> - <g transform="translate(5 74)"> - <rect width="34" height="4" x="7" y="7" fill="#E1DBF1" rx="2"/> - <use fill="black" filter="url(#o)" xlink:href="#p"/> - <use fill="#F9F9F9" xlink:href="#p"/> - </g> - <path fill="#6B4FBB" d="M4,0 L54,0 C56.209139,-4.05812251e-16 58,1.790861 58,4 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z"/> - </g> - </g> -</svg> diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 29f1fc6b354..8ec2e2c79fc 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -117,20 +117,6 @@ = link_to project_boards_path(@project), title: boards_link_text do %span = boards_link_text - .feature-highlight.js-feature-highlight{ disabled: true, data: { trigger: 'manual', container: 'body', toggle: 'popover', placement: 'right', highlight: 'issue-boards' } } - .feature-highlight-popover-content - = render 'feature_highlight/issue_boards.svg' - .feature-highlight-popover-sub-content - %span= _('Use') - = link_to 'Issue Boards', project_boards_path(@project) - %span= _('to create customized software development workflows like') - %strong= _('Scrum') - %span= _('or') - %strong= _('Kanban') - %hr - %button.btn-link.dismiss-feature-highlight{ type: 'button' } - %span= _("Got it! Don't show this again") - = custom_icon('thumbs_up') = nav_link(controller: :labels) do = link_to project_labels_path(@project), title: 'Labels' do diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 5d778d67ae7..8abbd828032 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -74,7 +74,9 @@ %h4.prepend-top-0.warning-title Change username %p - Changing your username will change path to all personal projects! + Changing your username can have unintended side effects. + = succeed '.' do + = link_to 'Learn more', help_page_path('user/profile/index', anchor: 'changing-your-username'), target: '_blank' .col-lg-8 = form_for @user, url: update_username_profile_path, method: :put, html: {class: "update-username"} do |f| .form-group diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 5e980314307..d5b83b53ebb 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -27,6 +27,8 @@ - if can?(current_user, :push_code, @project) %div{ class: container_class } + - if show_auto_devops_callout?(@project) + = render 'shared/auto_devops_callout' .prepend-top-20 .empty_wrapper %h3.page-title-empty diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 7dc35be57a6..64c648f201b 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -24,7 +24,7 @@ - if issue.milestone %span.issuable-milestone.hidden-xs - = link_to project_issues_path(issue.project, milestone_title: issue.milestone.title) do + = link_to project_issues_path(issue.project, milestone_title: issue.milestone.title), data: { html: 1, toggle: 'tooltip', title: milestone_tooltip_title(issue.milestone) } do = icon('clock-o') = issue.milestone.title - if issue.due_date diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 0a1ebcb8124..2b5e8711b0a 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -23,7 +23,7 @@ - if merge_request.milestone %span.issuable-milestone.hidden-xs - = link_to project_merge_requests_path(merge_request.project, milestone_title: merge_request.milestone.title) do + = link_to project_merge_requests_path(merge_request.project, milestone_title: merge_request.milestone.title), data: { html: 1, toggle: 'tooltip', title: milestone_tooltip_title(merge_request.milestone) } do = icon('clock-o') = merge_request.milestone.title - if merge_request.target_project.default_branch != merge_request.target_branch diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index 607892a42fd..d88e3d794d3 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -62,7 +62,10 @@ ":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" } %span.line-resolve-btn.is-disabled{ type: "button", ":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" } - = render "shared/icons/icon_status_success.svg" + %template{ 'v-if' => 'resolvedDiscussionCount === discussionCount' } + = render 'shared/icons/icon_status_success_solid.svg' + %template{ 'v-else' => '' } + = render 'shared/icons/icon_resolve_discussion.svg' %span.line-resolve-text {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ resolvedCountText }} resolved = render "discussions/new_issue_for_all_discussions", merge_request: @merge_request diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 819392b8f0c..cc41b908946 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -16,7 +16,9 @@ New project - if import_sources_enabled? %p - Create or Import your project from popular Git services + A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), #{link_to 'among other things', help_page_path("user/project/index.md", anchor: "projects-features"), target: '_blank'}. + %p + All features are enabled when you create a project, but you can disable the ones you don’t need in the project settings. .col-lg-9.js-toggle-container = form_for @project, html: { class: 'new_project' } do |f| .create-project-options diff --git a/app/views/projects/pipelines_settings/_show.html.haml b/app/views/projects/pipelines_settings/_show.html.haml index 324cd423ede..21d01242c0e 100644 --- a/app/views/projects/pipelines_settings/_show.html.haml +++ b/app/views/projects/pipelines_settings/_show.html.haml @@ -3,11 +3,15 @@ = form_for @project, url: project_pipelines_settings_path(@project) do |f| %fieldset.builds-feature .form-group - %p Pipelines need to have Auto DevOps enabled or have a .gitlab-ci.yml configured before you can begin using Continuous Integration and Delivery. %h5 Auto DevOps (Beta) %p - Auto DevOps will automatically build, test, and deploy your application based on a predefined Continious Integration and Delivery configuration. + Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration. + This will happen starting with the next event (e.g.: push) that occurs to the project. = link_to 'Learn more about Auto DevOps', help_page_path('topics/autodevops/index.md') + - message = auto_devops_warning_message(@project) + - if message + %p.settings-message.text-center + = message.html_safe = f.fields_for :auto_devops_attributes, @auto_devops do |form| .radio = form.label :enabled_true do @@ -15,26 +19,24 @@ %strong Enable Auto DevOps %br %span.descr - The Auto DevOps pipeline configuration will be used when there is no .gitlab-ci.yml - in the project. + The Auto DevOps pipeline configuration will be used when there is no <code>.gitlab-ci.yml</code> in the project. .radio = form.label :enabled_false do = form.radio_button :enabled, 'false' %strong Disable Auto DevOps %br %span.descr - A specific .gitlab-ci.yml file needs to be specified before you can begin using Continious Integration and Delivery. + An explicit <code>.gitlab-ci.yml</code> needs to be specified before you can begin using Continious Integration and Delivery. .radio - = form.label :enabled do - = form.radio_button :enabled, nil - %strong - Instance default (status: #{current_application_settings.auto_devops_enabled?}) + = form.label :enabled_nil do + = form.radio_button :enabled, '' + %strong Instance default (#{current_application_settings.auto_devops_enabled? ? 'enabled' : 'disabled'}) %br %span.descr - Follow the instance default to either have Auto DevOps enabled or disabled when there is no project specific .gitlab-ci.yml file specified. + Follow the instance default to either have Auto DevOps enabled or disabled when there is no project specific <code>.gitlab-ci.yml</code>. %br %p - Define a domain used by Auto DevOps to deploy towards, this is required for deploys to succeed. + You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages. = form.text_field :domain, class: 'form-control', placeholder: 'domain.com' %hr diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index d4f71d023c6..47c056d097a 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -13,7 +13,7 @@ %button.btn.js-settings-toggle = expanded ? 'Collapse' : 'Expand' %p - Update your CI/CD configuration, like job timeout. + Update your CI/CD configuration, like job timeout or Auto DevOps. .settings-content.no-animate{ class: ('expanded' if expanded) } = render 'projects/pipelines_settings/show' diff --git a/app/views/projects/settings/integrations/_project_hook.html.haml b/app/views/projects/settings/integrations/_project_hook.html.haml index d5792e95f5a..82516cb4bcf 100644 --- a/app/views/projects/settings/integrations/_project_hook.html.haml +++ b/app/views/projects/settings/integrations/_project_hook.html.haml @@ -10,7 +10,7 @@ %span.append-right-10.inline SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'} = link_to 'Edit', edit_project_hook_path(@project, hook), class: 'btn btn-sm' - = render 'shared/web_hooks/test_button', triggers: ProjectHook::TRIGGERS, hook: hook, button_class: 'btn-small' + = render 'shared/web_hooks/test_button', triggers: ProjectHook::TRIGGERS, hook: hook, button_class: 'btn-sm' = link_to project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-transparent' do %span.sr-only Remove = icon('trash') diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml index 2e1bd5a088c..d0b9e891b82 100644 --- a/app/views/shared/_group_form.html.haml +++ b/app/views/shared/_group_form.html.haml @@ -22,11 +22,9 @@ - if @group.persisted? .alert.alert-warning.prepend-top-10 - %ul - %li Changing group path can have unintended side effects. - %li Renaming group path will rename directory for all related projects - %li It will change web url for access group and group projects. - %li It will change the git path to repositories under this group. + Changing group path can have unintended side effects. + = succeed '.' do + = link_to 'Learn more', help_page_path('user/group/index', anchor: 'changing-a-groups-path'), target: '_blank' .form-group.group-name-holder = f.label :name, class: 'control-label' do diff --git a/app/views/shared/_target_switcher.html.haml b/app/views/shared/_target_switcher.html.haml index 9236868652f..bbe9692a7da 100644 --- a/app/views/shared/_target_switcher.html.haml +++ b/app/views/shared/_target_switcher.html.haml @@ -1,5 +1,5 @@ - dropdown_toggle_text = @ref || @project.default_branch -= form_tag nil, method: :get, style: { display: 'none' }, class: "project-refs-target-form" do += form_tag nil, method: :get, class: "project-refs-form project-refs-target-form" do = hidden_field_tag :destination, destination - if defined?(path) = hidden_field_tag :path, path @@ -7,14 +7,10 @@ = hidden_field_tag key, value, id: nil .dropdown = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project, find: ['branches']), field_name: 'ref', input_field_name: 'new-branch', submit_form_on_click: true, visit: false }, { toggle_class: "js-project-refs-dropdown" } - %ul.dropdown-menu.dropdown-menu-selectable.git-revision-dropdown{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) } - %li - = dropdown_title _("Create a new branch") - %li - = dropdown_input _("Create a new branch") - %li - = dropdown_title _("Select existing branch"), options: {close: false} - %li - = dropdown_filter _("Search branches and tags") - = dropdown_content - = dropdown_loading + .dropdown-menu.dropdown-menu-selectable.git-revision-dropdown{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) } + = dropdown_title _("Create a new branch") + = dropdown_input _("Create a new branch") + = dropdown_title _("Select existing branch"), options: {close: false} + = dropdown_filter _("Search branches and tags") + = dropdown_content + = dropdown_loading diff --git a/app/views/shared/boards/components/_board.html.haml b/app/views/shared/boards/components/_board.html.haml index c5a8b32c772..c687e66fd43 100644 --- a/app/views/shared/boards/components/_board.html.haml +++ b/app/views/shared/boards/components/_board.html.haml @@ -27,7 +27,7 @@ %span.issue-count-badge-count.pull-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' } {{ list.issuesSize }} - if can?(current_user, :admin_list, current_board_parent) - %button.issue-count-badge-add-button.btn.btn-small.btn-default.has-tooltip.js-no-trigger-collapse{ type: "button", + %button.issue-count-badge-add-button.btn.btn-sm.btn-default.has-tooltip.js-no-trigger-collapse{ type: "button", "@click" => "showNewIssueForm", "v-if" => 'list.type !== "closed"', "aria-label" => "New issue", diff --git a/app/views/shared/icons/_thumbs_up.svg b/app/views/shared/icons/_thumbs_up.svg deleted file mode 100644 index 7267462418e..00000000000 --- a/app/views/shared/icons/_thumbs_up.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.33 5h5.282a2 2 0 0 1 1.963 2.38l-.563 2.905a3 3 0 0 1-.243.732l-1.104 2.286A3 3 0 0 1 10.964 15H7a3 3 0 0 1-3-3V5.7a2 2 0 0 1 .436-1.247l3.11-3.9A.632.632 0 0 1 8.486.5l.138.137a1 1 0 0 1 .28.87L8.33 5zM1 6h2v7H1a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"/></svg> diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 2e1c5f6e1b4..7b7411b1e23 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -26,7 +26,7 @@ = icon('clock-o', 'aria-hidden': 'true') %span.milestone-title - if issuable.milestone - %span.has-tooltip{ title: "#{issuable.milestone.title}<br>#{milestone_remaining_days(issuable.milestone)}", data: { container: 'body', html: 1, placement: 'left' } } + %span.has-tooltip{ title: "#{issuable.milestone.title}<br>#{milestone_tooltip_title(issuable.milestone)}", data: { container: 'body', html: 1, placement: 'left' } } = issuable.milestone.title - else None @@ -37,7 +37,7 @@ = link_to 'Edit', '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right' .value.hide-collapsed - if issuable.milestone - = link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_remaining_days(issuable.milestone), data: { container: "body", html: 1 } + = link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_tooltip_title(issuable.milestone), data: { container: "body", html: 1 } - else %span.no-value None diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml index 3739f4c221d..14395bcc661 100644 --- a/app/views/shared/milestones/_issuable.html.haml +++ b/app/views/shared/milestones/_issuable.html.haml @@ -26,6 +26,6 @@ %span.assignee-icon - assignees.each do |assignee| - = link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: assignee.id, state: 'all' }), + = link_to polymorphic_path(issuable_type_args, { milestone_title: @milestone.title, assignee_id: assignee.id, state: 'all' }), class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do - image_tag(avatar_icon(assignee, 16), class: "avatar s16", alt: '') |