diff options
Diffstat (limited to 'app')
96 files changed, 833 insertions, 421 deletions
diff --git a/app/assets/javascripts/branches/branches_delete_modal.js b/app/assets/javascripts/branches/branches_delete_modal.js index af8bcdc1794..cbc28374b80 100644 --- a/app/assets/javascripts/branches/branches_delete_modal.js +++ b/app/assets/javascripts/branches/branches_delete_modal.js @@ -7,6 +7,7 @@ class DeleteModal { this.$branchName = $('.js-branch-name', this.$modal); this.$confirmInput = $('.js-delete-branch-input', this.$modal); this.$deleteBtn = $('.js-delete-branch', this.$modal); + this.$notMerged = $('.js-not-merged', this.$modal); this.bindEvents(); } @@ -16,8 +17,10 @@ class DeleteModal { } setModalData(e) { - this.branchName = e.currentTarget.dataset.branchName || ''; - this.deletePath = e.currentTarget.dataset.deletePath || ''; + const branchData = e.currentTarget.dataset; + this.branchName = branchData.branchName || ''; + this.deletePath = branchData.deletePath || ''; + this.isMerged = !!branchData.isMerged; this.updateModal(); } @@ -30,6 +33,7 @@ class DeleteModal { this.$confirmInput.val(''); this.$deleteBtn.attr('href', this.deletePath); this.$deleteBtn.attr('disabled', true); + this.$notMerged.toggleClass('hidden', this.isMerged); } } diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js b/app/assets/javascripts/filtered_search/dropdown_user.js index 7246ccbb281..720fbc87ea0 100644 --- a/app/assets/javascripts/filtered_search/dropdown_user.js +++ b/app/assets/javascripts/filtered_search/dropdown_user.js @@ -15,6 +15,7 @@ class DropdownUser extends gl.FilteredSearchDropdown { params: { per_page: 20, active: true, + group_id: this.getGroupId(), project_id: this.getProjectId(), current_user: true, }, @@ -47,6 +48,10 @@ class DropdownUser extends gl.FilteredSearchDropdown { super.renderContent(forceShowList); } + getGroupId() { + return this.input.getAttribute('data-group-id'); + } + getProjectId() { return this.input.getAttribute('data-project-id'); } diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js index 4b19f7b4188..ad8254167a2 100644 --- a/app/assets/javascripts/fly_out_nav.js +++ b/app/assets/javascripts/fly_out_nav.js @@ -148,7 +148,7 @@ export const documentMouseMove = (e) => { export const subItemsMouseLeave = (relatedTarget) => { clearTimeout(timeoutId); - if (!relatedTarget.closest(`.${IS_OVER_CLASS}`)) { + if (relatedTarget && !relatedTarget.closest(`.${IS_OVER_CLASS}`)) { hideMenu(currentOpenMenu); } }; diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 7d7f91227f9..2538d9c2093 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -127,13 +127,6 @@ import DropdownUtils from './filtered_search/dropdown_utils'; $('.has-tooltip', $value).tooltip({ container: 'body' }); - return $value.find('a').each(function(i) { - return setTimeout((function(_this) { - return function() { - return gl.animate.animate($(_this), 'pulse'); - }; - })(this), 200 * i); - }); }); }; $dropdown.glDropdown({ diff --git a/app/assets/javascripts/lib/utils/animate.js b/app/assets/javascripts/lib/utils/animate.js deleted file mode 100644 index d93c1d0da59..00000000000 --- a/app/assets/javascripts/lib/utils/animate.js +++ /dev/null @@ -1,49 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-param-reassign, no-void, prefer-template, no-var, new-cap, prefer-arrow-callback, consistent-return, max-len */ -(function() { - (function(w) { - if (w.gl == null) { - w.gl = {}; - } - if (gl.animate == null) { - gl.animate = {}; - } - gl.animate.animate = function($el, animation, options, done) { - if ((options != null ? options.cssStart : void 0) != null) { - $el.css(options.cssStart); - } - $el.removeClass(animation + ' animated').addClass(animation + ' animated').one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function() { - $(this).removeClass(animation + ' animated'); - if (done != null) { - done(); - } - if ((options != null ? options.cssEnd : void 0) != null) { - $el.css(options.cssEnd); - } - }); - }; - gl.animate.animateEach = function($els, animation, time, options, done) { - var dfd; - dfd = $.Deferred(); - if (!$els.length) { - dfd.resolve(); - } - $els.each(function(i) { - setTimeout((function(_this) { - return function() { - var $this; - $this = $(_this); - return gl.animate.animate($this, animation, options, function() { - if (i === $els.length - 1) { - dfd.resolve(); - if (done != null) { - return done(); - } - } - }); - }; - })(this), time * i); - }); - return dfd.promise(); - }; - })(window); -}).call(window); diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 0bc31a56684..0f84470828a 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -39,7 +39,6 @@ import './commit/file'; import './commit/image_file'; // lib/utils -import './lib/utils/animate'; import './lib/utils/bootstrap_linked_tabs'; import './lib/utils/common_utils'; import './lib/utils/datetime_utility'; diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 04579058688..4675b1fcb8f 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -45,7 +45,7 @@ import _ from 'underscore'; if (issueUpdateURL) { milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>'); milestoneLinkNoneTemplate = '<span class="no-value">None</span>'; - collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left"> <%- title %> </span>'); + collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- name %><br /><%- remaining %>" data-placement="left" data-html="true"> <%- title %> </span>'); } return $dropdown.glDropdown({ showMenuAbove: showMenuAbove, @@ -208,6 +208,7 @@ import _ from 'underscore'; if (data.milestone != null) { data.milestone.full_path = _this.currentProject.full_path; data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date); + data.milestone.name = data.milestone.title; $value.html(milestoneLinkTemplate(data.milestone)); return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone)); } else { diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index b596c4f383f..5d96b193fce 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -1,7 +1,6 @@ <script> /* global Flash */ import _ from 'underscore'; - import statusCodes from '../../lib/utils/http_status'; import MonitoringService from '../services/monitoring_service'; import GraphGroup from './graph_group.vue'; import Graph from './graph.vue'; @@ -21,10 +20,9 @@ hasMetrics: gl.utils.convertPermissionToBoolean(metricsData.hasMetrics), documentationPath: metricsData.documentationPath, settingsPath: metricsData.settingsPath, - endpoint: metricsData.additionalMetrics, + metricsEndpoint: metricsData.additionalMetrics, deploymentEndpoint: metricsData.deploymentEndpoint, showEmptyState: true, - backOffRequestCounter: 0, updateAspectRatio: false, updatedAspectRatios: 0, resizeThrottled: {}, @@ -39,50 +37,16 @@ methods: { getGraphsData() { - const maxNumberOfRequests = 3; this.state = 'loading'; - gl.utils.backOff((next, stop) => { - this.service.get().then((resp) => { - if (resp.status === statusCodes.NO_CONTENT) { - this.backOffRequestCounter = this.backOffRequestCounter += 1; - if (this.backOffRequestCounter < maxNumberOfRequests) { - next(); - } else { - stop(new Error('Failed to connect to the prometheus server')); - } - } else { - stop(resp); - } - }).catch(stop); - }) - .then((resp) => { - if (resp.status === statusCodes.NO_CONTENT) { - this.state = 'unableToConnect'; - return false; - } - return resp.json(); - }) - .then((metricGroupsData) => { - if (!metricGroupsData) return false; - this.store.storeMetrics(metricGroupsData.data); - return this.getDeploymentData(); - }) - .then((deploymentData) => { - if (deploymentData !== false) { - this.store.storeDeploymentData(deploymentData.deployments); - this.showEmptyState = false; - } - return {}; - }) - .catch(() => { - this.state = 'unableToConnect'; - }); - }, - - getDeploymentData() { - return this.service.getDeploymentData(this.deploymentEndpoint) - .then(resp => resp.json()) - .catch(() => new Flash('Error getting deployment information.')); + Promise.all([ + this.service.getGraphsData() + .then(data => this.store.storeMetrics(data)), + this.service.getDeploymentData() + .then(data => this.store.storeDeploymentData(data)) + .catch(() => new Flash('Error getting deployment information.')), + ]) + .then(() => { this.showEmptyState = false; }) + .catch(() => { this.state = 'unableToConnect'; }); }, resize() { @@ -99,7 +63,10 @@ }, created() { - this.service = new MonitoringService(this.endpoint); + this.service = new MonitoringService({ + metricsEndpoint: this.metricsEndpoint, + deploymentEndpoint: this.deploymentEndpoint, + }); eventHub.$on('toggleAspectRatio', this.toggleAspectRatio); }, diff --git a/app/assets/javascripts/monitoring/services/monitoring_service.js b/app/assets/javascripts/monitoring/services/monitoring_service.js index 1e9ae934853..4ed651d5740 100644 --- a/app/assets/javascripts/monitoring/services/monitoring_service.js +++ b/app/assets/javascripts/monitoring/services/monitoring_service.js @@ -1,19 +1,54 @@ import Vue from 'vue'; import VueResource from 'vue-resource'; +import statusCodes from '../../lib/utils/http_status'; Vue.use(VueResource); +const MAX_REQUESTS = 3; + +function backOffRequest(makeRequestCallback) { + let requestCounter = 0; + return gl.utils.backOff((next, stop) => { + makeRequestCallback().then((resp) => { + if (resp.status === statusCodes.NO_CONTENT) { + requestCounter += 1; + if (requestCounter < MAX_REQUESTS) { + next(); + } else { + stop(new Error('Failed to connect to the prometheus server')); + } + } else { + stop(resp); + } + }).catch(stop); + }); +} + export default class MonitoringService { - constructor(endpoint) { - this.graphs = Vue.resource(endpoint); + constructor({ metricsEndpoint, deploymentEndpoint }) { + this.metricsEndpoint = metricsEndpoint; + this.deploymentEndpoint = deploymentEndpoint; } - get() { - return this.graphs.get(); + getGraphsData() { + return backOffRequest(() => Vue.http.get(this.metricsEndpoint)) + .then(resp => resp.json()) + .then((response) => { + if (!response || !response.data) { + throw new Error('Unexpected metrics data response from prometheus endpoint'); + } + return response.data; + }); } - // eslint-disable-next-line class-methods-use-this - getDeploymentData(endpoint) { - return Vue.http.get(endpoint); + getDeploymentData() { + return backOffRequest(() => Vue.http.get(this.deploymentEndpoint)) + .then(resp => resp.json()) + .then((response) => { + if (!response || !response.deployments) { + throw new Error('Unexpected deployment data response from prometheus endpoint'); + } + return response.deployments; + }); } } diff --git a/app/assets/javascripts/new_sidebar.js b/app/assets/javascripts/new_sidebar.js index cea4f35096a..f2eb2338a1e 100644 --- a/app/assets/javascripts/new_sidebar.js +++ b/app/assets/javascripts/new_sidebar.js @@ -15,7 +15,6 @@ export default class NewNavSidebar { this.$openSidebar = $('.toggle-mobile-nav'); this.$closeSidebar = $('.close-nav-button'); this.$sidebarToggle = $('.js-toggle-sidebar'); - this.$topLevelLinks = $('.sidebar-top-level-items > li > a'); } bindEvents() { @@ -56,10 +55,6 @@ export default class NewNavSidebar { this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed); } NewNavSidebar.setCollapsedCookie(collapsed); - - this.$topLevelLinks.attr('title', function updateTopLevelTitle() { - return collapsed ? this.getAttribute('aria-label') : ''; - }); } render() { diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index c0524bf6aa3..35e7a10379f 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -19,6 +19,7 @@ @import "framework/flash"; @import "framework/forms"; @import "framework/gfm"; +@import "framework/gitlab-theme"; @import "framework/header"; @import "framework/highlight"; @import "framework/issue_box"; diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss new file mode 100644 index 00000000000..f844d6f1d5a --- /dev/null +++ b/app/assets/stylesheets/framework/gitlab-theme.scss @@ -0,0 +1,281 @@ +/** + * Styles the GitLab application with a specific color theme + */ + +@mixin gitlab-theme($color-100, $color-200, $color-500, $color-700, $color-800, $color-900, $color-alternate) { + // Header + + header.navbar-gitlab-new { + background: linear-gradient(to right, $color-900, $color-800); + + .navbar-collapse { + color: $color-200; + } + + .container-fluid { + .navbar-toggle { + border-left: 1px solid lighten($color-700, 10%); + } + } + + .navbar-sub-nav, + .navbar-nav { + > li { + > a:hover, + > a:focus { + background-color: rgba($color-200, .2); + } + + &.active > a, + &.dropdown.open > a { + color: $color-900; + background-color: $color-alternate; + + svg { + fill: currentColor; + } + } + + &.line-separator { + border-left: 1px solid rgba($color-200, .2); + } + } + } + + .navbar-sub-nav { + color: $color-200; + } + + .nav { + > li { + color: $color-200; + + > a { + svg { + fill: $color-200; + } + + &.header-user-dropdown-toggle { + .header-user-avatar { + border-color: $color-200; + } + } + + &:hover, + &:focus { + @media (min-width: $screen-sm-min) { + background-color: rgba($color-200, .2); + } + + svg { + fill: currentColor; + } + } + } + + &.active > a, + &.dropdown.open > a { + color: $color-900; + background-color: $color-alternate; + + &:hover { + svg { + fill: $color-900; + } + } + } + + .impersonated-user, + .impersonated-user:hover { + svg { + fill: $color-900; + } + } + } + } + } + + .title { + > a { + &:hover, + &:focus { + background-color: rgba($color-200, .2); + } + } + } + + .search { + form { + background-color: rgba($color-200, .2); + + &:hover { + background-color: rgba($color-200, .3); + } + } + + .location-badge { + color: $color-100; + background-color: rgba($color-200, .1); + border-right: 1px solid $color-800; + } + + .search-input::placeholder { + color: rgba($color-200, .8); + } + + .search-input-wrap { + .search-icon, + .clear-icon { + color: rgba($color-200, .8); + } + } + + &.search-active { + form { + background-color: $white-light; + } + + .location-badge { + color: $gl-text-color; + } + + .search-input-wrap { + .search-icon { + color: rgba($color-200, .8); + } + } + } + } + + .btn-sign-in { + background-color: $color-100; + color: $color-900; + } + + + // Sidebar + .nav-sidebar li.active { + box-shadow: inset 4px 0 0 $color-700; + + > a { + color: $color-800; + } + + svg { + fill: $color-800; + } + } + + .sidebar-top-level-items > li.active .badge { + color: $color-800; + } + + .nav-links li.active a { + border-bottom-color: $color-500; + + .badge { + font-weight: $gl-font-weight-bold; + } + } +} + + +body { + &.ui_indigo { + @include gitlab-theme($indigo-100, $indigo-200, $indigo-500, $indigo-700, $indigo-800, $indigo-900, $white-light); + } + + &.ui_dark { + @include gitlab-theme($theme-gray-100, $theme-gray-200, $theme-gray-500, $theme-gray-700, $theme-gray-800, $theme-gray-900, $white-light); + } + + &.ui_blue { + @include gitlab-theme($theme-blue-100, $theme-blue-200, $theme-blue-500, $theme-blue-700, $theme-blue-800, $theme-blue-900, $white-light); + } + + &.ui_green { + @include gitlab-theme($theme-green-100, $theme-green-200, $theme-green-500, $theme-green-700, $theme-green-800, $theme-green-900, $white-light); + } + + &.ui_light { + @include gitlab-theme($theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-700, $theme-gray-700, $theme-gray-100, $theme-gray-700); + + header.navbar-gitlab-new { + background: $theme-gray-100; + box-shadow: 0 2px 0 0 $border-color; + + .logo-text svg { + fill: $theme-gray-900; + } + + .navbar-sub-nav, + .navbar-nav { + > li { + > a:hover, + > a:focus { + color: $theme-gray-900; + } + + &.active > a { + color: $white-light; + + &:hover { + color: $white-light; + } + } + } + } + + .container-fluid { + .navbar-toggle, + .navbar-toggle:hover { + color: $theme-gray-700; + border-left: 1px solid $theme-gray-200; + } + } + } + + .search { + form { + background-color: $white-light; + box-shadow: inset 0 0 0 1px $border-color; + + &:hover { + background-color: $white-light; + box-shadow: inset 0 0 0 1px $blue-100; + + .location-badge { + box-shadow: inset 0 0 0 1px $blue-100; + } + } + } + + .search-input-wrap { + .search-icon { + color: $theme-gray-200; + } + } + + .location-badge { + color: $theme-gray-700; + box-shadow: inset 0 0 0 1px $border-color; + background-color: $nav-badge-bg; + border-right: 0; + } + } + + .nav-sidebar li.active { + > a { + color: $theme-gray-900; + } + + svg { + fill: $theme-gray-900; + } + } + + .sidebar-top-level-items > li.active .badge { + color: $theme-gray-900; + } + } +} diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index b00a2d053e2..ab3c34df1fb 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -111,7 +111,6 @@ header { svg { height: 16px; width: 23px; - fill: currentColor; } } diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 5ffa67a1220..2f7717760ec 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -328,7 +328,7 @@ border-bottom: 1px solid $border-color; transition: padding $sidebar-transition-duration; text-align: center; - margin-top: $header-height; + margin-top: $new-navbar-height; .container-fluid { position: relative; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index e300b006026..a3da9fd44e8 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -13,6 +13,7 @@ $sidebar-breakpoint: 1024px; $darken-normal-factor: 7%; $darken-dark-factor: 10%; $darken-border-factor: 5%; +$darken-border-dashed-factor: 25%; $white-light: #fff; $white-normal: #f0f0f0; @@ -74,6 +75,8 @@ $red-700: #a62d19; $red-800: #8b2615; $red-900: #711e11; +// GitLab themes + $indigo-50: #f7f7ff; $indigo-100: #ebebfa; $indigo-200: #d1d1f0; @@ -86,6 +89,43 @@ $indigo-800: #393982; $indigo-900: #292961; $indigo-950: #1a1a40; +$theme-gray-50: #fafafa; +$theme-gray-100: #f2f2f2; +$theme-gray-200: #dfdfdf; +$theme-gray-300: #cccccc; +$theme-gray-400: #bababa; +$theme-gray-500: #a7a7a7; +$theme-gray-600: #949494; +$theme-gray-700: #707070; +$theme-gray-800: #4f4f4f; +$theme-gray-900: #2e2e2e; +$theme-gray-950: #1f1f1f; + +$theme-blue-50: #f4f8fc; +$theme-blue-100: #e6edf5; +$theme-blue-200: #c8d7e6; +$theme-blue-300: #97b3cf; +$theme-blue-400: #648cb4; +$theme-blue-500: #4a79a8; +$theme-blue-600: #3e6fa0; +$theme-blue-700: #305c88; +$theme-blue-800: #25496e; +$theme-blue-900: #1a3652; +$theme-blue-950: #0f2235; + +$theme-green-50: #f2faf6; +$theme-green-100: #e4f3ea; +$theme-green-200: #c0dfcd; +$theme-green-300: #8ac2a1; +$theme-green-400: #52a274; +$theme-green-500: #35935c; +$theme-green-600: #288a50; +$theme-green-700: #1c7441; +$theme-green-800: #145d33; +$theme-green-900: #0d4524; +$theme-green-950: #072d16; + + $black: #000; $black-transparent: rgba(0, 0, 0, 0.3); $almost-black: #242424; @@ -95,6 +135,7 @@ $border-white-normal: darken($white-normal, $darken-border-factor); $border-gray-light: darken($gray-light, $darken-border-factor); $border-gray-normal: darken($gray-normal, $darken-border-factor); +$border-gray-normal-dashed: darken($gray-normal, $darken-border-dashed-factor); $border-gray-dark: darken($white-normal, $darken-border-factor); /* diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss index 2b6c0fc015c..58e205537ef 100644 --- a/app/assets/stylesheets/new_nav.scss +++ b/app/assets/stylesheets/new_nav.scss @@ -9,10 +9,20 @@ header.navbar-gitlab-new { color: $white-light; - background: linear-gradient(to right, $indigo-900, $indigo-800); border-bottom: 0; min-height: $new-navbar-height; + .logo-text { + line-height: initial; + + svg { + width: 55px; + height: 14px; + margin: 0; + fill: $white-light; + } + } + .header-content { display: -webkit-flex; display: flex; @@ -38,10 +48,10 @@ header.navbar-gitlab-new { img { height: 28px; - margin-right: 10px; + margin-right: 8px; } - > a { + a { display: -webkit-flex; display: flex; align-items: center; @@ -54,22 +64,6 @@ header.navbar-gitlab-new { margin-right: 8px; } } - - .logo-text { - line-height: initial; - - svg { - width: 55px; - height: 14px; - margin: 0; - fill: $white-light; - } - } - - &:hover, - &:focus { - background-color: rgba($indigo-200, .2); - } } } @@ -106,7 +100,6 @@ header.navbar-gitlab-new { .navbar-collapse { padding-left: 0; - color: $indigo-200; box-shadow: 0; @media (max-width: $screen-xs-max) { @@ -132,7 +125,6 @@ header.navbar-gitlab-new { font-size: 14px; text-align: center; color: currentColor; - border-left: 1px solid lighten($indigo-700, 10%); &:hover, &:focus, @@ -167,63 +159,49 @@ header.navbar-gitlab-new { will-change: color; margin: 4px 2px; padding: 6px 8px; - color: $indigo-200; height: 32px; @media (max-width: $screen-xs-max) { padding: 0; } - svg { - fill: $indigo-200; - } - &.header-user-dropdown-toggle { margin-left: 2px; .header-user-avatar { - border-color: $indigo-200; margin-right: 0; } } - } - - .header-new-dropdown-toggle { - margin-right: 0; - } - > a:hover, - > a:focus { - text-decoration: none; - outline: 0; - opacity: 1; - color: $white-light; - - @media (min-width: $screen-sm-min) { - background-color: rgba($indigo-200, .2); - } + &:hover, + &:focus { + text-decoration: none; + outline: 0; + opacity: 1; + color: $white-light; - svg { - fill: currentColor; - } + svg { + fill: currentColor; + } - &.header-user-dropdown-toggle { - .header-user-avatar { - border-color: $white-light; + &.header-user-dropdown-toggle { + .header-user-avatar { + border-color: $white-light; + } } } } + .header-new-dropdown-toggle { + margin-right: 0; + } + .impersonated-user, .impersonated-user:hover { margin-right: 1px; background-color: $white-light; border-top-right-radius: 0; border-bottom-right-radius: 0; - - svg { - fill: $indigo-900; - } } .impersonation-btn, @@ -241,8 +219,6 @@ header.navbar-gitlab-new { &.active > a, &.dropdown.open > a { - color: $indigo-900; - background-color: $white-light; svg { fill: currentColor; @@ -256,7 +232,6 @@ header.navbar-gitlab-new { display: -webkit-flex; display: flex; margin: 0 0 0 6px; - color: $indigo-200; .dropdown-chevron { position: relative; @@ -274,17 +249,6 @@ header.navbar-gitlab-new { text-decoration: none; outline: 0; color: $white-light; - background-color: rgba($indigo-200, .2); - - svg { - fill: currentColor; - } - } - - &.active > a, - &.dropdown.open > a { - color: $indigo-900; - background-color: $white-light; svg { fill: currentColor; @@ -309,7 +273,6 @@ header.navbar-gitlab-new { } &.line-separator { - border-left: 1px solid rgba($indigo-200, .2); margin: 8px; } } @@ -339,17 +302,14 @@ header.navbar-gitlab-new { height: 32px; border: 0; border-radius: $border-radius-default; - background-color: rgba($indigo-200, .2); transition: border-color ease-in-out 0.15s, background-color ease-in-out 0.15s; &:hover { - background-color: rgba($indigo-200, .3); box-shadow: none; } } &.search-active form { - background-color: $white-light; box-shadow: none; .search-input { @@ -377,43 +337,26 @@ header.navbar-gitlab-new { } .search-input::placeholder { - color: rgba($indigo-200, .8); transition: color ease-in-out 0.15s; } .location-badge { font-size: 12px; - color: $indigo-100; - background-color: rgba($indigo-200, .1); - will-change: color; margin: -4px 4px -4px -4px; line-height: 25px; padding: 4px 8px; border-radius: 2px 0 0 2px; - border-right: 1px solid $indigo-800; height: 32px; transition: border-color ease-in-out 0.15s; } - .search-input-wrap { - .search-icon, - .clear-icon { - color: rgba($indigo-200, .8); - } - } - &.search-active { .location-badge { - color: $gl-text-color; background-color: $nav-badge-bg; border-color: $border-color; } .search-input-wrap { - .search-icon { - color: rgba($indigo-200, .8); - } - .clear-icon { color: $white-light; } @@ -488,6 +431,7 @@ header.navbar-gitlab-new { .breadcrumb-item-text { @include str-truncated(128px); + text-decoration: inherit; } .breadcrumbs-list-angle { @@ -517,8 +461,6 @@ header.navbar-gitlab-new { .btn-sign-in { margin-top: 3px; - background-color: $indigo-100; - color: $indigo-900; font-weight: $gl-font-weight-bold; &:hover { diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index 378ef8926d5..952002c83d1 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -3,8 +3,6 @@ @import "bootstrap/variables"; $active-background: rgba(0, 0, 0, .04); -$active-border: $indigo-500; -$active-color: $indigo-700; $active-hover-background: $active-background; $active-hover-color: $gl-text-color; $inactive-badge-background: rgba(0, 0, 0, .08); @@ -162,16 +160,9 @@ $new-sidebar-collapsed-width: 50px; } li.active { - box-shadow: inset 4px 0 0 $active-border; - > a { - color: $active-color; font-weight: $gl-font-weight-bold; } - - svg { - fill: $active-color; - } } @media (max-width: $screen-xs-max) { @@ -224,7 +215,6 @@ $new-sidebar-collapsed-width: 50px; &:hover, &:focus { background: $active-background; - color: $active-color; } } } @@ -324,7 +314,6 @@ $new-sidebar-collapsed-width: 50px; } .badge { - color: $active-color; font-weight: $gl-font-weight-bold; } @@ -501,13 +490,3 @@ $new-sidebar-collapsed-width: 50px; .with-performance-bar .boards-list { height: calc(100vh - #{$new-navbar-height} - #{$performance-bar-height}); } - - -// Change color of all horizontal tabs to match the new indigo color -.nav-links li.active a { - border-bottom-color: $active-border; - - .badge { - font-weight: $gl-font-weight-bold; - } -} diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 54c3c0173ae..951580ea1fe 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -634,8 +634,16 @@ padding-top: 8px; padding-bottom: 8px; } + + .diff-changed-file { + display: flex; + align-items: center; + } } .diff-file-changes-path { - @include str-truncated(78%); + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index d8a15faf7e9..d01ee4b033c 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -449,6 +449,12 @@ } } } + + .milestone-title span { + @include str-truncated(100%); + display: block; + margin: 0 4px; + } } a { diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index d4dc43035eb..cf5f933a762 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -95,6 +95,8 @@ } .omniauth-container { + font-size: 13px; + p { margin: 0; } diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss index 305feaacaa1..c197494b152 100644 --- a/app/assets/stylesheets/pages/profiles/preferences.scss +++ b/app/assets/stylesheets/pages/profiles/preferences.scss @@ -1,3 +1,67 @@ +@mixin application-theme-preview($color-1, $color-2, $color-3, $color-4) { + .one { + background-color: $color-1; + border-top-left-radius: $border-radius-default; + } + + .two { + background-color: $color-2; + border-top-right-radius: $border-radius-default; + } + + .three { + background-color: $color-3; + border-bottom-left-radius: $border-radius-default; + } + + .four { + background-color: $color-4; + border-bottom-right-radius: $border-radius-default; + } +} + +.application-theme { + label { + margin-right: 20px; + text-align: center; + } + + .preview { + font-size: 0; + margin-bottom: 10px; + + &.indigo { + @include application-theme-preview($indigo-900, $indigo-700, $indigo-800, $indigo-500); + } + + &.dark { + @include application-theme-preview($theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-600); + } + + &.light { + @include application-theme-preview($theme-gray-600, $theme-gray-200, $theme-gray-400, $theme-gray-100); + } + + &.blue { + @include application-theme-preview($theme-blue-900, $theme-blue-700, $theme-blue-800, $theme-blue-500); + } + + &.green { + @include application-theme-preview($theme-green-900, $theme-green-700, $theme-green-800, $theme-green-500); + } + } + + .preview-row { + display: block; + } + + .quadrant { + display: inline-block; + height: 50px; + width: 80px; + } +} + .syntax-theme { label { margin-right: 20px; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 94e4f4334d4..6400b72742c 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -752,7 +752,7 @@ a.deploy-project-label { } li.missing { - border: 1px dashed $border-gray-normal; + border: 1px dashed $border-gray-normal-dashed; border-radius: $border-radius-default; a { diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 69abb13add4..7dfcf7b7d9c 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -71,6 +71,11 @@ height: 100%; .monaco-editor.vs { + .current-line { + border: none; + background: $well-light-border; + } + .line-numbers { cursor: pointer; @@ -84,6 +89,13 @@ } } + .blob-no-preview { + .vertical-center { + justify-content: center; + width: 100%; + } + } + &.edit-mode { .blob-viewer-container { overflow: hidden; @@ -103,7 +115,7 @@ overflow: auto; > div, - .file-content { + .file-content:not(.wiki) { display: flex; } diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb index e5cba774dcb..a7ab481519d 100644 --- a/app/controllers/admin/deploy_keys_controller.rb +++ b/app/controllers/admin/deploy_keys_controller.rb @@ -10,9 +10,8 @@ class Admin::DeployKeysController < Admin::ApplicationController end def create - @deploy_key = deploy_keys.new(create_params.merge(user: current_user)) - - if @deploy_key.save + @deploy_key = DeployKeys::CreateService.new(current_user, create_params.merge(public: true)).execute + if @deploy_key.persisted? redirect_to admin_deploy_keys_path else render 'new' diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 9ec7719fabb..cbcef70e957 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -211,6 +211,7 @@ class Admin::UsersController < Admin::ApplicationController :provider, :remember_me, :skype, + :theme_id, :twitter, :username, :website_url diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb index 8d4ec2d6d9d..0d74078645a 100644 --- a/app/controllers/boards/issues_controller.rb +++ b/app/controllers/boards/issues_controller.rb @@ -11,9 +11,15 @@ module Boards issues = Boards::Issues::ListService.new(board_parent, current_user, filter_params).execute issues = issues.page(params[:page]).per(params[:per] || 20) make_sure_position_is_set(issues) + issues = issues.preload(:project, + :milestone, + :assignees, + labels: [:priorities], + notes: [:award_emoji, :author] + ) render json: { - issues: serialize_as_json(issues.preload(:project)), + issues: serialize_as_json(issues), size: issues.total_count } end @@ -76,14 +82,13 @@ module Boards def serialize_as_json(resource) resource.as_json( - labels: true, only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position], + labels: true, include: { project: { only: [:id, :path] }, assignees: { only: [:id, :name, :username], methods: [:avatar_url] }, milestone: { only: [:id, :title] } - }, - user: current_user + } ) end end diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb index 3eb485de9db..be667687c18 100644 --- a/app/controllers/ci/lints_controller.rb +++ b/app/controllers/ci/lints_controller.rb @@ -7,11 +7,11 @@ module Ci def create @content = params[:content] - @error = Ci::GitlabCiYamlProcessor.validation_message(@content) + @error = Gitlab::Ci::YamlProcessor.validation_message(@content) @status = @error.blank? if @error.blank? - @config_processor = Ci::GitlabCiYamlProcessor.new(@content) + @config_processor = Gitlab::Ci::YamlProcessor.new(@content) @stages = @config_processor.stages @builds = @config_processor.builds @jobs = @config_processor.jobs diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb index f71ab702e71..cd94a36a6e7 100644 --- a/app/controllers/dashboard/projects_controller.rb +++ b/app/controllers/dashboard/projects_controller.rb @@ -48,7 +48,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ProjectsFinder .new(params: finder_params, current_user: current_user) .execute - .includes(:route, :creator, namespace: :route) + .includes(:route, :creator, namespace: [:route, :owner]) end def load_events diff --git a/app/controllers/profiles/gpg_keys_controller.rb b/app/controllers/profiles/gpg_keys_controller.rb index 6779cc6ddac..689c76059f6 100644 --- a/app/controllers/profiles/gpg_keys_controller.rb +++ b/app/controllers/profiles/gpg_keys_controller.rb @@ -7,9 +7,9 @@ class Profiles::GpgKeysController < Profiles::ApplicationController end def create - @gpg_key = current_user.gpg_keys.new(gpg_key_params) + @gpg_key = GpgKeys::CreateService.new(current_user, gpg_key_params).execute - if @gpg_key.save + if @gpg_key.persisted? redirect_to profile_gpg_keys_path else @gpg_keys = current_user.gpg_keys.select(&:persisted?) diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index f9f0e8eef83..89d6d7f1b52 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -11,9 +11,9 @@ class Profiles::KeysController < Profiles::ApplicationController end def create - @key = current_user.keys.new(key_params) + @key = Keys::CreateService.new(current_user, key_params).execute - if @key.save + if @key.persisted? redirect_to profile_key_path(@key) else @keys = current_user.keys.select(&:persisted?) diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb index 1e557c47638..cce2a847b53 100644 --- a/app/controllers/profiles/preferences_controller.rb +++ b/app/controllers/profiles/preferences_controller.rb @@ -35,7 +35,8 @@ class Profiles::PreferencesController < Profiles::ApplicationController :color_scheme_id, :layout, :dashboard, - :project_view + :project_view, + :theme_id ) end end diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 193549663ac..3c8eaa24080 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -27,7 +27,7 @@ class Projects::CompareController < Projects::ApplicationController def create if params[:from].blank? || params[:to].blank? - flash[:alert] = "You must select from and to branches" + flash[:alert] = "You must select a Source and a Target revision" from_to_vars = { from: params[:from].presence, to: params[:to].presence diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index c2e621fa190..cf8829ba95b 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -22,7 +22,7 @@ class Projects::DeployKeysController < Projects::ApplicationController end def create - @key = DeployKey.new(create_params.merge(user: current_user)) + @key = DeployKeys::CreateService.new(current_user, create_params).execute unless @key.valid? && @project.deploy_keys << @key flash[:alert] = @key.errors.full_messages.join(', ').html_safe diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index a3bfbf0694e..7ad7b3003af 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -132,10 +132,10 @@ class Projects::PipelinesController < Projects::ApplicationController def charts @charts = {} - @charts[:week] = Ci::Charts::WeekChart.new(project) - @charts[:month] = Ci::Charts::MonthChart.new(project) - @charts[:year] = Ci::Charts::YearChart.new(project) - @charts[:pipeline_times] = Ci::Charts::PipelineTime.new(project) + @charts[:week] = Gitlab::Ci::Charts::WeekChart.new(project) + @charts[:month] = Gitlab::Ci::Charts::MonthChart.new(project) + @charts[:year] = Gitlab::Ci::Charts::YearChart.new(project) + @charts[:pipeline_times] = Gitlab::Ci::Charts::PipelineTime.new(project) @counts = {} @counts[:total] = @project.pipelines.count(:all) diff --git a/app/helpers/auto_devops_helper.rb b/app/helpers/auto_devops_helper.rb index 4ff38f86b5f..c455d18cff8 100644 --- a/app/helpers/auto_devops_helper.rb +++ b/app/helpers/auto_devops_helper.rb @@ -1,7 +1,10 @@ module AutoDevopsHelper def show_auto_devops_callout?(project) - show_callout?('auto_devops_settings_dismissed') && + Feature.get(:auto_devops_banner_disabled).off? && + show_callout?('auto_devops_settings_dismissed') && can?(current_user, :admin_pipeline, project) && - project.has_auto_devops_implicitly_disabled? + project.has_auto_devops_implicitly_disabled? && + !project.repository.gitlab_ci_yml && + project.ci_services.active.none? end end diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb index 4bd61aa8f86..62ac208f16a 100644 --- a/app/helpers/boards_helper.rb +++ b/app/helpers/boards_helper.rb @@ -77,4 +77,8 @@ module BoardsHelper 'max-select': dropdown_options[:data][:'max-select'] } end + + def boards_link_text + _("Board") + end end diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb index 85bc784d53c..aa3a9a055a0 100644 --- a/app/helpers/builds_helper.rb +++ b/app/helpers/builds_helper.rb @@ -30,7 +30,7 @@ module BuildsHelper def build_failed_issue_options { - title: "Build Failed ##{@build.id}", + title: "Job Failed ##{@build.id}", description: project_job_url(@project, @build) } end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 36b79da1bde..e8efe8fab27 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -21,7 +21,7 @@ module GroupsHelper group.ancestors.reverse.each_with_index do |parent, index| if index > 0 - add_to_breadcrumb_dropdown(group_title_link(parent, hidable: false, show_avatar: true), location: :before) + add_to_breadcrumb_dropdown(group_title_link(parent, hidable: false, show_avatar: true, for_dropdown: true), location: :before) else full_title += breadcrumb_list_item group_title_link(parent, hidable: false) end @@ -85,8 +85,8 @@ module GroupsHelper private - def group_title_link(group, hidable: false, show_avatar: false) - link_to(group_path(group), class: "group-path breadcrumb-item-text js-breadcrumb-item-text #{'hidable' if hidable}") do + def group_title_link(group, hidable: false, show_avatar: false, for_dropdown: false) + link_to(group_path(group), class: "group-path #{'breadcrumb-item-text' unless for_dropdown} js-breadcrumb-item-text #{'hidable' if hidable}") do output = if (group.try(:avatar_url) || show_avatar) && !Rails.env.test? image_tag(group_icon(group), class: "avatar-tile", width: 15, height: 15) diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb index d36bb4ab074..0d7347ed30d 100644 --- a/app/helpers/preferences_helper.rb +++ b/app/helpers/preferences_helper.rb @@ -40,6 +40,10 @@ module PreferencesHelper ] end + def user_application_theme + @user_application_theme ||= Gitlab::Themes.for_user(current_user).css_class + end + def user_color_scheme Gitlab::ColorSchemes.for_user(current_user).css_class end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 0c8cb9ba235..ddeff490d3a 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -320,7 +320,7 @@ module ProjectsHelper def git_user_name if current_user - current_user.name + current_user.name.gsub('"', '\"') else _("Your name") end diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index 3308ab0c259..ee701076a14 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -119,8 +119,4 @@ module TabHelper 'active' if current_controller?('oauth/applications') end - - def sidebar_link(href, title: nil, css: nil, &block) - link_to capture(&block), href, title: (title if collapsed_sidebar?), class: css, aria: { label: title } - end end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index aede9b5f9da..c0cc60d5ebf 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -137,11 +137,11 @@ class ApplicationSetting < ActiveRecord::Base validates :housekeeping_full_repack_period, presence: true, - numericality: { only_integer: true, greater_than: :housekeeping_incremental_repack_period } + numericality: { only_integer: true, greater_than_or_equal_to: :housekeeping_incremental_repack_period } validates :housekeeping_gc_period, presence: true, - numericality: { only_integer: true, greater_than: :housekeeping_full_repack_period } + numericality: { only_integer: true, greater_than_or_equal_to: :housekeeping_full_repack_period } validates :terminal_max_session_time, presence: true, diff --git a/app/models/blob_viewer/gitlab_ci_yml.rb b/app/models/blob_viewer/gitlab_ci_yml.rb index 7267c3965d3..53bc247dec1 100644 --- a/app/models/blob_viewer/gitlab_ci_yml.rb +++ b/app/models/blob_viewer/gitlab_ci_yml.rb @@ -13,7 +13,7 @@ module BlobViewer prepare! - @validation_message = Ci::GitlabCiYamlProcessor.validation_message(blob.data) + @validation_message = Gitlab::Ci::YamlProcessor.validation_message(blob.data) end def valid? diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 5ebe6f180e6..ee544d8ac56 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -446,8 +446,8 @@ module Ci return unless trace trace = trace.dup - Ci::MaskSecret.mask!(trace, project.runners_token) if project - Ci::MaskSecret.mask!(trace, token) + Gitlab::Ci::MaskSecret.mask!(trace, project.runners_token) if project + Gitlab::Ci::MaskSecret.mask!(trace, token) trace end diff --git a/app/models/ci/group_variable.rb b/app/models/ci/group_variable.rb index f64bc245a67..afeae69ba39 100644 --- a/app/models/ci/group_variable.rb +++ b/app/models/ci/group_variable.rb @@ -1,6 +1,6 @@ module Ci class GroupVariable < ActiveRecord::Base - extend Ci::Model + extend Gitlab::Ci::Model include HasVariable include Presentable diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 871c76fbad3..8d017b9b3b1 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -1,6 +1,6 @@ module Ci class Pipeline < ActiveRecord::Base - extend Ci::Model + extend Gitlab::Ci::Model include HasStatus include Importable include AfterCommitQueue @@ -336,8 +336,8 @@ module Ci return @config_processor if defined?(@config_processor) @config_processor ||= begin - Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.full_path) - rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e + Gitlab::Ci::YamlProcessor.new(ci_yaml_file, project.full_path) + rescue Gitlab::Ci::YamlProcessor::ValidationError, Psych::SyntaxError => e self.yaml_errors = e.message nil rescue @@ -453,6 +453,10 @@ module Ci .fabricate! end + def latest_builds_with_artifacts + @latest_builds_with_artifacts ||= builds.latest.with_artifacts + end + private def ci_yaml_from_repo diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb index e7e02587759..10ead6b6d3b 100644 --- a/app/models/ci/pipeline_schedule.rb +++ b/app/models/ci/pipeline_schedule.rb @@ -1,6 +1,6 @@ module Ci class PipelineSchedule < ActiveRecord::Base - extend Ci::Model + extend Gitlab::Ci::Model include Importable acts_as_paranoid diff --git a/app/models/ci/pipeline_schedule_variable.rb b/app/models/ci/pipeline_schedule_variable.rb index ee5b8733fac..af989fb14b4 100644 --- a/app/models/ci/pipeline_schedule_variable.rb +++ b/app/models/ci/pipeline_schedule_variable.rb @@ -1,6 +1,6 @@ module Ci class PipelineScheduleVariable < ActiveRecord::Base - extend Ci::Model + extend Gitlab::Ci::Model include HasVariable belongs_to :pipeline_schedule diff --git a/app/models/ci/pipeline_variable.rb b/app/models/ci/pipeline_variable.rb index 00b419c3efa..de5aae17a15 100644 --- a/app/models/ci/pipeline_variable.rb +++ b/app/models/ci/pipeline_variable.rb @@ -1,6 +1,6 @@ module Ci class PipelineVariable < ActiveRecord::Base - extend Ci::Model + extend Gitlab::Ci::Model include HasVariable belongs_to :pipeline diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index b1798084787..a0d07902ba2 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -1,6 +1,6 @@ module Ci class Runner < ActiveRecord::Base - extend Ci::Model + extend Gitlab::Ci::Model RUNNER_QUEUE_EXPIRY_TIME = 60.minutes ONLINE_CONTACT_TIMEOUT = 1.hour diff --git a/app/models/ci/runner_project.rb b/app/models/ci/runner_project.rb index 5f01a0daae9..505d178ba8e 100644 --- a/app/models/ci/runner_project.rb +++ b/app/models/ci/runner_project.rb @@ -1,6 +1,6 @@ module Ci class RunnerProject < ActiveRecord::Base - extend Ci::Model + extend Gitlab::Ci::Model belongs_to :runner belongs_to :project diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index 754c37518b3..75b8ea2a371 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -1,6 +1,6 @@ module Ci class Stage < ActiveRecord::Base - extend Ci::Model + extend Gitlab::Ci::Model include Importable include HasStatus include Gitlab::OptimisticLocking diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb index 6df41a3f301..b5290bcaf53 100644 --- a/app/models/ci/trigger.rb +++ b/app/models/ci/trigger.rb @@ -1,6 +1,6 @@ module Ci class Trigger < ActiveRecord::Base - extend Ci::Model + extend Gitlab::Ci::Model acts_as_paranoid diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb index 2c860598281..215b1cf6753 100644 --- a/app/models/ci/trigger_request.rb +++ b/app/models/ci/trigger_request.rb @@ -1,6 +1,6 @@ module Ci class TriggerRequest < ActiveRecord::Base - extend Ci::Model + extend Gitlab::Ci::Model belongs_to :trigger belongs_to :pipeline, foreign_key: :commit_id diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index cf0fe04ddaf..67d3ec81b6f 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -1,6 +1,6 @@ module Ci class Variable < ActiveRecord::Base - extend Ci::Model + extend Gitlab::Ci::Model include HasVariable include Presentable diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb index 51768dd96bc..eae5eee4fee 100644 --- a/app/models/deploy_key.rb +++ b/app/models/deploy_key.rb @@ -28,10 +28,4 @@ class DeployKey < Key def can_push_to?(project) can_push? && has_access_to?(project) end - - private - - # we don't want to notify the user for deploy keys - def notify_user - end end diff --git a/app/models/environment.rb b/app/models/environment.rb index 9b05f8b1cd5..44e39e21442 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -6,7 +6,10 @@ class Environment < ActiveRecord::Base belongs_to :project, required: true, validate: true - has_many :deployments, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :deployments, + -> (env) { where(project_id: env.project_id) }, + dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_one :last_deployment, -> { order('deployments.id DESC') }, class_name: 'Deployment' before_validation :nullify_external_url diff --git a/app/models/event.rb b/app/models/event.rb index 8e9490b66f4..0b1f053a7e6 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -241,13 +241,7 @@ class Event < ActiveRecord::Base def action_name if push? - if new_ref? - "pushed new" - elsif rm_ref? - "deleted" - else - "pushed to" - end + push_action_name elsif closed? "closed" elsif merged? @@ -263,11 +257,7 @@ class Event < ActiveRecord::Base elsif commented? "commented on" elsif created_project? - if project.external_import? - "imported" - else - "created" - end + created_project_action_name else "opened" end @@ -360,6 +350,24 @@ class Event < ActiveRecord::Base private + def push_action_name + if new_ref? + "pushed new" + elsif rm_ref? + "deleted" + else + "pushed to" + end + end + + def created_project_action_name + if project.external_import? + "imported" + else + "created" + end + end + def recent_update? project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago end diff --git a/app/models/gpg_key.rb b/app/models/gpg_key.rb index 1633acd4fa9..44deae4234b 100644 --- a/app/models/gpg_key.rb +++ b/app/models/gpg_key.rb @@ -36,7 +36,6 @@ class GpgKey < ActiveRecord::Base before_validation :extract_fingerprint, :extract_primary_keyid after_commit :update_invalid_gpg_signatures, on: :create - after_commit :notify_user, on: :create def primary_keyid super&.upcase @@ -107,8 +106,4 @@ class GpgKey < ActiveRecord::Base # only allows one key self.primary_keyid = Gitlab::Gpg.primary_keyids_from_key(key).first end - - def notify_user - NotificationService.new.new_gpg_key(self) - end end diff --git a/app/models/issue.rb b/app/models/issue.rb index 8c7d492e605..cd5056aae5e 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -30,9 +30,6 @@ class Issue < ActiveRecord::Base has_many :issue_assignees has_many :assignees, class_name: "User", through: :issue_assignees - has_many :issue_assignees - has_many :assignees, class_name: "User", through: :issue_assignees - validates :project, presence: true scope :in_projects, ->(project_ids) { where(project_id: project_ids) } diff --git a/app/models/key.rb b/app/models/key.rb index a6b4dcfec0d..4fa6cac2fd0 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -28,7 +28,6 @@ class Key < ActiveRecord::Base delegate :name, :email, to: :user, prefix: true after_commit :add_to_shell, on: :create - after_commit :notify_user, on: :create after_create :post_create_hook after_commit :remove_from_shell, on: :destroy after_destroy :post_destroy_hook @@ -118,8 +117,4 @@ class Key < ActiveRecord::Base "type is forbidden. Must be #{allowed_types}" end - - def notify_user - NotificationService.new.new_key(self) - end end diff --git a/app/models/label.rb b/app/models/label.rb index 958141a7358..899028a01a0 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -127,7 +127,12 @@ class Label < ActiveRecord::Base end def priority(project) - priorities.find_by(project: project).try(:priority) + priority = if priorities.loaded? + priorities.first { |p| p.project == project } + else + priorities.find_by(project: project) + end + priority.try(:priority) end def template? diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 4a9a23fea1f..e279d8dd8c5 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -231,6 +231,13 @@ class Namespace < ActiveRecord::Base end def force_share_with_group_lock_on_descendants - descendants.update_all(share_with_group_lock: true) + return unless Group.supports_nested_groups? + + # We can't use `descendants.update_all` since Rails will throw away the WITH + # RECURSIVE statement. We also can't use WHERE EXISTS since we can't use + # different table aliases, hence we're just using WHERE IN. Since we have a + # maximum of 20 nested groups this should be fine. + Namespace.where(id: descendants.select(:id)) + .update_all(share_with_group_lock: true) end end diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb index 654be927ed8..ec0ebe4d353 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 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 c422819a082..94ae0acbe1a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1163,6 +1163,23 @@ class Project < ActiveRecord::Base pipelines.order(id: :desc).find_by(sha: sha, ref: ref) end + def latest_successful_pipeline_for_default_branch + if defined?(@latest_successful_pipeline_for_default_branch) + return @latest_successful_pipeline_for_default_branch + end + + @latest_successful_pipeline_for_default_branch = + pipelines.latest_successful_for(default_branch) + end + + def latest_successful_pipeline_for(ref = nil) + if ref && ref != default_branch + pipelines.latest_successful_for(ref) + else + latest_successful_pipeline_for_default_branch + end + end + def enable_ci project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED) end diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb index 53731579e87..7af3b6870e2 100644 --- a/app/models/project_auto_devops.rb +++ b/app/models/project_auto_devops.rb @@ -1,6 +1,9 @@ class ProjectAutoDevops < ActiveRecord::Base belongs_to :project + scope :enabled, -> { where(enabled: true) } + scope :disabled, -> { where(enabled: false) } + validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true } def variables diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb index 9d37184be2c..6a3118a11b8 100644 --- a/app/models/project_services/pipelines_email_service.rb +++ b/app/models/project_services/pipelines_email_service.rb @@ -80,6 +80,6 @@ class PipelinesEmailService < Service end def retrieve_recipients(data) - recipients.to_s.split(',').reject(&:blank?) + recipients.to_s.split(/[,(?:\r?\n) ]+/).reject(&:empty?) end end diff --git a/app/models/repository.rb b/app/models/repository.rb index 035f85a0b46..f2b54705e7b 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -90,6 +90,12 @@ class Repository ) end + # we need to have this method here because it is not cached in ::Git and + # the method is called multiple times for every request + def has_visible_content? + branch_count > 0 + end + def inspect "#<#{self.class.name}:#{@disk_path}>" end @@ -166,7 +172,7 @@ class Repository end def add_branch(user, branch_name, ref) - branch = raw_repository.add_branch(branch_name, committer: user, target: ref) + branch = raw_repository.add_branch(branch_name, user: user, target: ref) after_create_branch @@ -176,7 +182,7 @@ class Repository end def add_tag(user, tag_name, target, message = nil) - raw_repository.add_tag(tag_name, committer: user, target: target, message: message) + raw_repository.add_tag(tag_name, user: user, target: target, message: message) rescue Gitlab::Git::Repository::InvalidRef false end @@ -184,7 +190,7 @@ class Repository def rm_branch(user, branch_name) before_remove_branch - raw_repository.rm_branch(branch_name, committer: user) + raw_repository.rm_branch(branch_name, user: user) after_remove_branch true @@ -193,7 +199,7 @@ class Repository def rm_tag(user, tag_name) before_remove_tag - raw_repository.rm_tag(tag_name, committer: user) + raw_repository.rm_tag(tag_name, user: user) after_remove_tag true diff --git a/app/models/user.rb b/app/models/user.rb index 358b04ac71f..09c9b3250eb 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -35,6 +35,7 @@ class User < ActiveRecord::Base default_value_for :project_view, :files default_value_for :notified_of_own_activity, false default_value_for :preferred_language, I18n.default_locale + default_value_for :theme_id, gitlab_config.default_theme attr_encrypted :otp_secret, key: Gitlab::Application.secrets.otp_key_base, diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb index 743a08acefe..8c89eea607f 100644 --- a/app/serializers/build_details_entity.rb +++ b/app/serializers/build_details_entity.rb @@ -32,8 +32,8 @@ class BuildDetailsEntity < JobEntity private def build_failed_issue_options - { title: "Build Failed ##{build.id}", - description: project_job_path(project, build) } + { title: "Job Failed ##{build.id}", + description: "Job [##{build.id}](#{project_job_path(project, build)}) failed for #{build.sha}:\n" } end def current_user diff --git a/app/services/ci/pipeline_trigger_service.rb b/app/services/ci/pipeline_trigger_service.rb index 1e5ad28ba57..120af8c1e61 100644 --- a/app/services/ci/pipeline_trigger_service.rb +++ b/app/services/ci/pipeline_trigger_service.rb @@ -14,7 +14,7 @@ module Ci pipeline = Ci::CreatePipelineService.new(project, trigger.owner, ref: params[:ref]) .execute(:trigger, ignore_skip_ci: true) do |pipeline| - trigger.trigger_requests.create!(pipeline: pipeline) + pipeline.trigger_requests.create!(trigger: trigger) create_pipeline_variables!(pipeline) end diff --git a/app/services/deploy_keys/create_service.rb b/app/services/deploy_keys/create_service.rb new file mode 100644 index 00000000000..16de3d08df2 --- /dev/null +++ b/app/services/deploy_keys/create_service.rb @@ -0,0 +1,7 @@ +module DeployKeys + class CreateService < Keys::BaseService + def execute + DeployKey.create(params.merge(user: user)) + end + end +end diff --git a/app/services/gpg_keys/create_service.rb b/app/services/gpg_keys/create_service.rb new file mode 100644 index 00000000000..e822a89c4d3 --- /dev/null +++ b/app/services/gpg_keys/create_service.rb @@ -0,0 +1,9 @@ +module GpgKeys + class CreateService < Keys::BaseService + def execute + key = user.gpg_keys.create(params) + notification_service.new_gpg_key(key) if key.persisted? + key + end + end +end diff --git a/app/services/keys/base_service.rb b/app/services/keys/base_service.rb new file mode 100644 index 00000000000..545832d0bd4 --- /dev/null +++ b/app/services/keys/base_service.rb @@ -0,0 +1,13 @@ +module Keys + class BaseService + attr_accessor :user, :params + + def initialize(user, params) + @user, @params = user, params + end + + def notification_service + NotificationService.new + end + end +end diff --git a/app/services/keys/create_service.rb b/app/services/keys/create_service.rb new file mode 100644 index 00000000000..e2e5a6c46c5 --- /dev/null +++ b/app/services/keys/create_service.rb @@ -0,0 +1,9 @@ +module Keys + class CreateService < ::Keys::BaseService + def execute + key = user.keys.create(params) + notification_service.new_key(key) if key.persisted? + key + end + end +end diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 069f8f89e0b..703f4165128 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -111,6 +111,11 @@ GitLab API %span.pull-right = API::API::version + - if Gitlab.config.pages.enabled + %p + GitLab Pages + %span.pull-right + = Gitlab::Pages::VERSION %p Git %span.pull-right diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml index bfd7dd25a7d..546cec4d565 100644 --- a/app/views/devise/shared/_omniauth_box.html.haml +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -7,6 +7,8 @@ %span.light - has_icon = provider_has_icon?(provider) = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: 'oauth-login' + (has_icon ? ' oauth-image-link' : ' btn'), id: "oauth-login-#{provider}" - %fieldset.prepend-top-10 - = check_box_tag :remember_me - = label_tag :remember_me, 'Remember me' + %fieldset.prepend-top-10.checkbox.remember-me + %label + = check_box_tag :remember_me, nil, false, class: 'remember-me-checkbox' + %span + Remember me diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 65ac8aaa59b..0ca34b276a7 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: I18n.locale, class: page_class } = render "layouts/head" - %body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}", find_file: find_file_path } } + %body{ class: "#{user_application_theme} #{@body_class}", data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}", find_file: find_file_path } } = render "layouts/init_auto_complete" if @gfm_form = render 'peek/bar' = render "layouts/header/default" diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml index fcebb385a65..615238b94ad 100644 --- a/app/views/layouts/nav/sidebar/_admin.html.haml +++ b/app/views/layouts/nav/sidebar/_admin.html.haml @@ -7,7 +7,7 @@ .sidebar-context-title Admin Area %ul.sidebar-top-level-items = nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts conversational_development_index), html_options: {class: 'home'}) do - = sidebar_link admin_root_path, title: _('Overview'), css: 'shortcuts-tree' do + = link_to admin_root_path, class: 'shortcuts-tree' do .nav-icon-container = custom_icon('overview') %span.nav-item-name @@ -53,7 +53,7 @@ ConvDev Index = nav_link(controller: %w(system_info background_jobs logs health_check requests_profiles)) do - = sidebar_link admin_system_info_path, title: _('Monitoring') do + = link_to admin_system_info_path do .nav-icon-container = custom_icon('monitoring') %span.nav-item-name @@ -87,7 +87,7 @@ Requests Profiles = nav_link(controller: :broadcast_messages) do - = sidebar_link admin_broadcast_messages_path, title: _('Messages') do + = link_to admin_broadcast_messages_path do .nav-icon-container = custom_icon('messages') %span.nav-item-name @@ -99,7 +99,7 @@ #{ _('Messages') } = nav_link(controller: [:hooks, :hook_logs]) do - = sidebar_link admin_hooks_path, title: _('Hooks') do + = link_to admin_hooks_path do .nav-icon-container = custom_icon('system_hooks') %span.nav-item-name @@ -111,7 +111,7 @@ #{ _('System Hooks') } = nav_link(controller: :applications) do - = sidebar_link admin_applications_path, title: _('Applications') do + = link_to admin_applications_path do .nav-icon-container = custom_icon('applications') %span.nav-item-name @@ -123,7 +123,7 @@ #{ _('Applications') } = nav_link(controller: :abuse_reports) do - = sidebar_link admin_abuse_reports_path, title: _("Abuse Reports") do + = link_to admin_abuse_reports_path do .nav-icon-container = custom_icon('abuse_reports') %span.nav-item-name @@ -138,7 +138,7 @@ - if akismet_enabled? = nav_link(controller: :spam_logs) do - = sidebar_link admin_spam_logs_path, title: _("Spam Logs") do + = link_to admin_spam_logs_path do .nav-icon-container = custom_icon('spam_logs') %span.nav-item-name @@ -150,7 +150,7 @@ #{ _('Spam Logs') } = nav_link(controller: :deploy_keys) do - = sidebar_link admin_deploy_keys_path, title: _('Deploy Keys') do + = link_to admin_deploy_keys_path do .nav-icon-container = custom_icon('key') %span.nav-item-name @@ -162,7 +162,7 @@ #{ _('Deploy Keys') } = nav_link(controller: :services) do - = sidebar_link admin_application_settings_services_path, title: _('Service Templates') do + = link_to admin_application_settings_services_path do .nav-icon-container = custom_icon('service_templates') %span.nav-item-name @@ -174,7 +174,7 @@ #{ _('Service Templates') } = nav_link(controller: :labels) do - = sidebar_link admin_labels_path, title: _('Labels') do + = link_to admin_labels_path do .nav-icon-container = custom_icon('labels') %span.nav-item-name @@ -186,7 +186,7 @@ #{ _('Labels') } = nav_link(controller: :appearances) do - = sidebar_link admin_appearances_path, title: _('Appearances') do + = link_to admin_appearances_path do .nav-icon-container = custom_icon('appearance') %span.nav-item-name @@ -198,7 +198,7 @@ #{ _('Appearance') } = nav_link(controller: :application_settings) do - = sidebar_link admin_application_settings_path, title: _('Settings') do + = link_to admin_application_settings_path do .nav-icon-container = custom_icon('settings') %span.nav-item-name diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index e01dfa7c854..910e0d8b5ec 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -11,7 +11,7 @@ = @group.name %ul.sidebar-top-level-items = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do - = sidebar_link group_path(@group), title: _('Group overview') do + = link_to group_path(@group) do .nav-icon-container = custom_icon('project') %span.nav-item-name @@ -34,7 +34,7 @@ Activity = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do - = sidebar_link issues_group_path(@group), title: _('Issues') do + = link_to issues_group_path(@group) do .nav-icon-container = custom_icon('issues') %span.nav-item-name @@ -64,7 +64,7 @@ Milestones = nav_link(path: 'groups#merge_requests') do - = sidebar_link merge_requests_group_path(@group), title: _('Merge Requests') do + = link_to merge_requests_group_path(@group) do .nav-icon-container = custom_icon('mr_bold') %span.nav-item-name @@ -77,7 +77,7 @@ #{ _('Merge Requests') } %span.badge.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(merge_requests.count) = nav_link(path: 'group_members#index') do - = sidebar_link group_group_members_path(@group), title: _('Members') do + = link_to group_group_members_path(@group) do .nav-icon-container = custom_icon('members') %span.nav-item-name @@ -89,7 +89,7 @@ #{ _('Members') } - if current_user && can?(current_user, :admin_group, @group) = nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do - = sidebar_link edit_group_path(@group), title: _('Settings') do + = link_to edit_group_path(@group) do .nav-icon-container = custom_icon('settings') %span.nav-item-name diff --git a/app/views/layouts/nav/sidebar/_profile.html.haml b/app/views/layouts/nav/sidebar/_profile.html.haml index 4c26d107ea7..2c402591f62 100644 --- a/app/views/layouts/nav/sidebar/_profile.html.haml +++ b/app/views/layouts/nav/sidebar/_profile.html.haml @@ -7,7 +7,7 @@ .sidebar-context-title User Settings %ul.sidebar-top-level-items = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do - = sidebar_link profile_path, title: _('Profile Settings') do + = link_to profile_path do .nav-icon-container = custom_icon('profile') %span.nav-item-name @@ -18,7 +18,7 @@ %strong.fly-out-top-item-name #{ _('Profile') } = nav_link(controller: [:accounts, :two_factor_auths]) do - = sidebar_link profile_account_path, title: _('Account') do + = link_to profile_account_path do .nav-icon-container = custom_icon('account') %span.nav-item-name @@ -30,7 +30,7 @@ #{ _('Account') } - if current_application_settings.user_oauth_applications? = nav_link(controller: 'oauth/applications') do - = sidebar_link applications_profile_path, title: _('Applications') do + = link_to applications_profile_path do .nav-icon-container = custom_icon('applications') %span.nav-item-name @@ -41,7 +41,7 @@ %strong.fly-out-top-item-name #{ _('Applications') } = nav_link(controller: :chat_names) do - = sidebar_link profile_chat_names_path, title: _('Chat') do + = link_to profile_chat_names_path do .nav-icon-container = custom_icon('chat') %span.nav-item-name @@ -52,7 +52,7 @@ %strong.fly-out-top-item-name #{ _('Chat') } = nav_link(controller: :personal_access_tokens) do - = sidebar_link profile_personal_access_tokens_path, title: _('Access Tokens') do + = link_to profile_personal_access_tokens_path do .nav-icon-container = custom_icon('access_tokens') %span.nav-item-name @@ -63,7 +63,7 @@ %strong.fly-out-top-item-name #{ _('Access Tokens') } = nav_link(controller: :emails) do - = sidebar_link profile_emails_path, title: _('Emails') do + = link_to profile_emails_path do .nav-icon-container = custom_icon('emails') %span.nav-item-name @@ -75,7 +75,7 @@ #{ _('Emails') } - unless current_user.ldap_user? = nav_link(controller: :passwords) do - = sidebar_link edit_profile_password_path, title: _('Password') do + = link_to edit_profile_password_path do .nav-icon-container = custom_icon('lock') %span.nav-item-name @@ -86,7 +86,7 @@ %strong.fly-out-top-item-name #{ _('Password') } = nav_link(controller: :notifications) do - = sidebar_link profile_notifications_path, title: _('Notifications') do + = link_to profile_notifications_path do .nav-icon-container = custom_icon('notifications') %span.nav-item-name @@ -97,7 +97,7 @@ %strong.fly-out-top-item-name #{ _('Notifications') } = nav_link(controller: :keys) do - = sidebar_link profile_keys_path, title: _('SSH Keys') do + = link_to profile_keys_path do .nav-icon-container = custom_icon('key') %span.nav-item-name @@ -108,7 +108,7 @@ %strong.fly-out-top-item-name #{ _('SSH Keys') } = nav_link(controller: :gpg_keys) do - = sidebar_link profile_gpg_keys_path, title: _('GPG Keys') do + = link_to profile_gpg_keys_path do .nav-icon-container = custom_icon('key_2') %span.nav-item-name @@ -119,7 +119,7 @@ %strong.fly-out-top-item-name #{ _('GPG Keys') } = nav_link(controller: :preferences) do - = sidebar_link profile_preferences_path, title: _('Preferences') do + = link_to profile_preferences_path do .nav-icon-container = custom_icon('preferences') %span.nav-item-name @@ -130,7 +130,7 @@ %strong.fly-out-top-item-name #{ _('Preferences') } = nav_link(path: 'profiles#audit_log') do - = sidebar_link audit_log_profile_path, title: _('Authentication log') do + = link_to audit_log_profile_path do .nav-icon-container = custom_icon('authentication_log') %span.nav-item-name diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 9589e81c750..29f1fc6b354 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -9,7 +9,7 @@ = @project.name %ul.sidebar-top-level-items = nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do - = sidebar_link project_path(@project), title: _('Project overview'), css: 'shortcuts-project' do + = link_to project_path(@project), class: 'shortcuts-project' do .nav-icon-container = custom_icon('project') %span.nav-item-name @@ -36,7 +36,7 @@ - if project_nav_tab? :files = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do - = sidebar_link project_tree_path(@project), title: _('Repository'), css: 'shortcuts-tree' do + = link_to project_tree_path(@project), class: 'shortcuts-tree' do .nav-icon-container = custom_icon('doc_text') %span.nav-item-name @@ -82,7 +82,7 @@ - if project_nav_tab? :container_registry = nav_link(controller: %w[projects/registry/repositories]) do - = sidebar_link project_container_registry_index_path(@project), title: _('Container Registry'), css: 'shortcuts-container-registry' do + = link_to project_container_registry_index_path(@project), class: 'shortcuts-container-registry' do .nav-icon-container = custom_icon('container_registry') %span.nav-item-name @@ -90,7 +90,7 @@ - if project_nav_tab? :issues = nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do - = sidebar_link project_issues_path(@project), title: _('Issues'), css: 'shortcuts-issues' do + = link_to project_issues_path(@project), class: 'shortcuts-issues' do .nav-icon-container = custom_icon('issues') %span.nav-item-name @@ -114,9 +114,9 @@ List = nav_link(controller: :boards) do - = link_to project_boards_path(@project), title: 'Board' do + = link_to project_boards_path(@project), title: boards_link_text do %span - Board + = 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' @@ -144,7 +144,7 @@ - if project_nav_tab? :merge_requests = nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do - = sidebar_link project_merge_requests_path(@project), title: _('Merge Requests'), css: 'shortcuts-merge_requests' do + = link_to project_merge_requests_path(@project), class: 'shortcuts-merge_requests' do .nav-icon-container = custom_icon('mr_bold') %span.nav-item-name @@ -161,7 +161,7 @@ - if project_nav_tab? :pipelines = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts]) do - = sidebar_link project_pipelines_path(@project), title: _('CI / CD'), css: 'shortcuts-pipelines' do + = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines' do .nav-icon-container = custom_icon('pipeline') %span.nav-item-name @@ -205,7 +205,7 @@ - if project_nav_tab? :wiki = nav_link(controller: :wikis) do - = sidebar_link get_project_wiki_path(@project), title: _('Wiki'), css: 'shortcuts-wiki' do + = link_to get_project_wiki_path(@project), class: 'shortcuts-wiki' do .nav-icon-container = custom_icon('wiki') %span.nav-item-name @@ -218,7 +218,7 @@ - if project_nav_tab? :snippets = nav_link(controller: :snippets) do - = sidebar_link project_snippets_path(@project), title: _('Snippets'), css: 'shortcuts-snippets' do + = link_to project_snippets_path(@project), class: 'shortcuts-snippets' do .nav-icon-container = custom_icon('snippets') %span.nav-item-name @@ -231,7 +231,7 @@ - if project_nav_tab? :settings = nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show]) do - = sidebar_link edit_project_path(@project), title: _('Settings'), css: 'shortcuts-tree' do + = link_to edit_project_path(@project), class: 'shortcuts-tree' do .nav-icon-container = custom_icon('settings') %span.nav-item-name diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 352c2d66bab..66d1d1e8d44 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -3,6 +3,26 @@ = render 'profiles/head' = form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f| + .col-lg-4.application-theme + %h4.prepend-top-0 + GitLab navigation theme + %p Customize the appearance of the application header and navigation sidebar. + .col-lg-8.application-theme + - Gitlab::Themes.each do |theme| + = label_tag do + .preview{ class: theme.name.downcase } + .preview-row + .quadrant.one + .quadrant.two + .preview-row + .quadrant.three + .quadrant.four + = f.radio_button :theme_id, theme.id, checked: Gitlab::Themes.for_user(@user).id == theme.id + = theme.name + + .col-sm-12 + %hr + .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 Syntax highlighting theme diff --git a/app/views/profiles/preferences/update.js.erb b/app/views/profiles/preferences/update.js.erb index 431ab9d052b..8966dd3fd86 100644 --- a/app/views/profiles/preferences/update.js.erb +++ b/app/views/profiles/preferences/update.js.erb @@ -1,3 +1,7 @@ +// Remove body class for any previous theme, re-add current one +$('body').removeClass('<%= Gitlab::Themes.body_classes %>') +$('body').addClass('<%= user_application_theme %>') + // Toggle container-fluid class if ('<%= current_user.layout %>' === 'fluid') { $('.content-wrapper .container-fluid').removeClass('container-limited') diff --git a/app/views/projects/blob/viewers/_download.html.haml b/app/views/projects/blob/viewers/_download.html.haml index 6d1138f7959..253566c43be 100644 --- a/app/views/projects/blob/viewers/_download.html.haml +++ b/app/views/projects/blob/viewers/_download.html.haml @@ -1,5 +1,5 @@ .file-content.blob_file.blob-no-preview - .center + .center.render-error.vertical-center = link_to blob_raw_path do %h1.light = icon('download') diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 19712a8f1be..05c1d2b383c 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -43,7 +43,8 @@ data: { toggle: "modal", target: "#modal-delete-branch", delete_path: project_branch_path(@project, branch.name), - branch_name: branch.name } } + branch_name: branch.name, + is_merged: ("true" if @repository.merged_to_root_ref?(branch.name)) } } = icon("trash-o") - else %button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled", diff --git a/app/views/projects/branches/_delete_protected_modal.html.haml b/app/views/projects/branches/_delete_protected_modal.html.haml index c5888afa54d..f00a0ee6925 100644 --- a/app/views/projects/branches/_delete_protected_modal.html.haml +++ b/app/views/projects/branches/_delete_protected_modal.html.haml @@ -6,13 +6,18 @@ %h3.page-title Delete protected branch = surround "'", "'?" do - %span.js-branch-name>[branch name] + %span.js-branch-name.ref-name>[branch name] .modal-body %p You’re about to permanently delete the protected branch = succeed '.' do - %strong.js-branch-name [branch name] + %strong.js-branch-name.ref-name [branch name] + %p.js-not-merged + - default_branch = capture do + %span.ref-name= @repository.root_ref + = s_("Branches|This branch hasn’t been merged into %{default_branch}.").html_safe % { default_branch: default_branch } + = s_("Branches|To avoid data loss, consider merging this branch before deleting it.") %p Once you confirm and press = succeed ',' do diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 883922dbf04..9d85e027ac9 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -1,4 +1,4 @@ -- pipeline = local_assigns.fetch(:pipeline) { project.pipelines.latest_successful_for(ref) } +- pipeline = local_assigns.fetch(:pipeline) { project.latest_successful_pipeline_for(ref) } - if !project.empty_repo? && can?(current_user, :download_code, project) .project-action-button.dropdown.inline> @@ -26,18 +26,16 @@ %i.fa.fa-download %span= _('Download tar') - - if pipeline - - artifacts = pipeline.builds.latest.with_artifacts - - if artifacts.any? - %li.dropdown-header Artifacts - - unless pipeline.latest? - - latest_pipeline = project.pipeline_for(ref) - %li - .unclickable= ci_status_for_statuseable(latest_pipeline) - %li.dropdown-header Previous Artifacts - - artifacts.each do |job| - %li - = link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do - %i.fa.fa-download - %span - #{ s_('DownloadArtifacts|Download') } '#{job.name}' + - if pipeline && pipeline.latest_builds_with_artifacts.any? + %li.dropdown-header Artifacts + - unless pipeline.latest? + - latest_pipeline = project.pipeline_for(ref) + %li + .unclickable= ci_status_for_statuseable(latest_pipeline) + %li.dropdown-header Previous Artifacts + - pipeline.latest_builds_with_artifacts.each do |job| + %li + = link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do + %i.fa.fa-download + %span + #{s_('DownloadArtifacts|Download')} '#{job.name}' diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index b8655808d89..a16ffb433a5 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -32,7 +32,7 @@ .commiter - commit_author_link = commit_author_link(commit, avatar: false, size: 24) - - commit_timeago = time_ago_with_tooltip(commit.committed_date) + - commit_timeago = time_ago_with_tooltip(commit.committed_date, placement: 'bottom') - commit_text = _('%{commit_author_link} committed %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago } #{ commit_text.html_safe } diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index 94b7db5eb25..a518fced2b4 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -2,22 +2,22 @@ .clearfix - if params[:to] && params[:from] .compare-switch-container - = link_to icon('exchange'), {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip btn btn-white', title: 'Switch base of comparison'} - .form-group.dropdown.compare-form-group.from.js-compare-from-dropdown - .input-group.inline-input-group - %span.input-group-addon from - = hidden_field_tag :from, params[:from] - = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do - .dropdown-toggle-text.str-truncated= params[:from] || 'Select branch/tag' - = render 'shared/ref_dropdown' - .compare-ellipsis.inline ... + = link_to icon('exchange'), { from: params[:to], to: params[:from] }, class: 'commits-compare-switch has-tooltip btn btn-white', title: 'Swap revisions' .form-group.dropdown.compare-form-group.to.js-compare-to-dropdown .input-group.inline-input-group - %span.input-group-addon to + %span.input-group-addon Source = hidden_field_tag :to, params[:to] = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do .dropdown-toggle-text.str-truncated= params[:to] || 'Select branch/tag' = render 'shared/ref_dropdown' + .compare-ellipsis.inline ... + .form-group.dropdown.compare-form-group.from.js-compare-from-dropdown + .input-group.inline-input-group + %span.input-group-addon Target + = hidden_field_tag :from, params[:from] + = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do + .dropdown-toggle-text.str-truncated= params[:from] || 'Select branch/tag' + = render 'shared/ref_dropdown' = button_tag "Compare", class: "btn btn-create commits-compare-btn" - if @merge_request.present? diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index 2632fea6eba..1ce3ad0c0fd 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -7,13 +7,19 @@ .sub-header-block Compare Git revisions. %br - Fill input field with commit SHA like - %code.ref-name 4eedf23 - or branch/tag name like - %code.ref-name master - and press compare button for the commits list and a code diff. + Choose a branch/tag (e.g. + = succeed ')' do + %code.ref-name master + or enter a commit SHA (e.g. + = succeed ')' do + %code.ref-name 4eedf23 + to see what's changed or to create a merge request. %br - Changes are shown <b>from</b> the version in the first field <b>to</b> the version in the second field. + Changes are shown as if the + %b source + revision was being merged into the + %b target + revision. .prepend-top-20 = render "form" diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml index ad2d355ab4a..2de2cf9e38c 100644 --- a/app/views/projects/diffs/_stats.html.haml +++ b/app/views/projects/diffs/_stats.html.haml @@ -21,9 +21,9 @@ %ul - diff_files.each do |diff_file| %li - %a{ href: "##{hexdigest(diff_file.file_path)}", title: diff_file.new_path } + %a.diff-changed-file{ href: "##{hexdigest(diff_file.file_path)}", title: diff_file.new_path } = icon("#{diff_file_changed_icon(diff_file)} fw", class: "#{diff_file_changed_icon_color(diff_file)} append-right-5") - %span.diff-file-changes-path= diff_file.new_path + %span.diff-file-changes-path.append-right-5= diff_file.new_path .pull-right %span.cgreen< +#{diff_file.added_lines} diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml index aa004a739d7..01879556894 100644 --- a/app/views/projects/diffs/viewers/_image.html.haml +++ b/app/views/projects/diffs/viewers/_image.html.haml @@ -41,10 +41,10 @@ .swipe.view.hide .swipe-frame .frame.deleted - = image_tag(old_blob_raw_path, alt: diff_file.old_path) + = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) .swipe-wrap .frame.added - = image_tag(blob_raw_path, alt: diff_file.new_path) + = image_tag(blob_raw_path, alt: diff_file.new_path, lazy: false) %span.swipe-bar %span.top-handle %span.bottom-handle @@ -52,9 +52,9 @@ .onion-skin.view.hide .onion-skin-frame .frame.deleted - = image_tag(old_blob_raw_path, alt: diff_file.old_path) + = image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) .frame.added - = image_tag(blob_raw_path, alt: diff_file.new_path) + = image_tag(blob_raw_path, alt: diff_file.new_path, lazy: false) .controls .transparent .drag-track diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml index ac8e15a48b2..e660fce652f 100644 --- a/app/views/projects/runners/_form.html.haml +++ b/app/views/projects/runners/_form.html.haml @@ -11,7 +11,7 @@ .col-sm-10 .checkbox = f.check_box :access_level, {}, 'ref_protected', 'not_protected' - %span.light This runner will only run on pipelines trigged on protected branches + %span.light This runner will only run on pipelines triggered on protected branches .form-group = label :run_untagged, 'Run untagged jobs', class: 'control-label' .col-sm-10 @@ -39,6 +39,6 @@ Tags .col-sm-10 = f.text_field :tag_list, value: runner.tag_list.sort.join(', '), class: 'form-control' - .help-block You can setup jobs to only use Runners with specific tags + .help-block You can setup jobs to only use Runners with specific tags. Separate tags with commas. .form-actions = f.submit 'Save changes', class: 'btn btn-save' diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index c4ed7f6e750..d3f0aa2d339 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -11,13 +11,13 @@ - if params[:author_id].present? = hidden_field_tag(:author_id, params[:author_id]) = dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit", - placeholder: "Search authors", data: { any_user: "Any Author", first_user: current_user.try(:username), current_user: true, project_id: @project.try(:id), selected: params[:author_id], field_name: "author_id", default_label: "Author" } }) + placeholder: "Search authors", data: { any_user: "Any Author", first_user: current_user&.username, current_user: true, project_id: @project&.id, group_id: @group&.id, selected: params[:author_id], field_name: "author_id", default_label: "Author" } }) .filter-item.inline - if params[:assignee_id].present? = hidden_field_tag(:assignee_id, params[:assignee_id]) = dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", - placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: current_user.try(:username), null_user: true, current_user: true, project_id: @project.try(:id), group_id: @group&.id, selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } }) + placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: current_user&.username, null_user: true, current_user: true, project_id: @project&.id, group_id: @group&.id, selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } }) .filter-item.inline.milestone-filter = render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true, show_started: true diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 0afa48b392c..9cae3f51825 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -24,9 +24,9 @@ .block.milestone .sidebar-collapsed-icon = icon('clock-o', 'aria-hidden': 'true') - %span + %span.milestone-title - if issuable.milestone - %span.has-tooltip{ title: milestone_remaining_days(issuable.milestone), data: { container: 'body', html: 1, placement: 'left' } } + %span.has-tooltip{ title: "#{issuable.milestone.title}<br>#{milestone_remaining_days(issuable.milestone)}", data: { container: 'body', html: 1, placement: 'left' } } = issuable.milestone.title - else None |