diff options
author | Phil Hughes <me@iamphill.com> | 2017-07-07 15:48:05 +0100 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2017-07-07 15:48:05 +0100 |
commit | fee26d777cd89aa26f7530cfe116defeb9f2c551 (patch) | |
tree | d10eb95db0d41b512c4880302032eb3fb1b00b6b /app | |
parent | 15a282387938c896ff7b83cb1bb4fe447b856b2b (diff) | |
parent | 1a3edcec4363239a4d080bc9af53d9d455dccfb4 (diff) | |
download | gitlab-ce-fee26d777cd89aa26f7530cfe116defeb9f2c551.tar.gz |
Merge branch 'master' into new-nav-fix-contextual-breadcrumbs
Diffstat (limited to 'app')
90 files changed, 707 insertions, 430 deletions
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index 1be9df19c81..6a008112203 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -2,6 +2,7 @@ import './lib/utils/url_utility'; import FilesCommentButton from './files_comment_button'; +import SingleFileDiff from './single_file_diff'; const UNFOLD_COUNT = 20; let isBound = false; @@ -10,7 +11,11 @@ class Diff { constructor() { const $diffFile = $('.files .diff-file'); - $diffFile.singleFileDiff(); + $diffFile.each((index, file) => { + if (!$.data(file, 'singleFileDiff')) { + $.data(file, 'singleFileDiff', new SingleFileDiff(file)); + } + }); FilesCommentButton.init($diffFile); diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 72a39fcb283..9e90a36a364 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -1,18 +1,14 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */ /* global ProjectSelect */ -/* global UsernameValidator */ -/* global ActiveTabMemoizer */ /* global ShortcutsNavigation */ /* global IssuableIndex */ /* global ShortcutsIssuable */ -/* global ZenMode */ /* global Milestone */ /* global IssuableForm */ /* global LabelsSelect */ /* global MilestoneSelect */ /* global Commit */ /* global NotificationsForm */ -/* global TreeView */ /* global NotificationsDropdown */ /* global GroupAvatar */ /* global LineHighlighter */ @@ -26,7 +22,6 @@ /* global ProjectAvatar */ /* global CompareAutocomplete */ /* global ProjectNew */ -/* global Star */ /* global ProjectShow */ /* global Labels */ /* global Shortcuts */ @@ -55,9 +50,19 @@ import UsersSelect from './users_select'; import RefSelectDropdown from './ref_select_dropdown'; import GfmAutoComplete from './gfm_auto_complete'; import ShortcutsBlob from './shortcuts_blob'; +import SigninTabsMemoizer from './signin_tabs_memoizer'; +import Star from './star'; +import Todos from './todos'; +import TreeView from './tree'; +import UsagePing from './usage_ping'; +import UsernameValidator from './username_validator'; +import VersionCheckImage from './version_check_image'; +import Wikis from './wikis'; +import ZenMode from './zen_mode'; import initSettingsPanels from './settings_panels'; import initExperimentalFlags from './experimental_flags'; import OAuthRememberMe from './oauth_remember_me'; +import PerformanceBar from './performance_bar'; (function() { var Dispatcher; @@ -128,7 +133,7 @@ import OAuthRememberMe from './oauth_remember_me'; break; case 'sessions:new': new UsernameValidator(); - new ActiveTabMemoizer(); + new SigninTabsMemoizer(); new OAuthRememberMe({ container: $(".omniauth-container") }).bindEvents(); break; case 'projects:boards:show': @@ -168,7 +173,7 @@ import OAuthRememberMe from './oauth_remember_me'; new ProjectSelect(); break; case 'dashboard:todos:index': - new gl.Todos(); + new Todos(); break; case 'dashboard:projects:index': case 'dashboard:projects:starred': @@ -323,7 +328,7 @@ import OAuthRememberMe from './oauth_remember_me'; new gl.Members(); new UsersSelect(); break; - case 'projects:settings:members:show': + case 'projects:project_members:index': new gl.MemberExpirationDate('.js-access-expiration-date-groups'); new GroupsSelect(); new gl.MemberExpirationDate(); @@ -385,7 +390,7 @@ import OAuthRememberMe from './oauth_remember_me'; new BlobViewer(); break; case 'help:index': - gl.VersionCheckImage.bindErrorEvent($('img.js-version-status-badge')); + VersionCheckImage.bindErrorEvent($('img.js-version-status-badge')); break; case 'search:show': new Search(); @@ -401,6 +406,7 @@ import OAuthRememberMe from './oauth_remember_me'; initSettingsPanels(); break; case 'projects:settings:ci_cd:show': + case 'groups:settings:ci_cd:show': new gl.ProjectVariables(); break; case 'ci:lints:create': @@ -437,7 +443,7 @@ import OAuthRememberMe from './oauth_remember_me'; new Admin(); switch (path[1]) { case 'cohorts': - new gl.UsagePing(); + new UsagePing(); break; case 'groups': new UsersSelect(); @@ -489,7 +495,7 @@ import OAuthRememberMe from './oauth_remember_me'; new NotificationsDropdown(); break; case 'wikis': - new gl.Wikis(); + new Wikis(); shortcut_handler = new ShortcutsWiki(); new ZenMode(); new gl.GLForm($('.wiki-form'), true); @@ -521,6 +527,10 @@ import OAuthRememberMe from './oauth_remember_me'; if (!shortcut_handler) { new Shortcuts(); } + + if (document.querySelector('#peek')) { + new PerformanceBar({ container: '#peek' }); + } }; Dispatcher.prototype.initSearch = function() { diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js index 92f6f0d4117..9ac1325fc95 100644 --- a/app/assets/javascripts/issuable_form.js +++ b/app/assets/javascripts/issuable_form.js @@ -1,12 +1,12 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-useless-escape, no-new, quotes, object-shorthand, no-unused-vars, comma-dangle, no-alert, consistent-return, no-else-return, prefer-template, one-var, one-var-declaration-per-line, curly, max-len */ /* global GitLab */ -/* global ZenMode */ /* global Autosave */ /* global dateFormat */ /* global Pikaday */ import UsersSelect from './users_select'; import GfmAutoComplete from './gfm_auto_complete'; +import ZenMode from './zen_mode'; (function() { this.IssuableForm = (function() { diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js index 0860e237ce1..7490cd31f58 100644 --- a/app/assets/javascripts/issue.js +++ b/app/assets/javascripts/issue.js @@ -4,13 +4,13 @@ import 'vendor/jquery.waitforimages'; import '~/lib/utils/text_utility'; import './flash'; -import './task_list'; +import TaskList from './task_list'; import CreateMergeRequestDropdown from './create_merge_request_dropdown'; class Issue { constructor() { if ($('a.btn-close').length) { - this.taskList = new gl.TaskList({ + this.taskList = new TaskList({ dataType: 'issue', fieldName: 'description', selector: '.detail-page-description', diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue index 43db66c8e08..48bad8f1e68 100644 --- a/app/assets/javascripts/issue_show/components/description.vue +++ b/app/assets/javascripts/issue_show/components/description.vue @@ -1,5 +1,6 @@ <script> import animateMixin from '../mixins/animate'; + import TaskList from '../../task_list'; export default { mixins: [animateMixin], @@ -46,7 +47,7 @@ if (this.canUpdate) { // eslint-disable-next-line no-new - new gl.TaskList({ + new TaskList({ dataType: 'issue', fieldName: 'description', selector: '.detail-page-description', diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index fe752d95b90..892b3fab1c6 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -143,26 +143,13 @@ import './render_math'; import './right_sidebar'; import './search'; import './search_autocomplete'; -import './signin_tabs_memoizer'; -import './single_file_diff'; import './smart_interval'; import './snippets_list'; import './star'; import './subscription'; import './subscription_select'; import './syntax_highlight'; -import './task_list'; -import './todos'; -import './tree'; -import './usage_ping'; import './user'; -import './user_tabs'; -import './username_validator'; -import './users_select'; -import './version_check_image'; -import './visibility_select'; -import './wikis'; -import './zen_mode'; // eslint-disable-next-line global-require, import/no-commonjs if (process.env.NODE_ENV !== 'production') require('./test_utils/'); diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js index f93feeec1c2..7ba9efc5387 100644 --- a/app/assets/javascripts/merge_request.js +++ b/app/assets/javascripts/merge_request.js @@ -2,7 +2,7 @@ /* global MergeRequestTabs */ import 'vendor/jquery.waitforimages'; -import './task_list'; +import TaskList from './task_list'; import './merge_request_tabs'; (function() { @@ -25,7 +25,7 @@ import './merge_request_tabs'; this.initMRBtnListeners(); this.initCommitMessageListeners(); if ($("a.btn-close").length) { - this.taskList = new gl.TaskList({ + this.taskList = new TaskList({ dataType: 'merge_request', fieldName: 'description', selector: '.detail-page-description', diff --git a/app/assets/javascripts/monitoring/components/monitoring_column.vue b/app/assets/javascripts/monitoring/components/monitoring_column.vue index 0cd62053d14..c376baea79c 100644 --- a/app/assets/javascripts/monitoring/components/monitoring_column.vue +++ b/app/assets/javascripts/monitoring/components/monitoring_column.vue @@ -105,9 +105,9 @@ this.measurements = measurements.small; } this.data = query.result[0].values; - this.unitOfDisplay = query.unit || 'N/A'; + this.unitOfDisplay = query.unit || ''; this.yAxisLabel = this.columnData.y_label || 'Values'; - this.legendTitle = query.legend || 'Average'; + this.legendTitle = query.label || 'Average'; this.graphWidth = this.$refs.baseSvg.clientWidth - this.margin.left - this.margin.right; this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom; @@ -219,16 +219,16 @@ }; </script> <template> - <div + <div :class="classType"> - <h5 + <h5 class="text-center graph-title"> {{columnData.title}} </h5> <div class="prometheus-svg-container" :style="paddingBottomRootSvg"> - <svg + <svg :viewBox="outterViewBox" ref="baseSvg"> <g @@ -239,7 +239,7 @@ class="y-axis" transform="translate(70, 20)"> </g> - <monitoring-legends + <monitoring-legends :graph-width="graphWidth" :graph-height="graphHeight" :margin="margin" @@ -249,7 +249,7 @@ :y-axis-label="yAxisLabel" :metric-usage="metricUsage" /> - <svg + <svg class="graph-data" :viewBox="innerViewBox" ref="graphData"> @@ -267,7 +267,7 @@ stroke-width="2" transform="translate(-5, 20)"> </path> - <rect + <rect class="prometheus-graph-overlay" :width="(graphWidth - 70)" :height="(graphHeight - 100)" @@ -281,7 +281,7 @@ :graph-height="graphHeight" :graph-height-offset="graphHeightOffset" /> - <monitoring-flag + <monitoring-flag v-if="showFlag" :current-x-coordinate="currentXCoordinate" :current-y-coordinate="currentYCoordinate" diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 555b8c8a65c..1a68c5bca00 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -21,7 +21,7 @@ import CommentTypeToggle from './comment_type_toggle'; import loadAwardsHandler from './awards_handler'; import './autosave'; import './dropzone_input'; -import './task_list'; +import TaskList from './task_list'; window.autosize = autosize; window.Dropzone = Dropzone; @@ -71,7 +71,7 @@ export default class Notes { this.addBinding(); this.setPollingInterval(); this.setupMainTargetNoteForm(); - this.taskList = new gl.TaskList({ + this.taskList = new TaskList({ dataType: 'note', fieldName: 'note', selector: '.notes' diff --git a/app/assets/javascripts/peek.js b/app/assets/javascripts/peek.js deleted file mode 100644 index de1a99fa3bd..00000000000 --- a/app/assets/javascripts/peek.js +++ /dev/null @@ -1,16 +0,0 @@ -import 'vendor/peek'; -import 'vendor/peek.performance_bar'; - -$(document).on('click', '#peek-show-queries', (e) => { - e.preventDefault(); - $('.peek-rblineprof-modal').hide(); - const $modal = $('#modal-peek-pg-queries'); - if ($modal.length) { - $modal.modal('toggle'); - } -}); - -$(document).on('click', '.js-lineprof-file', (e) => { - e.preventDefault(); - $(e.target).parents('.peek-rblineprof-file').find('.data').toggle(); -}); diff --git a/app/assets/javascripts/performance_bar.js b/app/assets/javascripts/performance_bar.js new file mode 100644 index 00000000000..9bbdf7f513c --- /dev/null +++ b/app/assets/javascripts/performance_bar.js @@ -0,0 +1,62 @@ +import 'vendor/peek'; +import 'vendor/peek.performance_bar'; + +export default class PerformanceBar { + constructor(opts) { + if (!PerformanceBar.singleton) { + this.init(opts); + PerformanceBar.singleton = this; + } + return PerformanceBar.singleton; + } + + init(opts) { + const $container = $(opts.container); + this.$sqlProfileLink = $container.find('.js-toggle-modal-peek-sql'); + this.$sqlProfileModal = $container.find('#modal-peek-pg-queries'); + this.$lineProfileLink = $container.find('.js-toggle-modal-peek-line-profile'); + this.$lineProfileModal = $('#modal-peek-line-profile'); + this.initEventListeners(); + this.showModalOnLoad(); + } + + initEventListeners() { + this.$sqlProfileLink.on('click', () => this.handleSQLProfileLink()); + this.$lineProfileLink.on('click', e => this.handleLineProfileLink(e)); + $(document).on('click', '.js-lineprof-file', PerformanceBar.toggleLineProfileFile); + } + + showModalOnLoad() { + // When a lineprofiler query-string param is present, we show the line + // profiler modal upon page load + if (/lineprofiler/.test(window.location.search)) { + PerformanceBar.toggleModal(this.$lineProfileModal); + } + } + + handleSQLProfileLink() { + PerformanceBar.toggleModal(this.$sqlProfileModal); + } + + handleLineProfileLink(e) { + const lineProfilerParameter = gl.utils.getParameterValues('lineprofiler'); + const lineProfilerParameterRegex = new RegExp(`lineprofiler=${lineProfilerParameter[0]}`); + const shouldToggleModal = lineProfilerParameter.length > 0 && + lineProfilerParameterRegex.test(e.currentTarget.href); + + if (shouldToggleModal) { + e.preventDefault(); + PerformanceBar.toggleModal(this.$lineProfileModal); + } + } + + static toggleModal($modal) { + if ($modal.length) { + $modal.modal('toggle'); + } + } + + static toggleLineProfileFile(e) { + $(e.currentTarget).parents('.peek-rblineprof-file').find('.data').toggle(); + } +} diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js index c0f757269cb..fd89a1a85c3 100644 --- a/app/assets/javascripts/project_new.js +++ b/app/assets/javascripts/project_new.js @@ -1,5 +1,7 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, one-var, no-underscore-dangle, prefer-template, no-else-return, prefer-arrow-callback, max-len */ +import VisibilitySelect from './visibility_select'; + function highlightChanges($elm) { $elm.addClass('highlight-changes'); setTimeout(() => $elm.removeClass('highlight-changes'), 10); @@ -30,7 +32,7 @@ function highlightChanges($elm) { ProjectNew.prototype.initVisibilitySelect = function() { const visibilityContainer = document.querySelector('.js-visibility-select'); if (!visibilityContainer) return; - const visibilitySelect = new gl.VisibilitySelect(visibilityContainer); + const visibilitySelect = new VisibilitySelect(visibilityContainer); visibilitySelect.init(); const $visibilitySelect = $(visibilityContainer).find('select'); diff --git a/app/assets/javascripts/signin_tabs_memoizer.js b/app/assets/javascripts/signin_tabs_memoizer.js index 3997a695d15..20255398047 100644 --- a/app/assets/javascripts/signin_tabs_memoizer.js +++ b/app/assets/javascripts/signin_tabs_memoizer.js @@ -6,7 +6,7 @@ import AccessorUtilities from './lib/utils/accessor'; * Memorize the last selected tab after reloading a page. * Does that setting the current selected tab in the localStorage */ -class ActiveTabMemoizer { +export default class SigninTabsMemoizer { constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) { this.currentTabKey = currentTabKey; this.tabSelector = tabSelector; @@ -51,5 +51,3 @@ class ActiveTabMemoizer { return window.localStorage.getItem(this.currentTabKey); } } - -window.ActiveTabMemoizer = ActiveTabMemoizer; diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js index 00d04ce0c33..4505a79a2df 100644 --- a/app/assets/javascripts/single_file_diff.js +++ b/app/assets/javascripts/single_file_diff.js @@ -2,18 +2,13 @@ import FilesCommentButton from './files_comment_button'; -window.SingleFileDiff = (function() { - var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER; +const WRAPPER = '<div class="diff-content"></div>'; +const LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>'; +const ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>'; +const COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>'; - WRAPPER = '<div class="diff-content"></div>'; - - LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>'; - - ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>'; - - COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>'; - - function SingleFileDiff(file) { +export default class SingleFileDiff { + constructor(file) { this.file = file; this.toggleDiff = this.toggleDiff.bind(this); this.content = $('.diff-content', this.file); @@ -37,7 +32,7 @@ window.SingleFileDiff = (function() { }).bind(this)); } - SingleFileDiff.prototype.toggleDiff = function($target, cb) { + toggleDiff($target, cb) { if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return; this.isOpen = !this.isOpen; if (!this.isOpen && !this.hasError) { @@ -58,9 +53,9 @@ window.SingleFileDiff = (function() { this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right'); return this.getContentHTML(cb); } - }; + } - SingleFileDiff.prototype.getContentHTML = function(cb) { + getContentHTML(cb) { this.collapsedContent.hide(); this.loadingContent.show(); $.get(this.diffForPath, (function(_this) { @@ -84,15 +79,5 @@ window.SingleFileDiff = (function() { if (cb) cb(); }; })(this)); - }; - - return SingleFileDiff; -})(); - -$.fn.singleFileDiff = function() { - return this.each(function() { - if (!$.data(this, 'singleFileDiff')) { - return $.data(this, 'singleFileDiff', new window.SingleFileDiff(this)); - } - }); -}; + } +} diff --git a/app/assets/javascripts/snippets_list.js b/app/assets/javascripts/snippets_list.js index da7b9e08447..3b6d999b1c3 100644 --- a/app/assets/javascripts/snippets_list.js +++ b/app/assets/javascripts/snippets_list.js @@ -1,9 +1,9 @@ -/* eslint-disable arrow-parens, no-param-reassign, space-before-function-paren, func-names, no-var, max-len */ - -window.gl.SnippetsList = function() { - var $holder = $('.snippets-list-holder'); +function SnippetsList() { + const $holder = $('.snippets-list-holder'); $holder.find('.pagination').on('ajax:success', (e, data) => { $holder.replaceWith(data.html); }); -}; +} + +window.gl.SnippetsList = SnippetsList; diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js index 840ae1edd9d..6d38124f1c1 100644 --- a/app/assets/javascripts/star.js +++ b/app/assets/javascripts/star.js @@ -1,8 +1,8 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, max-len */ /* global Flash */ -window.Star = (function() { - function Star() { +export default class Star { + constructor() { $('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) { var $starIcon, $starSpan, $this, toggleStar; $this = $(this); @@ -23,6 +23,4 @@ window.Star = (function() { new Flash('Star toggle failed. Try again later.', 'alert'); }); } - - return Star; -})(); +} diff --git a/app/assets/javascripts/subscription_select.js b/app/assets/javascripts/subscription_select.js index a48434181b6..37e39ce5477 100644 --- a/app/assets/javascripts/subscription_select.js +++ b/app/assets/javascripts/subscription_select.js @@ -1,7 +1,7 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, max-len */ -window.SubscriptionSelect = (function() { - function SubscriptionSelect() { +class SubscriptionSelect { + constructor() { $('.js-subscription-event').each(function(i, el) { var fieldName; fieldName = $(el).data("field-name"); @@ -28,6 +28,6 @@ window.SubscriptionSelect = (function() { }); }); } +} - return SubscriptionSelect; -})(); +window.SubscriptionSelect = SubscriptionSelect; diff --git a/app/assets/javascripts/task_list.js b/app/assets/javascripts/task_list.js index 419c458ff34..c39f569da5e 100644 --- a/app/assets/javascripts/task_list.js +++ b/app/assets/javascripts/task_list.js @@ -2,7 +2,7 @@ import 'deckar01-task_list'; -class TaskList { +export default class TaskList { constructor(options = {}) { this.selector = options.selector; this.dataType = options.dataType; @@ -48,6 +48,3 @@ class TaskList { }); } } - -window.gl = window.gl || {}; -window.gl.TaskList = TaskList; diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js index 7230946b484..cd305631c10 100644 --- a/app/assets/javascripts/todos.js +++ b/app/assets/javascripts/todos.js @@ -2,7 +2,7 @@ import UsersSelect from './users_select'; -class Todos { +export default class Todos { constructor() { this.initFilters(); this.bindEvents(); @@ -159,6 +159,3 @@ class Todos { } } } - -window.gl = window.gl || {}; -gl.Todos = Todos; diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js index 77ae6109bc6..7777ed1c3dc 100644 --- a/app/assets/javascripts/tree.js +++ b/app/assets/javascripts/tree.js @@ -1,7 +1,7 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, class-methods-use-this */ -window.TreeView = (function() { - function TreeView() { +export default class TreeView { + constructor() { this.initKeyNav(); // Code browser tree slider // Make the entire tree-item row clickable, but not if clicking another link (like a commit message) @@ -22,7 +22,7 @@ window.TreeView = (function() { $('span.log_loading:first').removeClass('hide'); } - TreeView.prototype.initKeyNav = function() { + initKeyNav() { var li, liSelected; li = $("tr.tree-item"); liSelected = null; @@ -60,7 +60,5 @@ window.TreeView = (function() { } } }); - }; - - return TreeView; -})(); + } +} diff --git a/app/assets/javascripts/usage_ping.js b/app/assets/javascripts/usage_ping.js index fd3af7d7ab6..2389056bd02 100644 --- a/app/assets/javascripts/usage_ping.js +++ b/app/assets/javascripts/usage_ping.js @@ -1,4 +1,4 @@ -function UsagePing() { +export default function UsagePing() { const usageDataUrl = $('.usage-data').data('endpoint'); $.ajax({ @@ -10,6 +10,3 @@ function UsagePing() { }, }); } - -window.gl = window.gl || {}; -window.gl.UsagePing = UsagePing; diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/user.js index 3ab9ef5408e..9ef94ac7616 100644 --- a/app/assets/javascripts/user.js +++ b/app/assets/javascripts/user.js @@ -1,6 +1,7 @@ /* eslint-disable class-methods-use-this, comma-dangle, arrow-parens, no-param-reassign */ import Cookies from 'js-cookie'; +import UserTabs from './user_tabs'; class User { constructor({ action }) { @@ -17,7 +18,7 @@ class User { } initTabs() { - return new window.gl.UserTabs({ + return new UserTabs({ parentEl: '.user-profile', action: this.action }); diff --git a/app/assets/javascripts/user_tabs.js b/app/assets/javascripts/user_tabs.js index be70f4cb4e2..f8e23c8624d 100644 --- a/app/assets/javascripts/user_tabs.js +++ b/app/assets/javascripts/user_tabs.js @@ -60,7 +60,7 @@ content on the Users#show page. </div> */ -class UserTabs { +export default class UserTabs { constructor ({ defaultAction, action, parentEl }) { this.loaded = {}; this.defaultAction = defaultAction || 'activity'; @@ -171,6 +171,3 @@ class UserTabs { return this.$parentEl.find('.nav-links .active a').data('action'); } } - -window.gl = window.gl || {}; -window.gl.UserTabs = UserTabs; diff --git a/app/assets/javascripts/username_validator.js b/app/assets/javascripts/username_validator.js index abe6c30f4f3..a348d69153c 100644 --- a/app/assets/javascripts/username_validator.js +++ b/app/assets/javascripts/username_validator.js @@ -8,7 +8,7 @@ const successMessageSelector = '.username .validation-success'; const pendingMessageSelector = '.username .validation-pending'; const invalidMessageSelector = '.username .gl-field-error'; -class UsernameValidator { +export default class UsernameValidator { constructor() { this.inputElement = $('#new_user_username'); this.inputDomElement = this.inputElement.get(0); @@ -129,5 +129,3 @@ class UsernameValidator { $inputErrorMessage.show(); } } - -window.UsernameValidator = UsernameValidator; diff --git a/app/assets/javascripts/version_check_image.js b/app/assets/javascripts/version_check_image.js index 88ba991af47..ec515e892c6 100644 --- a/app/assets/javascripts/version_check_image.js +++ b/app/assets/javascripts/version_check_image.js @@ -3,6 +3,3 @@ export default class VersionCheckImage { imageElement.off('error').on('error', () => imageElement.hide()); } } - -window.gl = window.gl || {}; -gl.VersionCheckImage = VersionCheckImage; diff --git a/app/assets/javascripts/visibility_select.js b/app/assets/javascripts/visibility_select.js index b6bbbaa0936..0c928d0d5f6 100644 --- a/app/assets/javascripts/visibility_select.js +++ b/app/assets/javascripts/visibility_select.js @@ -1,4 +1,4 @@ -class VisibilitySelect { +export default class VisibilitySelect { constructor(container) { if (!container) throw new Error('VisibilitySelect requires a container element as argument 1'); this.container = container; @@ -19,6 +19,3 @@ class VisibilitySelect { this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description; } } - -window.gl = window.gl || {}; -window.gl.VisibilitySelect = VisibilitySelect; diff --git a/app/assets/javascripts/wikis.js b/app/assets/javascripts/wikis.js index 03d183ebd84..00676bcb0b3 100644 --- a/app/assets/javascripts/wikis.js +++ b/app/assets/javascripts/wikis.js @@ -1,10 +1,9 @@ -/* eslint-disable no-param-reassign */ /* global Breakpoints */ import 'vendor/jquery.nicescroll'; import './breakpoints'; -class Wikis { +export default class Wikis { constructor() { this.bp = Breakpoints.get(); this.sidebarEl = document.querySelector('.js-wiki-sidebar'); @@ -63,6 +62,3 @@ class Wikis { } } } - -window.gl = window.gl || {}; -window.gl.Wikis = Wikis; diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js index 08f80735e93..99c7644e4d9 100644 --- a/app/assets/javascripts/zen_mode.js +++ b/app/assets/javascripts/zen_mode.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, comma-dangle, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, comma-dangle, max-len, class-methods-use-this */ /* global Mousetrap */ // Zen Mode (full screen) textarea @@ -35,8 +35,8 @@ window.Dropzone = Dropzone; // **Target** a.js-zen-leave // -window.ZenMode = (function() { - function ZenMode() { +export default class ZenMode { + constructor() { this.active_backdrop = null; this.active_textarea = null; $(document).on('click', '.js-zen-enter', function(e) { @@ -66,7 +66,7 @@ window.ZenMode = (function() { }); } - ZenMode.prototype.enter = function(backdrop) { + enter(backdrop) { Mousetrap.pause(); this.active_backdrop = $(backdrop); this.active_backdrop.addClass('fullscreen'); @@ -74,9 +74,9 @@ window.ZenMode = (function() { // Prevent a user-resized textarea from persisting to fullscreen this.active_textarea.removeAttr('style'); return this.active_textarea.focus(); - }; + } - ZenMode.prototype.exit = function() { + exit() { if (this.active_textarea) { Mousetrap.unpause(); this.active_textarea.closest('.zen-backdrop').removeClass('fullscreen'); @@ -85,13 +85,11 @@ window.ZenMode = (function() { this.active_backdrop = null; return Dropzone.forElement('.div-dropzone').enable(); } - }; + } - ZenMode.prototype.scrollTo = function(zen_area) { + scrollTo(zen_area) { return $.scrollTo(zen_area, 0, { offset: -150 }); - }; - - return ZenMode; -})(); + } +} diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index 7098203321d..a28f54936be 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -21,3 +21,9 @@ body.modal-open { width: 860px; } } + +@media (min-width: $screen-lg-min) { + .modal-full { + width: 98%; + } +} diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index da4d91511e0..2e4b1280a97 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -594,3 +594,15 @@ Convdev Index $color-high-score: $green-400; $color-average-score: $orange-400; $color-low-score: $red-400; + +/* +Performance Bar +*/ +$perf-bar-text: #999; +$perf-bar-production: #222; +$perf-bar-staging: #291430; +$perf-bar-development: #4c1210; +$perf-bar-bucket-bg: #111; +$perf-bar-bucket-color: #ccc; +$perf-bar-bucket-box-shadow-from: rgba($white-light, .2); +$perf-bar-bucket-box-shadow-to: rgba($black, .25); diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 59e0624d94e..7adf17dddb8 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -731,11 +731,11 @@ .merge-request-tabs-holder { top: $header-height; - z-index: 100; + z-index: 200; background-color: $white-light; border-bottom: 1px solid $border-color; - @media(min-width: $screen-sm-min) { + @media (min-width: $screen-sm-min) { position: sticky; position: -webkit-sticky; } @@ -770,6 +770,12 @@ max-width: $limited-layout-width; margin-left: auto; margin-right: auto; + + .inner-page-scroll-tabs { + background-color: $white-light; + margin-left: -$gl-padding; + padding-left: $gl-padding; + } } } diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss new file mode 100644 index 00000000000..2890b6b1e49 --- /dev/null +++ b/app/assets/stylesheets/performance_bar.scss @@ -0,0 +1,103 @@ +@import "framework/variables"; +@import "peek/views/performance_bar"; +@import "peek/views/rblineprof"; + +#peek { + height: 35px; + background: $black; + line-height: 35px; + color: $perf-bar-text; + + &.disabled { + display: none; + } + + &.production { + background-color: $perf-bar-production; + } + + &.staging { + background-color: $perf-bar-staging; + } + + &.development { + background-color: $perf-bar-development; + } + + .wrapper { + width: 1000px; + margin: 0 auto; + } + + // UI Elements + .bucket { + background: $perf-bar-bucket-bg; + display: inline-block; + padding: 4px 6px; + font-family: Consolas, "Liberation Mono", Courier, monospace; + line-height: 1; + color: $perf-bar-bucket-color; + border-radius: 3px; + box-shadow: 0 1px 0 $perf-bar-bucket-box-shadow-from, inset 0 1px 2px $perf-bar-bucket-box-shadow-to; + + .hidden { + display: none; + } + + &:hover .hidden { + display: inline; + } + } + + strong { + color: $white-light; + } + + table { + color: $black; + + strong { + color: $black; + } + } + + .view { + margin-right: 15px; + float: left; + + &:last-child { + margin-right: 0; + } + } + + .css-truncate { + &.css-truncate-target, + .css-truncate-target { + display: inline-block; + max-width: 125px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: top; + } + + &.expandable:hover .css-truncate-target, + &.expandable:hover.css-truncate-target { + max-width: 10000px !important; + } + } +} + +#modal-peek-pg-queries-content { + color: $black; +} + +.peek-rblineprof-file { + pre.duration { + width: 280px; + } + + .data { + overflow: visible; + } +} diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index 650ec1e326a..693e2f6365c 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -47,7 +47,7 @@ module IssuableCollections end def merge_requests_collection - merge_requests_finder.execute.preload(:source_project, :target_project, :author, :assignee, :labels, :milestone, :merge_request_diff, :head_pipeline, target_project: :namespace) + merge_requests_finder.execute.preload(:source_project, :target_project, :author, :assignee, :labels, :milestone, :head_pipeline, target_project: :namespace, merge_request_diff: :merge_request_diff_commits) end def issues_finder diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb index 47d9ae350ae..c6b1e443de6 100644 --- a/app/controllers/concerns/membership_actions.rb +++ b/app/controllers/concerns/membership_actions.rb @@ -70,7 +70,7 @@ module MembershipActions def members_page_url if membershipable.is_a?(Project) - project_settings_members_path(membershipable) + project_project_members_path(membershipable) else polymorphic_url([membershipable, :members]) end diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb new file mode 100644 index 00000000000..0142ad8278c --- /dev/null +++ b/app/controllers/groups/settings/ci_cd_controller.rb @@ -0,0 +1,24 @@ +module Groups + module Settings + class CiCdController < Groups::ApplicationController + before_action :authorize_admin_pipeline! + + def show + define_secret_variables + end + + private + + def define_secret_variables + @variable = Ci::GroupVariable.new(group: group) + .present(current_user: current_user) + @variables = group.variables.order_key_asc + .map { |variable| variable.present(current_user: current_user) } + end + + def authorize_admin_pipeline! + return render_404 unless can?(current_user, :admin_pipeline, group) + end + end + end +end diff --git a/app/controllers/groups/variables_controller.rb b/app/controllers/groups/variables_controller.rb new file mode 100644 index 00000000000..10038ff3ad9 --- /dev/null +++ b/app/controllers/groups/variables_controller.rb @@ -0,0 +1,64 @@ +module Groups + class VariablesController < Groups::ApplicationController + before_action :variable, only: [:show, :update, :destroy] + before_action :authorize_admin_build! + + def index + redirect_to group_settings_ci_cd_path(group) + end + + def show + end + + def update + if variable.update(variable_params) + redirect_to group_variables_path(group), + notice: 'Variable was successfully updated.' + else + render "show" + end + end + + def create + @variable = group.variables.create(variable_params) + .present(current_user: current_user) + + if @variable.persisted? + redirect_to group_settings_ci_cd_path(group), + notice: 'Variable was successfully created.' + else + render "show" + end + end + + def destroy + if variable.destroy + redirect_to group_settings_ci_cd_path(group), + status: 302, + notice: 'Variable was successfully removed.' + else + redirect_to group_settings_ci_cd_path(group), + status: 302, + notice: 'Failed to remove the variable.' + end + end + + private + + def variable_params + params.require(:variable).permit(*variable_params_attributes) + end + + def variable_params_attributes + %i[key value protected] + end + + def variable + @variable ||= group.variables.find(params[:id]).present(current_user: current_user) + end + + def authorize_admin_build! + return render_404 unless can?(current_user, :admin_build, group) + end + end +end diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index 8fc614b414d..f59200d3b1f 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -22,7 +22,7 @@ class Projects::GroupLinksController < Projects::ApplicationController flash[:alert] = 'Please select a group.' end - redirect_to project_settings_members_path(project) + redirect_to project_project_members_path(project) end def update @@ -36,7 +36,7 @@ class Projects::GroupLinksController < Projects::ApplicationController respond_to do |format| format.html do - redirect_to project_settings_members_path(project), status: 302 + redirect_to project_project_members_path(project), status: 302 end format.js { head :ok } end diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index a80562e77ce..c4723c72136 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -45,6 +45,7 @@ class Projects::MilestonesController < Projects::ApplicationController end def show + @project_namespace = @project.namespace.becomes(Namespace) end def create diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 57a6686f66c..f8ff7413b53 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -6,8 +6,23 @@ class Projects::ProjectMembersController < Projects::ApplicationController before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access] def index - sort = params[:sort].presence || sort_value_name - redirect_to project_settings_members_path(@project, sort: sort) + @sort = params[:sort].presence || sort_value_name + @group_links = @project.project_group_links + + @skip_groups = @group_links.pluck(:group_id) + @skip_groups << @project.namespace_id unless @project.personal? + @skip_groups += @project.group.ancestors.pluck(:id) if @project.group + + @project_members = MembersFinder.new(@project, current_user).execute + + if params[:search].present? + @project_members = @project_members.joins(:user).merge(User.search(params[:search])) + @group_links = @group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id)) + end + + @project_members = @project_members.sort(@sort).page(params[:page]) + @requesters = AccessRequestsFinder.new(@project).execute(current_user) + @project_member = @project.project_members.new end def update @@ -19,7 +34,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController end def resend_invite - redirect_path = project_settings_members_path(@project) + redirect_path = project_project_members_path(@project) @project_member = @project.project_members.find(params[:id]) @@ -42,7 +57,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController return render_404 end - redirect_to(project_settings_members_path(project), + redirect_to(project_project_members_path(project), notice: notice) end diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb index 24fe78bc1bd..ea7ceb3eaa5 100644 --- a/app/controllers/projects/settings/ci_cd_controller.rb +++ b/app/controllers/projects/settings/ci_cd_controller.rb @@ -21,7 +21,10 @@ module Projects end def define_secret_variables - @variable = Ci::Variable.new + @variable = Ci::Variable.new(project: project) + .present(current_user: current_user) + @variables = project.variables.order_key_asc + .map { |variable| variable.present(current_user: current_user) } end def define_triggers_variables diff --git a/app/controllers/projects/settings/members_controller.rb b/app/controllers/projects/settings/members_controller.rb deleted file mode 100644 index 54f9dceddef..00000000000 --- a/app/controllers/projects/settings/members_controller.rb +++ /dev/null @@ -1,27 +0,0 @@ -module Projects - module Settings - class MembersController < Projects::ApplicationController - include SortingHelper - - def show - @sort = params[:sort].presence || sort_value_name - @group_links = @project.project_group_links - - @skip_groups = @group_links.pluck(:group_id) - @skip_groups << @project.namespace_id unless @project.personal? - @skip_groups += @project.group.ancestors.pluck(:id) if @project.group - - @project_members = MembersFinder.new(@project, current_user).execute - - if params[:search].present? - @project_members = @project_members.joins(:user).merge(User.search(params[:search])) - @group_links = @group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id)) - end - - @project_members = @project_members.sort(@sort).page(params[:page]) - @requesters = AccessRequestsFinder.new(@project).execute(current_user) - @project_member = @project.project_members.new - end - end - end -end diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb index 326d31ecec2..6a825137564 100644 --- a/app/controllers/projects/variables_controller.rb +++ b/app/controllers/projects/variables_controller.rb @@ -1,4 +1,5 @@ class Projects::VariablesController < Projects::ApplicationController + before_action :variable, only: [:show, :update, :destroy] before_action :authorize_admin_build! layout 'project_settings' @@ -8,37 +9,39 @@ class Projects::VariablesController < Projects::ApplicationController end def show - @variable = @project.variables.find(params[:id]) end def update - @variable = @project.variables.find(params[:id]) - - if @variable.update_attributes(variable_params) - redirect_to project_variables_path(project), notice: 'Variable was successfully updated.' + if variable.update(variable_params) + redirect_to project_variables_path(project), + notice: 'Variable was successfully updated.' else - render action: "show" + render "show" end end def create - @variable = @project.variables.new(variable_params) + @variable = project.variables.create(variable_params) + .present(current_user: current_user) - if @variable.save - flash[:notice] = 'Variables were successfully updated.' - redirect_to project_settings_ci_cd_path(project) + if @variable.persisted? + redirect_to project_settings_ci_cd_path(project), + notice: 'Variable was successfully created.' else render "show" end end def destroy - @key = @project.variables.find(params[:id]) - @key.destroy - - redirect_to project_settings_ci_cd_path(project), - status: 302, - notice: 'Variable was successfully removed.' + if variable.destroy + redirect_to project_settings_ci_cd_path(project), + status: 302, + notice: 'Variable was successfully removed.' + else + redirect_to project_settings_ci_cd_path(project), + status: 302, + notice: 'Failed to remove the variable.' + end end private @@ -50,4 +53,8 @@ class Projects::VariablesController < Projects::ApplicationController def variable_params_attributes %i[id key value protected _destroy] end + + def variable + @variable ||= project.variables.find(params[:id]).present(current_user: current_user) + end end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index b5f4bbe97dc..2e36d0fdb5a 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -97,7 +97,7 @@ module GitlabRoutingHelper ## Members def project_members_url(project, *args) - project_project_members_url(project) + project_project_members_url(project, *args) end def project_member_path(project_member, *args) diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index e589ed4e56d..b769462abc2 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -23,7 +23,6 @@ module NavHelper def nav_header_class class_name = '' class_name << " with-horizontal-nav" if defined?(nav) && nav - class_name << " with-peek" if peek_enabled? class_name end diff --git a/app/helpers/performance_bar_helper.rb b/app/helpers/performance_bar_helper.rb new file mode 100644 index 00000000000..d24efe37f5f --- /dev/null +++ b/app/helpers/performance_bar_helper.rb @@ -0,0 +1,7 @@ +module PerformanceBarHelper + # This is a hack since using `alias_method :performance_bar_enabled?, :peek_enabled?` + # in WithPerformanceBar breaks tests (but works in the browser). + def performance_bar_enabled? + peek_enabled? + end +end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 5022b291f7f..25969adb649 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -267,15 +267,15 @@ module ProjectsHelper def tab_ability_map { - environments: :read_environment, - milestones: :read_milestone, - snippets: :read_project_snippet, - settings: :admin_project, - builds: :read_build, - labels: :read_label, - issues: :read_issue, - team: :read_project_member, - wiki: :read_wiki + environments: :read_environment, + milestones: :read_milestone, + snippets: :read_project_snippet, + settings: :admin_project, + builds: :read_build, + labels: :read_label, + issues: :read_issue, + project_members: :read_project_member, + wiki: :read_wiki } end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 8c44f4b0934..fd7ab59ce64 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -75,7 +75,7 @@ module SearchHelper { category: "Current Project", label: "Merge Requests", url: project_merge_requests_path(@project) }, { category: "Current Project", label: "Milestones", url: project_milestones_path(@project) }, { category: "Current Project", label: "Snippets", url: project_snippets_path(@project) }, - { category: "Current Project", label: "Members", url: project_settings_members_path(@project) }, + { category: "Current Project", label: "Members", url: project_project_members_path(@project) }, { category: "Current Project", label: "Wiki", url: project_wikis_path(@project) } ] else diff --git a/app/models/blob_viewer/readme.rb b/app/models/blob_viewer/readme.rb index 75c373a03bb..4604a9934a0 100644 --- a/app/models/blob_viewer/readme.rb +++ b/app/models/blob_viewer/readme.rb @@ -10,5 +10,11 @@ module BlobViewer def visible_to?(current_user) can?(current_user, :read_wiki, project) end + + def render_error + return if project.has_external_wiki? || (project.wiki_enabled? && project.wiki.has_home_page?) + + :no_wiki + end end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 48586ba8910..25e75a19f37 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -200,6 +200,7 @@ module Ci variables += project.deployment_variables if has_environment? variables += yaml_variables variables += user_variables + variables += project.group.secret_variables_for(ref, project).map(&:to_runner_variable) if project.group variables += secret_variables(environment: environment) variables += trigger_request.user_variables if trigger_request variables += persisted_environment_variables if environment @@ -218,7 +219,7 @@ module Ci .reorder(iid: :desc) merge_requests.find do |merge_request| - merge_request.commits_sha.include?(pipeline.sha) + merge_request.commit_shas.include?(pipeline.sha) end end end diff --git a/app/models/ci/group_variable.rb b/app/models/ci/group_variable.rb new file mode 100644 index 00000000000..f64bc245a67 --- /dev/null +++ b/app/models/ci/group_variable.rb @@ -0,0 +1,13 @@ +module Ci + class GroupVariable < ActiveRecord::Base + extend Ci::Model + include HasVariable + include Presentable + + belongs_to :group + + validates :key, uniqueness: { scope: :group_id } + + scope :unprotected, -> { where(protected: false) } + end +end diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index 0b8d0ff881a..cf0fe04ddaf 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -2,6 +2,7 @@ module Ci class Variable < ActiveRecord::Base extend Ci::Model include HasVariable + include Presentable belongs_to :project diff --git a/app/models/commit.rb b/app/models/commit.rb index 20206d57c4c..c7f62617c4c 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -138,7 +138,7 @@ class Commit safe_message.split("\n", 2)[1].try(:chomp) end - + def description? description.present? end diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb index c28974a3cdf..67ecf470f7e 100644 --- a/app/models/concerns/sha_attribute.rb +++ b/app/models/concerns/sha_attribute.rb @@ -3,6 +3,8 @@ module ShaAttribute module ClassMethods def sha_attribute(name) + return unless table_exists? + column = columns.find { |c| c.name == name.to_s } # In case the table doesn't exist we won't be able to find the column, diff --git a/app/models/group.rb b/app/models/group.rb index b93fce6100d..f29e642ac91 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -22,6 +22,7 @@ class Group < Namespace has_many :shared_projects, through: :project_group_links, source: :project has_many :notification_settings, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent has_many :labels, class_name: 'GroupLabel' + has_many :variables, class_name: 'Ci::GroupVariable' validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } validate :visibility_level_allowed_by_projects @@ -248,6 +249,14 @@ class Group < Namespace } end + def secret_variables_for(ref, project) + list_of_ids = [self] + ancestors + variables = Ci::GroupVariable.where(group: list_of_ids) + variables = variables.unprotected unless project.protected_for?(ref) + variables = variables.group_by(&:group_id) + list_of_ids.reverse.map { |group| variables[group.id] }.compact.flatten + end + protected def update_two_factor_requirement diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 2752181ed79..6ea774470af 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -31,7 +31,7 @@ class MergeRequest < ActiveRecord::Base after_create :ensure_merge_request_diff, unless: :importing? after_update :reload_diff_if_branch_changed - delegate :commits, :real_size, :commits_sha, :commits_count, + delegate :commits, :real_size, :commit_shas, :commits_count, to: :merge_request_diff, prefix: nil # When this attribute is true some MR validation is ignored @@ -518,7 +518,7 @@ class MergeRequest < ActiveRecord::Base def related_notes # Fetch comments only from last 100 commits commits_for_notes_limit = 100 - commit_ids = commits.last(commits_for_notes_limit).map(&:id) + commit_ids = commit_shas.take(commits_for_notes_limit) Note.where( "(project_id = :target_project_id AND noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR" + @@ -841,15 +841,18 @@ class MergeRequest < ActiveRecord::Base return Ci::Pipeline.none unless source_project @all_pipelines ||= source_project.pipelines - .where(sha: all_commits_sha, ref: source_branch) + .where(sha: all_commit_shas, ref: source_branch) .order(id: :desc) end # Note that this could also return SHA from now dangling commits # - def all_commits_sha + def all_commit_shas if persisted? - merge_request_diffs.flat_map(&:commits_sha).uniq + column_shas = MergeRequestDiffCommit.where(merge_request_diff: merge_request_diffs).pluck('DISTINCT(sha)') + serialised_shas = merge_request_diffs.where.not(st_commits: nil).flat_map(&:commit_shas) + + (column_shas + serialised_shas).uniq elsif compare_commits compare_commits.to_a.reverse.map(&:id) else diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 0cbf58a2325..4b141945ab4 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -11,6 +11,7 @@ class MergeRequestDiff < ActiveRecord::Base belongs_to :merge_request has_many :merge_request_diff_files, -> { order(:merge_request_diff_id, :relative_order) } + has_many :merge_request_diff_commits, -> { order(:merge_request_diff_id, :relative_order) } serialize :st_commits # rubocop:disable Cop/ActiveRecordSerialize serialize :st_diffs # rubocop:disable Cop/ActiveRecordSerialize @@ -47,14 +48,13 @@ class MergeRequestDiff < ActiveRecord::Base # Collect information about commits and diff from repository # and save it to the database as serialized data def save_git_content - ensure_commits_sha + ensure_commit_shas save_commits - reload_commits save_diffs keep_around_commits end - def ensure_commits_sha + def ensure_commit_shas merge_request.fetch_ref self.start_commit_sha ||= merge_request.target_branch_sha self.head_commit_sha ||= merge_request.source_branch_sha @@ -66,7 +66,7 @@ class MergeRequestDiff < ActiveRecord::Base # created before version 8.4 that does not store head_commit_sha in separate db field. def head_commit_sha if persisted? && super.nil? - last_commit.try(:sha) + last_commit_sha else super end @@ -97,16 +97,11 @@ class MergeRequestDiff < ActiveRecord::Base end def commits - @commits ||= load_commits(st_commits) + @commits ||= load_commits end - def reload_commits - @commits = nil - commits - end - - def last_commit - commits.first + def last_commit_sha + commit_shas.first end def first_commit @@ -131,8 +126,12 @@ class MergeRequestDiff < ActiveRecord::Base project.commit(head_commit_sha) end - def commits_sha - st_commits.map { |commit| commit[:id] } + def commit_shas + if st_commits.present? + st_commits.map { |commit| commit[:id] } + else + merge_request_diff_commits.map(&:sha) + end end def diff_refs=(new_diff_refs) @@ -207,7 +206,11 @@ class MergeRequestDiff < ActiveRecord::Base end def commits_count - st_commits.count + if st_commits.present? + st_commits.size + else + merge_request_diff_commits.size + end end def utf8_st_diffs @@ -231,29 +234,6 @@ class MergeRequestDiff < ActiveRecord::Base raw.any? { |element| VALID_CLASSES.include?(element.class) } end - def dump_commits(commits) - commits.map(&:to_hash) - end - - def load_commits(array) - array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash), merge_request.source_project) } - end - - # Load all commits related to current merge request diff from repo - # and save it as array of hashes in st_commits db field - def save_commits - new_attributes = {} - - commits = compare.commits - - if commits.present? - commits = Commit.decorate(commits, merge_request.source_project).reverse - new_attributes[:st_commits] = dump_commits(commits) - end - - update_columns_serialized(new_attributes) - end - def create_merge_request_diff_files(diffs) rows = diffs.map.with_index do |diff, index| diff.to_hash.merge( @@ -294,12 +274,18 @@ class MergeRequestDiff < ActiveRecord::Base end end - # Load diffs between branches related to current merge request diff from repo - # and save it as array of hashes in st_diffs db field + def load_commits + commits = st_commits.presence || merge_request_diff_commits + + commits.map do |commit| + Commit.new(Gitlab::Git::Commit.new(commit.to_hash), merge_request.source_project) + end + end + def save_diffs new_attributes = {} - if commits.size.zero? + if compare.commits.size.zero? new_attributes[:state] = :empty else diff_collection = compare.diffs(Commit.max_diff_options) @@ -319,7 +305,13 @@ class MergeRequestDiff < ActiveRecord::Base new_attributes[:state] = :overflow if diff_collection.overflow? end - update_columns_serialized(new_attributes) + update(new_attributes) + end + + def save_commits + MergeRequestDiffCommit.create_bulk(self.id, compare.commits.reverse) + + merge_request_diff_commits.reload end def repository @@ -332,29 +324,6 @@ class MergeRequestDiff < ActiveRecord::Base project.merge_base_commit(head_commit_sha, start_commit_sha).try(:sha) end - # - # #save or #update_attributes providing changes on serialized attributes do a lot of - # serialization and deserialization calls resulting in bad performance. - # Using #update_columns solves the problem with just one YAML.dump per serialized attribute that we provide. - # As a tradeoff we need to reload the current instance to properly manage time objects on those serialized - # attributes. So to keep the same behaviour as the attribute assignment we reload the instance. - # The difference is in the usage of - # #write_attribute= (#update_attributes) and #raw_write_attribute= (#update_columns) - # - # Ex: - # - # new_attributes[:st_commits].first.slice(:committed_date) - # => {:committed_date=>2014-02-27 11:01:38 +0200} - # YAML.load(YAML.dump(new_attributes[:st_commits].first.slice(:committed_date))) - # => {:committed_date=>2014-02-27 10:01:38 +0100} - # - def update_columns_serialized(new_attributes) - return unless new_attributes.any? - - update_columns(new_attributes.merge(updated_at: current_time_from_proper_timezone)) - reload - end - def keep_around_commits [repository, merge_request.source_project.repository].each do |repo| repo.keep_around(start_commit_sha) diff --git a/app/models/merge_request_diff_commit.rb b/app/models/merge_request_diff_commit.rb new file mode 100644 index 00000000000..cafdbe11849 --- /dev/null +++ b/app/models/merge_request_diff_commit.rb @@ -0,0 +1,38 @@ +class MergeRequestDiffCommit < ActiveRecord::Base + include ShaAttribute + + belongs_to :merge_request_diff + + sha_attribute :sha + alias_attribute :id, :sha + + def self.create_bulk(merge_request_diff_id, commits) + sha_attribute = Gitlab::Database::ShaAttribute.new + + rows = commits.map.with_index do |commit, index| + # See #parent_ids. + commit_hash = commit.to_hash.except(:parent_ids) + sha = commit_hash.delete(:id) + + commit_hash.merge( + merge_request_diff_id: merge_request_diff_id, + relative_order: index, + sha: sha_attribute.type_cast_for_database(sha) + ) + end + + Gitlab::Database.bulk_insert(self.table_name, rows) + end + + def to_hash + Gitlab::Git::Commit::SERIALIZE_KEYS.each_with_object({}) do |key, hash| + hash[key] = public_send(key) + end + end + + # We don't save these, because they would need a table or a serialised + # field. They aren't used anywhere, so just pretend the commit has no parents. + def parent_ids + [] + end +end diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index beaadbbd1ab..dfca0031af8 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -63,6 +63,10 @@ class ProjectWiki !!repository.exists? end + def has_home_page? + !!find_page('home') + end + # Returns an Array of Gitlab WikiPage instances or an # empty Array if this Wiki has no pages. def pages diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index dcb37416ca3..6defab75fce 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -31,6 +31,8 @@ class GroupPolicy < BasePolicy rule { master }.policy do enable :create_projects enable :admin_milestones + enable :admin_pipeline + enable :admin_build end rule { owner }.policy do diff --git a/app/presenters/ci/group_variable_presenter.rb b/app/presenters/ci/group_variable_presenter.rb new file mode 100644 index 00000000000..81fea106a5c --- /dev/null +++ b/app/presenters/ci/group_variable_presenter.rb @@ -0,0 +1,25 @@ +module Ci + class GroupVariablePresenter < Gitlab::View::Presenter::Delegated + presents :variable + + def placeholder + 'GROUP_VARIABLE' + end + + def form_path + if variable.persisted? + group_variable_path(group, variable) + else + group_variables_path(group) + end + end + + def edit_path + group_variable_path(group, variable) + end + + def delete_path + group_variable_path(group, variable) + end + end +end diff --git a/app/presenters/ci/variable_presenter.rb b/app/presenters/ci/variable_presenter.rb new file mode 100644 index 00000000000..5d7998393a6 --- /dev/null +++ b/app/presenters/ci/variable_presenter.rb @@ -0,0 +1,25 @@ +module Ci + class VariablePresenter < Gitlab::View::Presenter::Delegated + presents :variable + + def placeholder + 'PROJECT_VARIABLE' + end + + def form_path + if variable.persisted? + project_variable_path(project, variable) + else + project_variables_path(project) + end + end + + def edit_path + project_variable_path(project, variable) + end + + def delete_path + project_variable_path(project, variable) + end + end +end diff --git a/app/services/boards/create_service.rb b/app/services/boards/create_service.rb index 68f6a8619e5..9eedb9e65a2 100644 --- a/app/services/boards/create_service.rb +++ b/app/services/boards/create_service.rb @@ -1,19 +1,22 @@ module Boards class CreateService < BaseService def execute - if project.boards.empty? - create_board! - else - project.boards.first - end + create_board! if can_create_board? end private + def can_create_board? + project.boards.size == 0 + end + def create_board! - board = project.boards.create - board.lists.create(list_type: :backlog) - board.lists.create(list_type: :closed) + board = project.boards.create(params) + + if board.persisted? + board.lists.create(list_type: :backlog) + board.lists.create(list_type: :closed) + end board end diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index e0e7c43f802..c5f959a1874 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -68,7 +68,7 @@ module MergeRequests if merge_request.source_branch == @branch_name || force_push? merge_request.reload_diff(current_user) else - mr_commit_ids = merge_request.commits_sha + mr_commit_ids = merge_request.commit_shas push_commit_ids = @commits.map(&:id) matches = mr_commit_ids & push_commit_ids merge_request.reload_diff(current_user) if matches.any? @@ -128,7 +128,7 @@ module MergeRequests return unless @commits.present? merge_requests_for_source_branch.each do |merge_request| - mr_commit_ids = Set.new(merge_request.commits_sha) + mr_commit_ids = Set.new(merge_request.commit_shas) new_commits, existing_commits = @commits.partition do |commit| mr_commit_ids.include?(commit.id) @@ -144,7 +144,7 @@ module MergeRequests return unless @commits.present? merge_requests_for_source_branch.each do |merge_request| - commit_shas = merge_request.commits_sha + commit_shas = merge_request.commit_shas wip_commit = @commits.detect do |commit| commit.work_in_progress? && commit_shas.include?(commit.sha) diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb index e4dfe87e614..6f82159e6c7 100644 --- a/app/services/quick_actions/interpret_service.rb +++ b/app/services/quick_actions/interpret_service.rb @@ -146,32 +146,6 @@ module QuickActions end end - desc do - "Change assignee#{'(s)' if issuable.allows_multiple_assignees?}" - end - explanation do |users| - users = issuable.allows_multiple_assignees? ? users : users.take(1) - "Change #{'assignee'.pluralize(users.size)} to #{users.map(&:to_reference).to_sentence}." - end - params do - issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user' - end - condition do - issuable.persisted? && - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - parse_params do |assignee_param| - extract_users(assignee_param) - end - command :reassign do |users| - @updates[:assignee_ids] = - if issuable.allows_multiple_assignees? - users.map(&:id) - else - [users.last.id] - end - end - desc 'Set milestone' explanation do |milestone| "Sets the milestone to #{milestone.to_reference}." if milestone diff --git a/app/views/projects/variables/_content.html.haml b/app/views/ci/variables/_content.html.haml index 98f618ca3b8..98f618ca3b8 100644 --- a/app/views/projects/variables/_content.html.haml +++ b/app/views/ci/variables/_content.html.haml diff --git a/app/views/projects/variables/_form.html.haml b/app/views/ci/variables/_form.html.haml index 0a70a301cb4..eebd0955c80 100644 --- a/app/views/projects/variables/_form.html.haml +++ b/app/views/ci/variables/_form.html.haml @@ -1,12 +1,12 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @variable] do |f| += form_for @variable, as: :variable, url: @variable.form_path do |f| = form_errors(@variable) .form-group = f.label :key, "Key", class: "label-light" - = f.text_field :key, class: "form-control", placeholder: "PROJECT_VARIABLE", required: true + = f.text_field :key, class: "form-control", placeholder: @variable.placeholder, required: true .form-group = f.label :value, "Value", class: "label-light" - = f.text_area :value, class: "form-control", placeholder: "PROJECT_VARIABLE" + = f.text_area :value, class: "form-control", placeholder: @variable.placeholder .form-group .checkbox = f.label :protected do diff --git a/app/views/projects/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml index 5e6786f6698..007c2344b5a 100644 --- a/app/views/projects/variables/_index.html.haml +++ b/app/views/ci/variables/_index.html.haml @@ -1,16 +1,16 @@ .row.prepend-top-default.append-bottom-default .col-lg-4 - = render "projects/variables/content" + = render "ci/variables/content" .col-lg-8 %h5.prepend-top-0 Add a variable - = render "projects/variables/form", btn_text: "Add new variable" + = render "ci/variables/form", btn_text: "Add new variable" %hr %h5.prepend-top-0 - Your variables (#{@project.variables.size}) - - if @project.variables.empty? + Your variables (#{@variables.size}) + - if @variables.empty? %p.settings-message.text-center.append-bottom-0 No variables found, add one with the form above. - else - = render "projects/variables/table" + = render "ci/variables/table" %button.btn.btn-info.js-btn-toggle-reveal-values{ "data-status" => 'hidden' } Reveal Values diff --git a/app/views/ci/variables/_show.html.haml b/app/views/ci/variables/_show.html.haml new file mode 100644 index 00000000000..2bfb290629d --- /dev/null +++ b/app/views/ci/variables/_show.html.haml @@ -0,0 +1,9 @@ +- page_title "Variables" + +.row.prepend-top-default.append-bottom-default + .col-lg-3 + = render "ci/variables/content" + .col-lg-9 + %h5.prepend-top-0 + Update variable + = render "ci/variables/form", btn_text: "Save variable" diff --git a/app/views/projects/variables/_table.html.haml b/app/views/ci/variables/_table.html.haml index 4ce6a828812..71a0b56c4f4 100644 --- a/app/views/projects/variables/_table.html.haml +++ b/app/views/ci/variables/_table.html.haml @@ -11,18 +11,18 @@ %th Protected %th %tbody - - @project.variables.order_key_asc.each do |variable| + - @variables.each do |variable| - if variable.id? %tr %td.variable-key= variable.key %td.variable-value{ "data-value" => variable.value }****** %td.variable-protected= Gitlab::Utils.boolean_to_yes_no(variable.protected) %td.variable-menu - = link_to project_variable_path(@project, variable), class: "btn btn-transparent btn-variable-edit" do + = link_to variable.edit_path, class: "btn btn-transparent btn-variable-edit" do %span.sr-only Update = icon("pencil") - = link_to project_variable_path(@project, variable), class: "btn btn-transparent btn-variable-delete", method: :delete, data: { confirm: "Are you sure?" } do + = link_to variable.delete_path, class: "btn btn-transparent btn-variable-delete", method: :delete, data: { confirm: "Are you sure?" } do %span.sr-only Remove = icon("trash") diff --git a/app/views/groups/_settings_head.html.haml b/app/views/groups/_settings_head.html.haml index 2454e7355a7..623d233a46a 100644 --- a/app/views/groups/_settings_head.html.haml +++ b/app/views/groups/_settings_head.html.haml @@ -12,3 +12,8 @@ = link_to projects_group_path(@group), title: 'Projects' do %span Projects + + = nav_link(controller: :ci_cd) do + = link_to group_settings_ci_cd_path(@group), title: 'Pipelines' do + %span + Pipelines diff --git a/app/views/groups/settings/ci_cd/show.html.haml b/app/views/groups/settings/ci_cd/show.html.haml new file mode 100644 index 00000000000..bf36baf48ab --- /dev/null +++ b/app/views/groups/settings/ci_cd/show.html.haml @@ -0,0 +1,4 @@ +- page_title "Pipelines" += render "groups/settings_head" + += render 'ci/variables/index' diff --git a/app/views/groups/variables/show.html.haml b/app/views/groups/variables/show.html.haml new file mode 100644 index 00000000000..df533952b76 --- /dev/null +++ b/app/views/groups/variables/show.html.haml @@ -0,0 +1 @@ += render 'ci/variables/show' diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 331d1181220..56e628a2b74 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -27,10 +27,11 @@ %td.shortcut .key f %td Focus Filter - %tr - %td.shortcut - .key p b - %td Show/hide the Performance Bar + - if performance_bar_enabled? + %tr + %td.shortcut + .key p b + %td Show/hide the Performance Bar %tr %td.shortcut .key ? diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index abb6dc2e9f3..6ad22958df3 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -30,7 +30,7 @@ = stylesheet_link_tag "application", media: "all" = stylesheet_link_tag "print", media: "print" = stylesheet_link_tag "test", media: "all" if Rails.env.test? - = stylesheet_link_tag 'peek' if peek_enabled? + = stylesheet_link_tag 'performance_bar' if performance_bar_enabled? - if show_new_nav? = stylesheet_link_tag "new_nav", media: "all" @@ -44,7 +44,7 @@ = webpack_bundle_tag "main" = webpack_bundle_tag "raven" if current_application_settings.clientside_sentry_enabled = webpack_bundle_tag "test" if Rails.env.test? - = webpack_bundle_tag 'peek' if peek_enabled? + = webpack_bundle_tag 'performance_bar' if performance_bar_enabled? - if content_for?(:page_specific_javascripts) = yield :page_specific_javascripts diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index d879df8fc82..81f83b74826 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -3,7 +3,6 @@ = render "layouts/head" %body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } } = render "layouts/init_auto_complete" if @gfm_form - = render 'peek/bar' - if show_new_nav? = render "layouts/header/new" - else @@ -11,3 +10,5 @@ = render 'layouts/page', sidebar: sidebar, nav: nav = yield :scripts_body + + = render 'peek/bar' diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 14deb46eee3..fb90bb4b472 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -57,16 +57,17 @@ %span Snippets + - if project_nav_tab? :project_members + = nav_link(controller: :project_members) do + = link_to project_project_members_path(@project), title: 'Members', class: 'shortcuts-members' do + %span + Members + - if project_nav_tab? :settings = nav_link(path: %w[projects#edit members#show integrations#show services#edit repository#show ci_cd#show pages#show]) do = link_to edit_project_path(@project), title: 'Settings', class: 'shortcuts-tree' do %span Settings - - else - = nav_link(path: %w[members#show]) do - = link_to project_settings_members_path(@project), title: 'Settings', class: 'shortcuts-tree' do - %span - Settings -# Shortcut to Project > Activity %li.hidden diff --git a/app/views/peek/views/_rblineprof.html.haml b/app/views/peek/views/_rblineprof.html.haml new file mode 100644 index 00000000000..6c037930ca9 --- /dev/null +++ b/app/views/peek/views/_rblineprof.html.haml @@ -0,0 +1,7 @@ +Profile: + += link_to 'all', url_for(lineprofiler: 'true'), class: 'js-toggle-modal-peek-line-profile' +\/ += link_to 'app & lib', url_for(lineprofiler: 'app'), class: 'js-toggle-modal-peek-line-profile' +\/ += link_to 'views', url_for(lineprofiler: 'views'), class: 'js-toggle-modal-peek-line-profile' diff --git a/app/views/peek/views/_sql.html.haml b/app/views/peek/views/_sql.html.haml index 16fc010f66f..dd8b524064f 100644 --- a/app/views/peek/views/_sql.html.haml +++ b/app/views/peek/views/_sql.html.haml @@ -1,13 +1,13 @@ %strong - %a#peek-show-queries{ href: '#' } + %a.js-toggle-modal-peek-sql %span{ data: { defer_to: "#{view.defer_key}-duration" } }... \/ %span{ data: { defer_to: "#{view.defer_key}-calls" } }... #modal-peek-pg-queries.modal{ tabindex: -1 } - .modal-dialog - #modal-peek-pg-queries-content.modal-content + .modal-dialog.modal-full + .modal-content .modal-header - %a.close{ href: "#", "data-dismiss" => "modal" } × + %button.close.btn.btn-link.btn-sm{ type: 'button', data: { dismiss: 'modal' } } X %h4 SQL queries .modal-body{ data: { defer_to: "#{view.defer_key}-queries" } }... diff --git a/app/views/projects/blob/viewers/_readme.html.haml b/app/views/projects/blob/viewers/_readme.html.haml index 507f44d4745..d8492abc638 100644 --- a/app/views/projects/blob/viewers/_readme.html.haml +++ b/app/views/projects/blob/viewers/_readme.html.haml @@ -1,4 +1,4 @@ = icon('info-circle fw') = succeed '.' do To learn more about this project, read - = link_to "the wiki", project_wikis_path(viewer.project) + = link_to "the wiki", get_project_wiki_path(viewer.project) diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml deleted file mode 100644 index c7996077bc7..00000000000 --- a/app/views/projects/project_members/_group_members.html.haml +++ /dev/null @@ -1,18 +0,0 @@ -.panel.panel-default - .panel-heading - Group members with access to - %strong= @group.name - %span.badge= members.size - - if can?(current_user, :admin_group_member, @group) - .controls - = link_to 'Manage group members', - group_group_members_path(@group), - class: 'btn' - %ul.content-list - = render partial: 'shared/members/member', - collection: members.limit(20), - as: :member, - locals: { show_controls: false } - - if members.size > 20 - %li - and #{members.count - 20} more. For full list visit #{link_to 'group members page', group_group_members_path(@group)} diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml deleted file mode 100644 index 7902ddb1ae9..00000000000 --- a/app/views/projects/project_members/_shared_group_members.html.haml +++ /dev/null @@ -1,24 +0,0 @@ -- @project_group_links.each do |group_links| - - shared_group = group_links.group - - shared_group_members = shared_group.members - - shared_group_users_count = shared_group_members.size - .panel.panel-default - .panel-heading - Shared with - %strong= shared_group.name - group, members with - %strong= group_links.human_access - role (#{shared_group_users_count}) - - if can?(current_user, :admin_group, shared_group) - .panel-head-actions - = link_to group_group_members_path(shared_group), class: 'btn btn-sm' do - %i.fa.fa-pencil-square-o - Edit group members - %ul.content-list - = render partial: 'shared/members/member', - collection: shared_group_members.order(access_level: :desc).limit(20), - as: :member, - locals: { show_controls: false, show_roles: false } - - if shared_group_users_count > 20 - %li - and #{shared_group_users_count - 20} more. For full list visit #{link_to 'group members page', group_group_members_path(shared_group)} diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 7ed467c8841..1f18490594b 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -5,7 +5,7 @@ %strong #{@project.name} %span.badge= @project_members.total_count - = form_tag project_settings_members_path(@project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do + = form_tag project_project_members_path(@project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do .form-group = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } diff --git a/app/views/projects/project_members/import.html.haml b/app/views/projects/project_members/import.html.haml index 03b33eb2da7..f6ca8d5a921 100644 --- a/app/views/projects/project_members/import.html.haml +++ b/app/views/projects/project_members/import.html.haml @@ -12,4 +12,4 @@ .form-actions = button_tag 'Import project members', class: "btn btn-create" - = link_to "Cancel", project_settings_members_path(@project), class: "btn btn-cancel" + = link_to "Cancel", project_project_members_path(@project), class: "btn btn-cancel" diff --git a/app/views/projects/project_members/_index.html.haml b/app/views/projects/project_members/index.html.haml index fa99610c0be..25153fd0b6f 100644 --- a/app/views/projects/project_members/_index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -1,6 +1,8 @@ +- page_title "Members" + .row.prepend-top-default - .col-lg-4.settings-sidebar - %h4.prepend-top-0 + .col-lg-12 + %h4 Project members - if can?(current_user, :admin_project_member, @project) %p @@ -13,7 +15,6 @@ %i Masters or %i Owners - .col-lg-8 .light - if can?(current_user, :admin_project_member, @project) %ul.nav-links.project-member-tabs{ role: 'tablist' } diff --git a/app/views/projects/settings/_head.html.haml b/app/views/projects/settings/_head.html.haml index b5773acb5a4..15ba09b10ba 100644 --- a/app/views/projects/settings/_head.html.haml +++ b/app/views/projects/settings/_head.html.haml @@ -9,10 +9,6 @@ = link_to edit_project_path(@project), title: 'General' do %span General - = nav_link(controller: :members) do - = link_to project_settings_members_path(@project), title: 'Members' do - %span - Members - if can_edit = nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do = link_to project_settings_integrations_path(@project), title: 'Integrations' do diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index 712799dbadf..8ca15861025 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -8,6 +8,6 @@ = render "projects/settings/head" = render 'projects/runners/index' -= render 'projects/variables/index' += render 'ci/variables/index' = render 'projects/triggers/index' = render 'projects/pipelines_settings/show' diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index ad389e17221..27ccbb67bfc 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -6,7 +6,6 @@ = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") = render "projects/commits/head" -= render 'projects/last_push' - -%div{ class: container_class } +%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } + = render 'projects/last_push' = render 'projects/files', commit: @last_commit, project: @project, ref: @ref diff --git a/app/views/projects/variables/show.html.haml b/app/views/projects/variables/show.html.haml index 297a53ca98c..df533952b76 100644 --- a/app/views/projects/variables/show.html.haml +++ b/app/views/projects/variables/show.html.haml @@ -1,9 +1 @@ -- page_title "Variables" - -.row.prepend-top-default.append-bottom-default - .col-lg-3 - = render "content" - .col-lg-9 - %h5.prepend-top-0 - Update variable - = render "form", btn_text: "Save variable" += render 'ci/variables/show' diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml index a7c67ac9980..3739f4c221d 100644 --- a/app/views/shared/milestones/_issuable.html.haml +++ b/app/views/shared/milestones/_issuable.html.haml @@ -1,14 +1,13 @@ -# @project is present when viewing Project's milestone - project = @project || issuable.project - namespace = @project_namespace || project.namespace.becomes(Namespace) +- labels = issuable.labels - assignees = issuable.assignees -- issuable_type = issuable.class.table_name - base_url_args = [namespace, project] -- issuable_type_args = base_url_args + [issuable_type] +- issuable_type_args = base_url_args + [issuable.class.table_name] - issuable_url_args = base_url_args + [issuable] -- can_update = can?(current_user, :"update_#{issuable.to_ability_name}", issuable) -%li{ id: dom_id(issuable, 'sortable'), class: "issuable-row #{'is-disabled' unless can_update}", 'data-iid' => issuable.iid, 'data-id' => issuable.id, 'data-url' => polymorphic_path(issuable_url_args) } +%li.issuable-row %span - if show_project_name %strong #{project.name} · @@ -18,10 +17,10 @@ = confidential_icon(issuable) = link_to issuable.title, issuable_url_args, title: issuable.title .issuable-detail - = link_to [project.namespace.becomes(Namespace), project, issuable] do + = link_to [namespace, project, issuable] do %span.issuable-number= issuable.to_reference - - issuable.labels.each do |label| + - labels.each do |label| = link_to polymorphic_path(issuable_type_args, { milestone_title: @milestone.title, label_name: label.title, state: 'all' }) do - render_colored_label(label) diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml index e2d1695b7c3..b95a4ea674d 100644 --- a/app/views/shared/milestones/_tabs.html.haml +++ b/app/views/shared/milestones/_tabs.html.haml @@ -1,8 +1,10 @@ +- issues_accessible = milestone.is_a?(GlobalMilestone) || can?(current_user, :read_issue, @project) + .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller .fade-left= icon('angle-left') .fade-right= icon('angle-right') %ul.nav-links.scrolling-tabs.js-milestone-tabs - - if milestone.is_a?(GlobalMilestone) || can?(current_user, :read_issue, @project) + - if issues_accessible %li.active = link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do Issues @@ -25,13 +27,14 @@ Labels %span.badge= milestone.labels.count +- issues = milestone.sorted_issues(current_user) - show_project_name = local_assigns.fetch(:show_project_name, false) - show_full_project_name = local_assigns.fetch(:show_full_project_name, false) .tab-content.milestone-content - - if milestone.is_a?(GlobalMilestone) || can?(current_user, :read_issue, @project) + - if issues_accessible .tab-pane.active#tab-issues{ data: { sort_endpoint: (sort_issues_project_milestone_path(@project, @milestone) if @project && current_user) } } - = render 'shared/milestones/issues_tab', issues: milestone.sorted_issues(current_user), show_project_name: show_project_name, show_full_project_name: show_full_project_name + = render 'shared/milestones/issues_tab', issues: issues, show_project_name: show_project_name, show_full_project_name: show_full_project_name .tab-pane#tab-merge-requests -# loaded async = render "shared/milestones/tab_loading" |