diff options
author | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2016-12-14 14:37:05 +0100 |
---|---|---|
committer | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2016-12-14 14:37:05 +0100 |
commit | 7217a19e729534a122c0c4a0c842080cfcbd3330 (patch) | |
tree | 0f8bbe9d3713fb4a439b5634b46a11738946ff9f /app | |
parent | 92b0f54ea222ce4c4437a50683c972bacc1fee06 (diff) | |
parent | 3e90aa1119fc4c77e92e6492f1906f252d90b64e (diff) | |
download | gitlab-ce-7217a19e729534a122c0c4a0c842080cfcbd3330.tar.gz |
Merge branch 'master' into 22604-manual-actions
* master: (151 commits)
Improve build status specs contexts descriptions
Add some missing tests for detailed status methods
Fix wrong error message expectation in API::Commits spec
Remove trailing blank line from Allowable module
Move admin settings spinach feature to rspec
Encode when migrating ProcessCommitWorker jobs
Prevent overflow with vertical scroll when we have space to show content
Make rubocop happy
API: Ability to cherry-pick a commit
Be smarter when finding a sudoed user in API::Helpers
Update manual build icon SVG
Make it possible to mix `Gitlab::Routing` in
Backport hooks on group policies for the EE-specific implementation
Extract abilities checking module from ability model
Extend tests for pipeline detailed status helpers
Add tests for common build detailed status helpers
Add missing tests for build `cancelable?` method
Add tests for detailed build statuses factory
Make it possible to retry build that was canceled
Make build retryable only if complete and executed
...
Conflicts:
app/assets/stylesheets/pages/pipelines.scss
app/models/ci/build.rb
app/models/ci/pipeline.rb
app/models/ci/stage.rb
app/models/commit_status.rb
app/views/admin/runners/show.html.haml
app/views/projects/builds/_header.html.haml
app/views/projects/ci/builds/_build.html.haml
app/views/projects/ci/builds/_build_pipeline.html.haml
app/views/projects/ci/pipelines/_pipeline.html.haml
app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
app/views/projects/pipelines/_info.html.haml
lib/gitlab/ci/status/build/common.rb
lib/gitlab/ci/status/build/play.rb
lib/gitlab/ci/status/build/stop.rb
lib/gitlab/ci/status/core.rb
spec/lib/gitlab/ci/status/pipeline/common_spec.rb
Diffstat (limited to 'app')
104 files changed, 669 insertions, 438 deletions
diff --git a/app/assets/javascripts/commit/file.js b/app/assets/javascripts/commit/file.js index 3f29826fa9b..600bac8834a 100644 --- a/app/assets/javascripts/commit/file.js +++ b/app/assets/javascripts/commit/file.js @@ -3,7 +3,7 @@ this.CommitFile = (function() { function CommitFile(file) { if ($('.image', file).length) { - new ImageFile(file); + new gl.ImageFile(file); } } diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js index 4c2ae595319..fd8910e916f 100644 --- a/app/assets/javascripts/commit/image_file.js +++ b/app/assets/javascripts/commit/image_file.js @@ -1,6 +1,6 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, padded-blocks, max-len */ (function() { - this.ImageFile = (function() { + gl.ImageFile = (function() { var prepareFrames; // Width where images must fits in, for 2-up this gets divided by 2 diff --git a/app/assets/javascripts/copy_to_clipboard.js b/app/assets/javascripts/copy_to_clipboard.js index 1cc34e490c2..efa228a75d9 100644 --- a/app/assets/javascripts/copy_to_clipboard.js +++ b/app/assets/javascripts/copy_to_clipboard.js @@ -6,7 +6,7 @@ var genericError, genericSuccess, showTooltip; genericSuccess = function(e) { - showTooltip(e.trigger, 'Copied!'); + showTooltip(e.trigger, 'Copied'); // Clear the selection and blur the trigger so it loses its border e.clearSelection(); return $(e.trigger).blur(); @@ -31,7 +31,7 @@ var originalTitle = $target.data('original-title'); $target - .attr('title', 'Copied!') + .attr('title', 'Copied') .tooltip('fixTitle') .tooltip('show') .attr('title', originalTitle) diff --git a/app/assets/javascripts/diff.js.es6 b/app/assets/javascripts/diff.js.es6 index ecf9d1de81c..9cf33e62958 100644 --- a/app/assets/javascripts/diff.js.es6 +++ b/app/assets/javascripts/diff.js.es6 @@ -5,8 +5,11 @@ class Diff { constructor() { - $('.files .diff-file').singleFileDiff(); - $('.files .diff-file').filesCommentButton(); + const $diffFile = $('.files .diff-file'); + $diffFile.singleFileDiff(); + $diffFile.filesCommentButton(); + + $diffFile.each((index, file) => new gl.ImageFile(file)); if (this.diffViewType() === 'parallel') { $('.content-wrapper .container-fluid').removeClass('container-limited'); diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index 84faabf938a..1db29dd47fb 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -74,6 +74,8 @@ projectStoppedEnvironmentsPath: environmentsData.projectStoppedEnvironmentsPath, newEnvironmentPath: environmentsData.newEnvironmentPath, helpPagePath: environmentsData.helpPagePath, + commitIconSvg: environmentsData.commitIconSvg, + playIconSvg: environmentsData.playIconSvg, }; }, @@ -227,7 +229,9 @@ :model="model" :toggleRow="toggleRow.bind(model)" :can-create-deployment="canCreateDeploymentParsed" - :can-read-environment="canReadEnvironmentParsed"></tr> + :can-read-environment="canReadEnvironmentParsed" + :play-icon-svg="playIconSvg" + :commit-icon-svg="commitIconSvg"></tr> <tr v-if="model.isOpen && model.children && model.children.length > 0" is="environment-item" @@ -235,7 +239,9 @@ :model="children" :toggleRow="toggleRow.bind(children)" :can-create-deployment="canCreateDeploymentParsed" - :can-read-environment="canReadEnvironmentParsed"> + :can-read-environment="canReadEnvironmentParsed" + :play-icon-svg="playIconSvg" + :commit-icon-svg="commitIconSvg"> </tr> </template> diff --git a/app/assets/javascripts/environments/components/environment_actions.js.es6 b/app/assets/javascripts/environments/components/environment_actions.js.es6 index d149a446e0b..7c743705d51 100644 --- a/app/assets/javascripts/environments/components/environment_actions.js.es6 +++ b/app/assets/javascripts/environments/components/environment_actions.js.es6 @@ -12,38 +12,18 @@ required: false, default: () => [], }, - }, - - /** - * Appends the svg icon that were render in the index page. - * In order to reuse the svg instead of copy and paste in this template - * we need to render it outside this component using =custom_icon partial. - * - * TODO: Remove this when webpack is merged. - * - */ - mounted() { - const playIcon = document.querySelector('.play-icon-svg.hidden svg'); - - const dropdownContainer = this.$el.querySelector('.dropdown-play-icon-container'); - const actionContainers = this.$el.querySelectorAll('.action-play-icon-container'); - // Phantomjs does not have support to iterate a nodelist. - const actionsArray = [].slice.call(actionContainers); - - if (playIcon && actionsArray && dropdownContainer) { - dropdownContainer.appendChild(playIcon.cloneNode(true)); - actionsArray.forEach((element) => { - element.appendChild(playIcon.cloneNode(true)); - }); - } + playIconSvg: { + type: String, + required: false, + }, }, template: ` <div class="inline"> <div class="dropdown"> <a class="dropdown-new btn btn-default" data-toggle="dropdown"> - <span class="dropdown-play-icon-container"></span> + <span class="js-dropdown-play-icon-container" v-html="playIconSvg"></span> <i class="fa fa-caret-down"></i> </a> @@ -53,7 +33,9 @@ data-method="post" rel="nofollow" class="js-manual-action-link"> - <span class="action-play-icon-container"></span> + + <span class="js-action-play-icon-container" v-html="playIconSvg"></span> + <span> {{action.name}} </span> diff --git a/app/assets/javascripts/environments/components/environment_external_url.js.es6 b/app/assets/javascripts/environments/components/environment_external_url.js.es6 index 79cd5ded5bd..aed65b33c04 100644 --- a/app/assets/javascripts/environments/components/environment_external_url.js.es6 +++ b/app/assets/javascripts/environments/components/environment_external_url.js.es6 @@ -7,14 +7,14 @@ window.gl.environmentsList.ExternalUrlComponent = Vue.component('external-url-component', { props: { - external_url: { + externalUrl: { type: String, default: '', }, }, template: ` - <a class="btn external_url" :href="external_url" target="_blank"> + <a class="btn external_url" :href="externalUrl" target="_blank"> <i class="fa fa-external-link"></i> </a> `, diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index 6ed14261fc3..2e046a60146 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -58,6 +58,16 @@ required: false, default: false, }, + + commitIconSvg: { + type: String, + required: false, + }, + + playIconSvg: { + type: String, + required: false, + }, }, data() { @@ -451,11 +461,12 @@ <div v-if="!isFolder && hasLastDeploymentKey" class="js-commit-component"> <commit-component :tag="commitTag" - :commit_ref="commitRef" - :commit_url="commitUrl" - :short_sha="commitShortSha" + :commit-ref="commitRef" + :commit-url="commitUrl" + :short-sha="commitShortSha" :title="commitTitle" - :author="commitAuthor"> + :author="commitAuthor" + :commit-icon-svg="commitIconSvg"> </commit-component> </div> <p v-if="!isFolder && !hasLastDeploymentKey" class="commit-title"> @@ -476,6 +487,7 @@ <div v-if="hasManualActions && canCreateDeployment" class="inline js-manual-actions-container"> <actions-component + :play-icon-svg="playIconSvg" :actions="manualActions"> </actions-component> </div> @@ -483,22 +495,22 @@ <div v-if="model.external_url && canReadEnvironment" class="inline js-external-url-container"> <external-url-component - :external_url="model.external_url"> - </external_url-component> + :external-url="model.external_url"> + </external-url-component> </div> <div v-if="isStoppable && canCreateDeployment" class="inline js-stop-component-container"> <stop-component - :stop_url="model.stop_path"> + :stop-url="model.stop_path"> </stop-component> </div> <div v-if="canRetry && canCreateDeployment" class="inline js-rollback-component-container"> <rollback-component - :is_last_deployment="isLastDeployment" - :retry_url="retryUrl"> + :is-last-deployment="isLastDeployment" + :retry-url="retryUrl"> </rollback-component> </div> </div> diff --git a/app/assets/javascripts/environments/components/environment_rollback.js.es6 b/app/assets/javascripts/environments/components/environment_rollback.js.es6 index 55e5c826e07..6d4e8fad604 100644 --- a/app/assets/javascripts/environments/components/environment_rollback.js.es6 +++ b/app/assets/javascripts/environments/components/environment_rollback.js.es6 @@ -7,19 +7,20 @@ window.gl.environmentsList.RollbackComponent = Vue.component('rollback-component', { props: { - retry_url: { + retryUrl: { type: String, default: '', }, - is_last_deployment: { + + isLastDeployment: { type: Boolean, default: true, }, }, template: ` - <a class="btn" :href="retry_url" data-method="post" rel="nofollow"> - <span v-if="is_last_deployment"> + <a class="btn" :href="retryUrl" data-method="post" rel="nofollow"> + <span v-if="isLastDeployment"> Re-deploy </span> <span v-else> diff --git a/app/assets/javascripts/environments/components/environment_stop.js.es6 b/app/assets/javascripts/environments/components/environment_stop.js.es6 index e6d66a0148c..7292f924e5c 100644 --- a/app/assets/javascripts/environments/components/environment_stop.js.es6 +++ b/app/assets/javascripts/environments/components/environment_stop.js.es6 @@ -7,7 +7,7 @@ window.gl.environmentsList.StopComponent = Vue.component('stop-component', { props: { - stop_url: { + stopUrl: { type: String, default: '', }, @@ -15,7 +15,7 @@ template: ` <a class="btn stop-env-link" - :href="stop_url" + :href="stopUrl" data-confirm="Are you sure you want to stop this environment?" data-method="post" rel="nofollow"> diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6 index 6f9d6283071..2f3da745119 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.es6 +++ b/app/assets/javascripts/gfm_auto_complete.js.es6 @@ -52,6 +52,10 @@ return $.fn.atwho["default"].callbacks.filter(query, data, searchKey); }, beforeInsert: function(value) { + if (value && !this.setting.skipSpecialCharacterTest) { + var withoutAt = value.substring(1); + if (withoutAt && /[^\w\d]/.test(withoutAt)) value = value.charAt() + '"' + withoutAt + '"'; + } if (!GitLab.GfmAutoComplete.dataLoaded) { return this.at; } else { @@ -117,6 +121,7 @@ insertTpl: ':${name}:', data: ['loading'], startWithSpace: false, + skipSpecialCharacterTest: true, callbacks: { sorter: this.DefaultOptions.sorter, filter: this.DefaultOptions.filter, @@ -141,6 +146,7 @@ data: ['loading'], startWithSpace: false, alwaysHighlightFirst: true, + skipSpecialCharacterTest: true, callbacks: { sorter: this.DefaultOptions.sorter, filter: this.DefaultOptions.filter, @@ -219,12 +225,13 @@ } }; })(this), - insertTpl: '${atwho-at}"${title}"', + insertTpl: '${atwho-at}${title}', data: ['loading'], startWithSpace: false, callbacks: { matcher: this.DefaultOptions.matcher, sorter: this.DefaultOptions.sorter, + beforeInsert: this.DefaultOptions.beforeInsert, beforeSave: function(milestones) { return $.map(milestones, function(m) { if (m.title == null) { @@ -284,18 +291,11 @@ callbacks: { matcher: this.DefaultOptions.matcher, sorter: this.DefaultOptions.sorter, + beforeInsert: this.DefaultOptions.beforeInsert, beforeSave: function(merges) { - var sanitizeLabelTitle; - sanitizeLabelTitle = function(title) { - if (/[\w\?&]+\s+[\w\?&]+/g.test(title)) { - return "\"" + (sanitize(title)) + "\""; - } else { - return sanitize(title); - } - }; return $.map(merges, function(m) { return { - title: sanitizeLabelTitle(m.title), + title: sanitize(m.title), color: m.color, search: "" + m.title }; @@ -308,6 +308,7 @@ at: '/', alias: 'commands', searchKey: 'search', + skipSpecialCharacterTest: true, displayTpl: function(value) { var tpl = '<li>/${name}'; if (value.aliases.length > 0) { diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 969778dded7..9a91018a8e4 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -650,6 +650,11 @@ } else if(value) { field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']"); } + + if (this.options.isSelectable && !this.options.isSelectable(selectedObject, el)) { + return; + } + if (el.hasClass(ACTIVE_CLASS)) { el.removeClass(ACTIVE_CLASS); if (field && field.length) { diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_util.js b/app/assets/javascripts/graphs/stat_graph_contributors_util.js index 051ff98c774..1982f4af939 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors_util.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors_util.js @@ -2,7 +2,7 @@ (function() { window.ContributorsStatGraphUtil = { parse_log: function(log) { - var by_author, by_email, data, entry, i, len, total; + var by_author, by_email, data, entry, i, len, total, normalized_email; total = {}; by_author = {}; by_email = {}; @@ -11,7 +11,8 @@ if (total[entry.date] == null) { this.add_date(entry.date, total); } - data = by_author[entry.author_name] || by_email[entry.author_email]; + normalized_email = entry.author_email.toLowerCase(); + data = by_author[entry.author_name] || by_email[normalized_email]; if (data == null) { data = this.add_author(entry, by_author, by_email); } @@ -32,12 +33,14 @@ return collection[date].date = date; }, add_author: function(author, by_author, by_email) { - var data; + var data, normalized_email; data = {}; data.author_name = author.author_name; data.author_email = author.author_email; + normalized_email = author.author_email.toLowerCase(); by_author[author.author_name] = data; - return by_email[author.author_email] = data; + by_email[normalized_email] = data; + return data; }, store_data: function(entry, total, by_author) { this.store_commits(total, by_author); diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 index 895bc10784f..e3f367a11eb 100644 --- a/app/assets/javascripts/members.js.es6 +++ b/app/assets/javascripts/members.js.es6 @@ -1,38 +1,81 @@ -/* eslint-disable */ -((w) => { - w.gl = w.gl || {}; +/* eslint-disable class-methods-use-this */ +(() => { + window.gl = window.gl || {}; class Members { constructor() { this.addListeners(); + this.initGLDropdown(); } addListeners() { $('.project_member, .group_member').off('ajax:success').on('ajax:success', this.removeRow); - $('.js-member-update-control').off('change').on('change', this.formSubmit); - $('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess); + $('.js-member-update-control').off('change').on('change', this.formSubmit.bind(this)); + $('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess.bind(this)); gl.utils.disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change'); } + initGLDropdown() { + $('.js-member-permissions-dropdown').each((i, btn) => { + const $btn = $(btn); + + $btn.glDropdown({ + selectable: true, + isSelectable(selected, $el) { + return !$el.hasClass('is-active'); + }, + fieldName: $btn.data('field-name'), + id(selected, $el) { + return $el.data('id'); + }, + toggleLabel(selected, $el) { + return $el.text(); + }, + clicked: (selected, $link) => { + this.formSubmit(null, $link); + }, + }); + }); + } + removeRow(e) { const $target = $(e.target); if ($target.hasClass('btn-remove')) { $target.closest('.member') - .fadeOut(function () { + .fadeOut(function fadeOutMemberRow() { $(this).remove(); }); } } - formSubmit() { - $(this).closest('form').trigger("submit.rails").end().disable(); + formSubmit(e, $el = null) { + const $this = e ? $(e.currentTarget) : $el; + const { $toggle, $dateInput } = this.getMemberListItems($this); + + $this.closest('form').trigger('submit.rails'); + + $toggle.disable(); + $dateInput.disable(); } - formSuccess() { - $(this).find('.js-member-update-control').enable(); + formSuccess(e) { + const { $toggle, $dateInput } = this.getMemberListItems($(e.currentTarget).closest('.member')); + + $toggle.enable(); + $dateInput.enable(); + } + + getMemberListItems($el) { + const $memberListItem = $el.is('.member') ? $el : $(`#${$el.data('el-id')}`); + + return { + $memberListItem, + $toggle: $memberListItem.find('.dropdown-menu-toggle'), + $dateInput: $memberListItem.find('.js-access-expiration-date'), + }; } } gl.Members = Members; -})(window); +})(); diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index a55fe9df0b3..7022aa1263b 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -40,19 +40,26 @@ $('#modal_merge_info').modal({ show: false }); - this.firstCICheck = true; - this.readyForCICheck = false; - this.readyForCIEnvironmentCheck = false; - this.cancel = false; - clearInterval(this.fetchBuildStatusInterval); - clearInterval(this.fetchBuildEnvironmentStatusInterval); this.clearEventListeners(); this.addEventListeners(); this.getCIStatus(false); - this.getCIEnvironmentsStatus(); this.retrieveSuccessIcon(); - this.pollCIStatus(); - this.pollCIEnvironmentsStatus(); + + this.ciStatusInterval = new global.SmartInterval({ + callback: this.getCIStatus.bind(this, true), + startingInterval: 10000, + maxInterval: 30000, + hiddenInterval: 120000, + incrementByFactorOf: 5000, + }); + this.ciEnvironmentStatusInterval = new global.SmartInterval({ + callback: this.getCIEnvironmentsStatus.bind(this), + startingInterval: 30000, + maxInterval: 120000, + hiddenInterval: 240000, + incrementByFactorOf: 15000, + immediateExecution: true, + }); notifyPermissions(); } @@ -60,10 +67,6 @@ return $(document).off('page:change.merge_request'); }; - MergeRequestWidget.prototype.cancelPolling = function() { - return this.cancel = true; - }; - MergeRequestWidget.prototype.addEventListeners = function() { var allowedPages; allowedPages = ['show', 'commits', 'builds', 'pipelines', 'changes']; @@ -72,9 +75,6 @@ var page; page = $('body').data('page').split(':').last(); if (allowedPages.indexOf(page) < 0) { - clearInterval(_this.fetchBuildStatusInterval); - clearInterval(_this.fetchBuildEnvironmentStatusInterval); - _this.cancelPolling(); return _this.clearEventListeners(); } }; @@ -101,7 +101,7 @@ urlSuffix = deleteSourceBranch ? '?deleted_source_branch=true' : ''; return window.location.href = window.location.pathname + urlSuffix; } else if (data.merge_error) { - return this.$widgetBody.html("<h4>" + data.merge_error + "</h4>"); + return _this.$widgetBody.html("<h4>" + data.merge_error + "</h4>"); } else { callback = function() { return merge_request_widget.mergeInProgress(deleteSourceBranch); @@ -114,6 +114,11 @@ }); }; + MergeRequestWidget.prototype.cancelPolling = function () { + this.ciStatusInterval.cancel(); + this.ciEnvironmentStatusInterval.cancel(); + }; + MergeRequestWidget.prototype.getMergeStatus = function() { return $.get(this.opts.merge_check_url, function(data) { return $('.mr-state-widget').replaceWith(data); @@ -131,18 +136,6 @@ } }; - MergeRequestWidget.prototype.pollCIStatus = function() { - return this.fetchBuildStatusInterval = setInterval(((function(_this) { - return function() { - if (!_this.readyForCICheck) { - return; - } - _this.getCIStatus(true); - return _this.readyForCICheck = false; - }; - })(this)), 10000); - }; - MergeRequestWidget.prototype.getCIStatus = function(showNotification) { var _this; _this = this; @@ -150,23 +143,17 @@ return $.getJSON(this.opts.ci_status_url, (function(_this) { return function(data) { var message, status, title; - if (_this.cancel) { - return; - } - _this.readyForCICheck = true; if (data.status === '') { return; } if (data.environments && data.environments.length) _this.renderEnvironments(data.environments); - if (_this.firstCICheck || data.status !== _this.opts.ci_status && (data.status != null)) { + if (data.status !== _this.opts.ci_status && (data.status != null)) { _this.opts.ci_status = data.status; _this.showCIStatus(data.status); if (data.coverage) { _this.showCICoverage(data.coverage); } - // The first check should only update the UI, a notification - // should only be displayed on status changes - if (showNotification && !_this.firstCICheck) { + if (showNotification) { status = _this.ciLabelForStatus(data.status); if (status === "preparing") { title = _this.opts.ci_title.preparing; @@ -184,24 +171,13 @@ return Turbolinks.visit(_this.opts.builds_path); }); } - return _this.firstCICheck = false; } }; })(this)); }; - MergeRequestWidget.prototype.pollCIEnvironmentsStatus = function() { - this.fetchBuildEnvironmentStatusInterval = setInterval(() => { - if (!this.readyForCIEnvironmentCheck) return; - this.getCIEnvironmentsStatus(); - this.readyForCIEnvironmentCheck = false; - }, 300000); - }; - MergeRequestWidget.prototype.getCIEnvironmentsStatus = function() { $.getJSON(this.opts.ci_environments_status_url, (environments) => { - if (this.cancel) return; - this.readyForCIEnvironmentCheck = true; if (environments && environments.length) this.renderEnvironments(environments); }); }; @@ -212,11 +188,11 @@ if ($(`.mr-state-widget #${ environment.id }`).length) return; const $template = $(DEPLOYMENT_TEMPLATE); if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove(); - + if (!environment.stop_url) { $('.js-stop-env-link', $template).remove(); } - + if (environment.deployed_at && environment.deployed_at_formatted) { environment.deployed_at = gl.utils.getTimeago().format(environment.deployed_at, 'gl_en') + '.'; } else { diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6 index 72c6c4a1fcd..fb95648e1c7 100644 --- a/app/assets/javascripts/pipelines.js.es6 +++ b/app/assets/javascripts/pipelines.js.es6 @@ -4,7 +4,7 @@ ((global) => { class Pipelines { - constructor(options) { + constructor(options = {}) { if (options.initTabs && options.tabsOptions) { new global.LinkedTabs(options.tabsOptions); @@ -14,9 +14,11 @@ } addMarginToBuildColumns() { - this.pipelineGraph = document.querySelector('.pipeline-graph'); - const secondChildBuildNodes = document.querySelector('.pipeline-graph').querySelectorAll('.build:nth-child(2)'); - for (buildNodeIndex in secondChildBuildNodes) { + this.pipelineGraph = document.querySelector('.js-pipeline-graph'); + + const secondChildBuildNodes = this.pipelineGraph.querySelectorAll('.build:nth-child(2)'); + + for (const buildNodeIndex in secondChildBuildNodes) { const buildNode = secondChildBuildNodes[buildNodeIndex]; const firstChildBuildNode = buildNode.previousElementSibling; if (!firstChildBuildNode || !firstChildBuildNode.matches('.build')) continue; @@ -28,6 +30,7 @@ const columnBuilds = previousColumn.querySelectorAll('.build'); if (columnBuilds.length === 1) previousColumn.classList.add('no-margin'); } + this.pipelineGraph.classList.remove('hidden'); } } diff --git a/app/assets/javascripts/smart_interval.js.es6 b/app/assets/javascripts/smart_interval.js.es6 index 5eb15dba79b..40f67637c7c 100644 --- a/app/assets/javascripts/smart_interval.js.es6 +++ b/app/assets/javascripts/smart_interval.js.es6 @@ -7,24 +7,31 @@ (() => { class SmartInterval { /** - * @param { function } callback Function to be called on each iteration (required) - * @param { milliseconds } startingInterval `currentInterval` is set to this initially - * @param { milliseconds } maxInterval `currentInterval` will be incremented to this - * @param { integer } incrementByFactorOf `currentInterval` is incremented by this factor - * @param { boolean } lazyStart Configure if timer is initialized on instantiation or lazily + * @param { function } opts.callback Function to be called on each iteration (required) + * @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially + * @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this + * @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this + * when the page is hidden + * @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor + * @param { boolean } opts.lazyStart Configure if timer is initialized on + * instantiation or lazily + * @param { boolean } opts.immediateExecution Configure if callback should + * be executed before the first interval. */ - constructor({ callback, startingInterval, maxInterval, incrementByFactorOf, lazyStart }) { + constructor(opts = {}) { this.cfg = { - callback, - startingInterval, - maxInterval, - incrementByFactorOf, - lazyStart, + callback: opts.callback, + startingInterval: opts.startingInterval, + maxInterval: opts.maxInterval, + hiddenInterval: opts.hiddenInterval, + incrementByFactorOf: opts.incrementByFactorOf, + lazyStart: opts.lazyStart, + immediateExecution: opts.immediateExecution, }; this.state = { intervalId: null, - currentInterval: startingInterval, + currentInterval: this.cfg.startingInterval, pageVisibility: 'visible', }; @@ -36,6 +43,11 @@ const cfg = this.cfg; const state = this.state; + if (cfg.immediateExecution) { + cfg.immediateExecution = false; + cfg.callback(); + } + state.intervalId = window.setInterval(() => { cfg.callback(); @@ -54,14 +66,29 @@ this.stopTimer(); } + onVisibilityHidden() { + if (this.cfg.hiddenInterval) { + this.setCurrentInterval(this.cfg.hiddenInterval); + this.resume(); + } else { + this.cancel(); + } + } + // start a timer, using the existing interval resume() { this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped this.start(); } + onVisibilityVisible() { + this.cancel(); + this.start(); + } + destroy() { this.cancel(); + document.removeEventListener('visibilitychange', this.handleVisibilityChange); $(document).off('visibilitychange').off('page:before-unload'); } @@ -80,11 +107,7 @@ initVisibilityChangeHandling() { // cancel interval when tab no longer shown (prevents cached pages from polling) - $(document) - .off('visibilitychange').on('visibilitychange', (e) => { - this.state.pageVisibility = e.target.visibilityState; - this.handleVisibilityChange(); - }); + document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this)); } initPageUnloadHandling() { @@ -92,10 +115,11 @@ $(document).on('page:before-unload', () => this.cancel()); } - handleVisibilityChange() { - const state = this.state; - - const intervalAction = state.pageVisibility === 'hidden' ? this.cancel : this.resume; + handleVisibilityChange(e) { + this.state.pageVisibility = e.target.visibilityState; + const intervalAction = this.isPageVisible() ? + this.onVisibilityVisible : + this.onVisibilityHidden; intervalAction.apply(this); } @@ -111,6 +135,7 @@ incrementInterval() { const cfg = this.cfg; const currentInterval = this.getCurrentInterval(); + if (cfg.hiddenInterval && !this.isPageVisible()) return; let nextInterval = currentInterval * cfg.incrementByFactorOf; if (nextInterval > cfg.maxInterval) { @@ -120,6 +145,8 @@ this.setCurrentInterval(nextInterval); } + isPageVisible() { return this.state.pageVisibility === 'visible'; } + stopTimer() { const state = this.state; diff --git a/app/assets/javascripts/vue_common_component/commit.js.es6 b/app/assets/javascripts/vue_common_component/commit.js.es6 index 2ef2959cbf4..62a22e39a3b 100644 --- a/app/assets/javascripts/vue_common_component/commit.js.es6 +++ b/app/assets/javascripts/vue_common_component/commit.js.es6 @@ -23,7 +23,7 @@ * name * ref_url */ - commit_ref: { + commitRef: { type: Object, required: false, default: () => ({}), @@ -32,16 +32,16 @@ /** * Used to link to the commit sha. */ - commit_url: { + commitUrl: { type: String, required: false, default: '', }, /** - * Used to show the commit short_sha that links to the commit url. + * Used to show the commit short sha that links to the commit url. */ - short_sha: { + shortSha: { type: String, required: false, default: '', @@ -68,6 +68,11 @@ required: false, default: () => ({}), }, + + commitIconSvg: { + type: String, + required: false, + }, }, computed: { @@ -80,7 +85,7 @@ * @returns {Boolean} */ hasCommitRef() { - return this.commit_ref && this.commit_ref.name && this.commit_ref.ref_url; + return this.commitRef && this.commitRef.name && this.commitRef.ref_url; }, /** @@ -110,24 +115,6 @@ }, }, - /** - * In order to reuse the svg instead of copy and paste in this template - * we need to render it outside this component using =custom_icon partial. - * Make sure it has this structure: - * .commit-icon-svg.hidden - * svg - * - * TODO: Find a better way to include SVG - */ - mounted() { - const commitIconContainer = this.$el.querySelector('.commit-icon-container'); - const commitIcon = document.querySelector('.commit-icon-svg.hidden svg'); - - if (commitIconContainer && commitIcon) { - commitIconContainer.appendChild(commitIcon.cloneNode(true)); - } - }, - template: ` <div class="branch-commit"> @@ -138,15 +125,15 @@ <a v-if="hasCommitRef" class="monospace branch-name" - :href="commit_ref.ref_url"> - {{commit_ref.name}} + :href="commitRef.ref_url"> + {{commitRef.name}} </a> - <div class="icon-container commit-icon commit-icon-container"></div> + <div v-html="commitIconSvg" class="commit-icon js-commit-icon"></div> <a class="commit-id monospace" - :href="commit_url"> - {{short_sha}} + :href="commitUrl"> + {{shortSha}} </a> <p class="commit-title"> @@ -162,7 +149,7 @@ </a> <a class="commit-row-message" - :href="commit_url"> + :href="commitUrl"> {{title}} </a> </span> diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 8da3da2ad08..1c7b2f4df7c 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -1,7 +1,7 @@ @mixin btn-default { border-radius: 3px; font-size: $gl-font-size; - font-weight: 500; + font-weight: 400; padding: $gl-vert-padding $gl-btn-padding; &:focus, diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 33de652c06f..d5914b900e2 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -42,6 +42,11 @@ border-radius: $border-radius-base; white-space: nowrap; + &[disabled] { + background-color: $input-bg-disabled; + cursor: not-allowed; + } + &.no-outline { outline: 0; } diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 69da520f21f..ea77348633d 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -71,6 +71,10 @@ border-bottom: 2px solid $link-underline-blue; color: $black; font-weight: 600; + + .badge { + color: $black; + } } .badge { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index cfb2ed9321b..03fbfa5f1bd 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -486,6 +486,7 @@ $jq-ui-default-color: #777; $label-gray-bg: #f8fafc; $label-inverse-bg: #333; $label-remove-border: rgba(0, 0, 0, .1); +$label-border-radius: 14px; /* * Lint diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index de3d2ba549f..e716f24c8e2 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -1,6 +1,8 @@ -.deployments-container { - width: 100%; - overflow: auto; +@media (max-width: $screen-md-max) { + .deployments-container { + width: 100%; + overflow: auto; + } } .environments-list-loading { diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 90587b9425b..407c0afbac8 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -30,6 +30,7 @@ .color-label { padding: 6px 10px; + border-radius: $label-border-radius; } } diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index b1ccd644450..25c91203ff4 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -104,7 +104,8 @@ } .color-label { - padding: 3px 4px; + padding: 3px 7px; + border-radius: $label-border-radius; } .dropdown-labels-error { diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index 756efa9c7fa..5f3bbb40ba0 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -54,6 +54,10 @@ @media (min-width: $screen-sm-min) { width: 50%; } + + .dropdown-menu-toggle { + width: 100%; + } } .member-access-text { diff --git a/app/assets/stylesheets/pages/notifications.scss b/app/assets/stylesheets/pages/notifications.scss index 94fbbef3c77..7d61390a439 100644 --- a/app/assets/stylesheets/pages/notifications.scss +++ b/app/assets/stylesheets/pages/notifications.scss @@ -1,5 +1,9 @@ .notification-list-item { line-height: 34px; + + .dropdown-menu { + @extend .dropdown-menu-align-right; + } } .notification { diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 15da30dda2b..23312259f73 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -37,12 +37,13 @@ } } -.content-list { - - &.pipelines, - &.builds-content-list { - width: 100%; - overflow: auto; +@media (max-width: $screen-md-max) { + .content-list { + &.pipelines, + &.builds-content-list { + width: 100%; + overflow: auto; + } } } @@ -663,6 +664,10 @@ border-radius: 3px; color: $gl-text-color; } + + .stage { + max-width: 100px; + width: 100px; } .ci-status-icon svg { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 72b6685d940..6e0f6b1cd81 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -188,6 +188,10 @@ margin-left: 10px; } + .notification-dropdown .dropdown-menu { + @extend .dropdown-menu-align-right; + } + .download-button { @media (max-width: $screen-md-max) { margin-left: 0; diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss index 857eb76131a..ff13b86acf0 100644 --- a/app/assets/stylesheets/pages/snippets.scss +++ b/app/assets/stylesheets/pages/snippets.scss @@ -1,3 +1,13 @@ +.snippet-row { + .title { + margin-bottom: 2px; + } + + .snippet-filename { + padding: 0 2px; + } +} + .snippet-form-holder .file-holder .file-title { padding: 2px; } @@ -24,11 +34,17 @@ padding-bottom: $gl-padding; } +.snippet-header { + padding: $gl-padding 0; +} + .snippet-title { font-size: 24px; font-weight: 600; - padding: $gl-padding; - padding-left: 0; +} + +.snippet-edited-ago { + color: $gray-darkest; } .snippet-actions { diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 3fb8bba3cd0..53308948f62 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -35,13 +35,12 @@ class Projects::ProjectMembersController < Projects::ApplicationController @group_links = @project.project_group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id)) end - member_ids = @project_members.pluck(:id) + wheres = ["id IN (#{@project_members.select(:id).to_sql})"] + wheres << "id IN (#{group_members.select(:id).to_sql})" if group_members - if group_members - member_ids += group_members.pluck(:id) - end - - @project_members = Member.where(id: member_ids).order(access_level: :desc).page(params[:page]) + @project_members = Member. + where(wheres.join(' OR ')). + order(access_level: :desc).page(params[:page]) @requesters = AccessRequestsFinder.new(@project).execute(current_user) diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb index 0825a4311cb..2c097cb4d8d 100644 --- a/app/controllers/projects/releases_controller.rb +++ b/app/controllers/projects/releases_controller.rb @@ -10,7 +10,14 @@ class Projects::ReleasesController < Projects::ApplicationController end def update - release.update_attributes(release_params) + # Release belongs to Tag which is not active record object, + # it exists only to save a description to each Tag. + # If description is empty we should destroy the existing record. + if release_params[:description].present? + release.update_attributes(release_params) + else + release.destroy + end redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name) end diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index e290a0eadda..0720be2e55d 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -19,10 +19,12 @@ class Projects::SnippetsController < Projects::ApplicationController respond_to :html def index - @snippets = SnippetsFinder.new.execute(current_user, { + @snippets = SnippetsFinder.new.execute( + current_user, filter: :by_project, - project: @project - }) + project: @project, + scope: params[:scope] + ) @snippets = @snippets.page(params[:page]) end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 38e7c6f4a48..8c698695202 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -37,6 +37,12 @@ class SessionsController < Devise::SessionsController end end + def destroy + super + # hide the signed_out notice + flash[:notice] = nil + end + private # Handle an "initial setup" state, where there's only one user, it's an admin, diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index 00ff1611039..da6e6e87a6f 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -1,14 +1,17 @@ class SnippetsFinder def execute(current_user, params = {}) filter = params[:filter] + user = params.fetch(:user, current_user) case filter when :all then snippets(current_user).fresh + when :public then + Snippet.are_public.fresh when :by_user then - by_user(current_user, params[:user], params[:scope]) + by_user(current_user, user, params[:scope]) when :by_project - by_project(current_user, params[:project]) + by_project(current_user, params[:project], params[:scope]) end end @@ -29,35 +32,35 @@ class SnippetsFinder def by_user(current_user, user, scope) snippets = user.snippets.fresh - return snippets.are_public unless current_user - - if user == current_user - case scope - when 'are_internal' then - snippets.are_internal - when 'are_private' then - snippets.are_private - when 'are_public' then - snippets.are_public - else - snippets - end + if current_user + include_private = user == current_user + by_scope(snippets, scope, include_private) else - snippets.public_and_internal + snippets.are_public end end - def by_project(current_user, project) + def by_project(current_user, project, scope) snippets = project.snippets.fresh if current_user - if project.team.member?(current_user) || current_user.admin? - snippets - else - snippets.public_and_internal - end + include_private = project.team.member?(current_user) || current_user.admin? + by_scope(snippets, scope, include_private) else snippets.are_public end end + + def by_scope(snippets, scope = nil, include_private = false) + case scope.to_s + when 'are_private' + include_private ? snippets.are_private : Snippet.none + when 'are_internal' + snippets.are_internal + when 'are_public' + snippets.are_public + else + include_private ? snippets : snippets.public_and_internal + end + end end diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index dee3c78df47..4c7c16d694c 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -16,7 +16,7 @@ module ButtonHelper # See http://clipboardjs.com/#usage def clipboard_button(data = {}) css_class = data[:class] || 'btn-clipboard btn-transparent' - title = data[:title] || 'Copy to Clipboard' + title = data[:title] || 'Copy to clipboard' data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) content_tag :button, icon('clipboard'), diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb index 27975b7ddb7..ff8550439d0 100644 --- a/app/helpers/environment_helper.rb +++ b/app/helpers/environment_helper.rb @@ -14,10 +14,12 @@ module EnvironmentHelper end end - def deployment_link(deployment) + def deployment_link(deployment, text: nil) return unless deployment - link_to "##{deployment.iid}", [deployment.project.namespace.becomes(Namespace), deployment.project, deployment.deployable] + link_label = text ? text : "##{deployment.iid}" + + link_to link_label, [deployment.project.namespace.becomes(Namespace), deployment.project, deployment.deployable] end def last_deployment_link_for_environment_build(project, build) diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index f1a0b929d82..362046c0270 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -45,6 +45,12 @@ module EventsHelper @project.feature_available?(feature_key, current_user) end + def comments_visible? + event_filter_visible(:repository) || + event_filter_visible(:merge_requests) || + event_filter_visible(:issues) + end + def event_preposition(event) if event.push? || event.commented? || event.target "at" diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index af9087d8326..99db73c9ee0 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -159,6 +159,11 @@ module GitlabRoutingHelper resend_invite_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) end + # Snippets + def personal_snippet_url(snippet, *args) + snippet_url(snippet) + end + # Groups ## Members diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb index 7e33a562077..8c02b4061ca 100644 --- a/app/helpers/snippets_helper.rb +++ b/app/helpers/snippets_helper.rb @@ -8,6 +8,17 @@ module SnippetsHelper end end + # Return the path of a snippets index for a user or for a project + # + # @returns String, path to snippet index + def subject_snippets_path(subject = nil, opts = nil) + if subject.is_a?(Project) + namespace_project_snippets_path(subject.namespace, subject, opts) + else # assume subject === User + dashboard_snippets_path(opts) + end + end + # Get an array of line numbers surrounding a matching # line, bounded by min/max. # diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 65ee327a8e5..e7cf606a7ae 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -101,7 +101,9 @@ module Ci end def detailed_status(current_user) - Gitlab::Ci::Status::Build::Factory.new(self, current_user).fabricate! + Gitlab::Ci::Status::Build::Factory + .new(self, current_user) + .fabricate! end def manual? @@ -132,7 +134,8 @@ module Ci end def retryable? - project.builds_enabled? && commands.present? && complete? + project.builds_enabled? && commands.present? && + (success? || failed? || canceled?) end def retried? diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 1f33106d358..54f73171fd4 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -337,7 +337,9 @@ module Ci end def detailed_status(current_user) - Gitlab::Ci::Status::Pipeline::Factory.new(self, current_user).fabricate! + Gitlab::Ci::Status::Pipeline::Factory + .new(self, current_user) + .fabricate! end private diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index be52cce20f1..7ef59445d77 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -23,7 +23,9 @@ module Ci end def detailed_status(current_user) - Gitlab::Ci::Status::Stage::Factory.new(self, current_user).fabricate! + Gitlab::Ci::Status::Stage::Factory + .new(self, current_user) + .fabricate! end def statuses diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 6548a7dda2c..31cd381dcd2 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -133,6 +133,8 @@ class CommitStatus < ActiveRecord::Base end def detailed_status(current_user) - Gitlab::Ci::Status::Factory.new(self, current_user).fabricate! + Gitlab::Ci::Status::Factory + .new(self, current_user) + .fabricate! end end diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb index d36bb9da296..1108a64c59e 100644 --- a/app/models/concerns/routable.rb +++ b/app/models/concerns/routable.rb @@ -7,6 +7,7 @@ module Routable has_one :route, as: :source, autosave: true, dependent: :destroy validates_associated :route + validates :route, presence: true before_validation :update_route_path, if: :full_path_changed? end @@ -28,17 +29,17 @@ module Routable order_sql = "(CASE WHEN #{binary} routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)" - where_paths_in([path]).reorder(order_sql).take + where_full_path_in([path]).reorder(order_sql).take end # Builds a relation to find multiple objects by their full paths. # # Usage: # - # Klass.where_paths_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee}) + # Klass.where_full_path_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee}) # # Returns an ActiveRecord::Relation. - def where_paths_in(paths) + def where_full_path_in(paths) wheres = [] cast_lower = Gitlab::Database.postgresql? diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index dd9f1a7c85b..ea3cf1cdaac 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -101,7 +101,9 @@ class MergeRequest < ActiveRecord::Base validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?] validate :validate_fork, unless: :closed_without_fork? - scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) } + scope :by_source_or_target_branch, ->(branch_name) do + where("source_branch = :branch OR target_branch = :branch", branch: branch_name) + end scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } scope :by_milestone, ->(milestone) { where(milestone_id: milestone) } scope :of_projects, ->(ids) { where(target_project_id: ids) } diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb index 86a06321e21..fe6d7aabb22 100644 --- a/app/models/project_services/buildkite_service.rb +++ b/app/models/project_services/buildkite_service.rb @@ -3,7 +3,8 @@ require "addressable/uri" class BuildkiteService < CiService ENDPOINT = "https://buildkite.com" - prop_accessor :project_url, :token, :enable_ssl_verification + prop_accessor :project_url, :token + boolean_accessor :enable_ssl_verification validates :project_url, presence: true, url: true, if: :activated? validates :token, presence: true, if: :activated? diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb index 5e4dd101c53..adc78a427ee 100644 --- a/app/models/project_services/drone_ci_service.rb +++ b/app/models/project_services/drone_ci_service.rb @@ -1,5 +1,6 @@ class DroneCiService < CiService - prop_accessor :drone_url, :token, :enable_ssl_verification + prop_accessor :drone_url, :token + boolean_accessor :enable_ssl_verification validates :drone_url, presence: true, url: true, if: :activated? validates :token, presence: true, if: :activated? diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb index e0083c43adb..79285cbd26d 100644 --- a/app/models/project_services/emails_on_push_service.rb +++ b/app/models/project_services/emails_on_push_service.rb @@ -1,6 +1,6 @@ class EmailsOnPushService < Service - prop_accessor :send_from_committer_email - prop_accessor :disable_diffs + boolean_accessor :send_from_committer_email + boolean_accessor :disable_diffs prop_accessor :recipients validates :recipients, presence: true, if: :activated? @@ -24,20 +24,20 @@ class EmailsOnPushService < Service return unless supported_events.include?(push_data[:object_kind]) EmailsOnPushWorker.perform_async( - project_id, - recipients, - push_data, - send_from_committer_email: send_from_committer_email?, - disable_diffs: disable_diffs? + project_id, + recipients, + push_data, + send_from_committer_email: send_from_committer_email?, + disable_diffs: disable_diffs? ) end def send_from_committer_email? - self.send_from_committer_email == "1" + Gitlab::Utils.to_boolean(self.send_from_committer_email) end def disable_diffs? - self.disable_diffs == "1" + Gitlab::Utils.to_boolean(self.disable_diffs) end def fields diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 660a8ae3421..915f6fed74c 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -8,8 +8,8 @@ class HipchatService < Service ul ol li dl dt dd ] - prop_accessor :token, :room, :server, :notify, :color, :api_version - boolean_accessor :notify_only_broken_builds + prop_accessor :token, :room, :server, :color, :api_version + boolean_accessor :notify_only_broken_builds, :notify validates :token, presence: true, if: :activated? def initialize_properties @@ -75,7 +75,7 @@ class HipchatService < Service end def message_options(data = nil) - { notify: notify.present? && notify == '1', color: message_color(data) } + { notify: notify.present? && Gitlab::Utils.to_boolean(notify), color: message_color(data) } end def create_message(data) diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb index ce7d1c5d5b1..7355918feab 100644 --- a/app/models/project_services/irker_service.rb +++ b/app/models/project_services/irker_service.rb @@ -2,7 +2,8 @@ require 'uri' class IrkerService < Service prop_accessor :server_host, :server_port, :default_irc_uri - prop_accessor :colorize_messages, :recipients, :channels + prop_accessor :recipients, :channels + boolean_accessor :colorize_messages validates :recipients, presence: true, if: :activated? before_validation :get_channels diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 894315a8593..2d969d2fcb6 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -220,7 +220,7 @@ class JiraService < IssueTrackerService entity_title = data[:entity][:title] project_name = data[:project][:name] - message = "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]:\n'#{entity_title}'" + message = "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]:\n'#{entity_title.chomp}'" link_title = "GitLab: Mentioned on #{entity_name} - #{entity_title}" link_props = build_remote_link_props(url: entity_url, title: link_title) diff --git a/app/models/user.rb b/app/models/user.rb index b9bb4a9e3f7..1bd28203523 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -304,10 +304,6 @@ class User < ActiveRecord::Base personal_access_token.user if personal_access_token end - def by_username_or_id(name_or_id) - find_by('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i) - end - # Returns a user for the given SSH key. def find_by_ssh_key_id(key_id) find_by(id: Key.unscoped.select(:user_id).where(id: key_id)) diff --git a/app/policies/group_member_policy.rb b/app/policies/group_member_policy.rb index 62335527654..5a3fe814b77 100644 --- a/app/policies/group_member_policy.rb +++ b/app/policies/group_member_policy.rb @@ -15,5 +15,11 @@ class GroupMemberPolicy < BasePolicy elsif @user == target_user can! :destroy_group_member end + + additional_rules! + end + + def additional_rules! + # This is meant to be overriden in EE end end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index b65fb68cd88..6f943feb2a7 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -33,6 +33,8 @@ class GroupPolicy < BasePolicy if globally_viewable && @subject.request_access_enabled && !member can! :request_access end + + additional_rules!(master) end def can_read_group? @@ -43,4 +45,8 @@ class GroupPolicy < BasePolicy GroupProjectsFinder.new(@subject).execute(@user).any? end + + def additional_rules!(master) + # This is meant to be overriden in EE + end end diff --git a/app/policies/personal_snippet_policy.rb b/app/policies/personal_snippet_policy.rb index 46c5aa1a5be..d3913986cd8 100644 --- a/app/policies/personal_snippet_policy.rb +++ b/app/policies/personal_snippet_policy.rb @@ -6,9 +6,14 @@ class PersonalSnippetPolicy < BasePolicy if @subject.author == @user can! :read_personal_snippet can! :update_personal_snippet + can! :destroy_personal_snippet can! :admin_personal_snippet end + unless @user.external? + can! :create_personal_snippet + end + if @subject.internal? && !@user.external? can! :read_personal_snippet end diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index db5f2bf9b2e..4d410f66c55 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -35,7 +35,7 @@ module Commits success else error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title(current_user)} automatically. - It may have already been #{action.to_s.dasherize}, or a more recent commit may have updated some of its content." + A #{action.to_s.dasherize} may have already been performed with this #{@commit.change_type_title(current_user)}, or a more recent commit may have updated some of its content." raise ChangeError, error_msg end end diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index e4056306bc4..0a9563ed7e7 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -55,8 +55,9 @@ module MergeRequests # Refresh merge request diff if we push to source or target branch of merge request # Note: we should update merge requests from forks too def reload_merge_requests - merge_requests = @project.merge_requests.opened.by_branch(@branch_name).to_a - merge_requests += fork_merge_requests.by_branch(@branch_name).to_a + merge_requests = @project.merge_requests.opened. + by_source_or_target_branch(@branch_name).to_a + merge_requests += fork_merge_requests merge_requests = filter_merge_requests(merge_requests) merge_requests.each do |merge_request| @@ -157,13 +158,14 @@ module MergeRequests def merge_requests_for_source_branch @source_merge_requests ||= begin merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a - merge_requests += fork_merge_requests.where(source_branch: @branch_name).to_a + merge_requests += fork_merge_requests filter_merge_requests(merge_requests) end end def fork_merge_requests - @fork_merge_requests ||= @project.fork_merge_requests.opened + @fork_merge_requests ||= @project.fork_merge_requests.opened. + where(source_branch: @branch_name).to_a end def branch_added? diff --git a/app/uploaders/uploader_helper.rb b/app/uploaders/uploader_helper.rb index b10ad71d052..fbaea2744a3 100644 --- a/app/uploaders/uploader_helper.rb +++ b/app/uploaders/uploader_helper.rb @@ -1,6 +1,6 @@ # Extra methods for uploader module UploaderHelper - IMAGE_EXT = %w[png jpg jpeg gif bmp tiff] + IMAGE_EXT = %w[png jpg jpeg gif bmp tiff svg] # We recommend using the .mp4 format over .mov. Videos in .mov format can # still be used but you really need to make sure they are served with the # proper MIME type video/mp4 and not video/quicktime or your videos won't play diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index badeb11b208..ca503e35623 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -91,7 +91,7 @@ %strong ##{build.id} %td.status - = render "ci/status/icon_with_description", subject: build + = render 'ci/status/badge', status: build.detailed_status(current_user) %td.status - if project diff --git a/app/views/ci/status/_badge.html.haml b/app/views/ci/status/_badge.html.haml new file mode 100644 index 00000000000..f2135af2686 --- /dev/null +++ b/app/views/ci/status/_badge.html.haml @@ -0,0 +1,10 @@ +- status = local_assigns.fetch(:status) + +- if status.has_details? + = link_to status.details_path, class: "ci-status ci-#{status}" do + = custom_icon(status.icon) + = status.text +- else + %span{ class: "ci-status ci-#{status}" } + = custom_icon(status.icon) + = status.text diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml index b25e8ea1f0c..02e90bbfa55 100644 --- a/app/views/dashboard/_snippets_head.html.haml +++ b/app/views/dashboard/_snippets_head.html.haml @@ -1,7 +1,13 @@ -%ul.nav-links - = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do - = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do - Your Snippets - = nav_link(page: explore_snippets_path) do - = link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do - Explore Snippets +.top-area + %ul.nav-links + = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do + = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do + Your Snippets + = nav_link(page: explore_snippets_path) do + = link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do + Explore Snippets + + - if current_user + .nav-controls.hidden-xs + = link_to new_snippet_path, class: "btn btn-new", title: "New snippet" do + New snippet diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml index b2af438ea57..85cbe0bf0e6 100644 --- a/app/views/dashboard/snippets/index.html.haml +++ b/app/views/dashboard/snippets/index.html.haml @@ -2,41 +2,11 @@ - header_title "Snippets", dashboard_snippets_path = render 'dashboard/snippets_head' += render partial: 'snippets/snippets_scope_menu', locals: { include_private: true } -.nav-block - .controls.hidden-xs - = link_to new_snippet_path, class: "btn btn-new", title: "New snippet" do - = icon('plus') - New snippet +.visible-xs + + = link_to new_snippet_path, class: "btn btn-new btn-block", title: "New snippet" do + New snippet - .nav-links.snippet-scope-menu - %li{ class: ("active" unless params[:scope]) } - = link_to dashboard_snippets_path do - All - %span.badge - = current_user.snippets.count - - %li{ class: ("active" if params[:scope] == "are_private") } - = link_to dashboard_snippets_path(scope: 'are_private') do - Private - %span.badge - = current_user.snippets.are_private.count - - %li{ class: ("active" if params[:scope] == "are_internal") } - = link_to dashboard_snippets_path(scope: 'are_internal') do - Internal - %span.badge - = current_user.snippets.are_internal.count - - %li{ class: ("active" if params[:scope] == "are_public") } - = link_to dashboard_snippets_path(scope: 'are_public') do - Public - %span.badge - = current_user.snippets.are_public.count - - .visible-xs - = link_to new_snippet_path, class: "btn btn-new btn-block", title: "New snippet" do - = icon('plus') - New snippet - -= render 'snippets/snippets' += render partial: 'snippets/snippets', locals: { link_project: true } diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml index 7def9eacdc9..e5706d04736 100644 --- a/app/views/explore/snippets/index.html.haml +++ b/app/views/explore/snippets/index.html.haml @@ -6,12 +6,4 @@ - else = render 'explore/head' -.row-content-block - - if current_user - = link_to new_snippet_path, class: "btn btn-new btn-wide-on-sm pull-right", title: "New snippet" do - New snippet - - .oneline - Public snippets created by you and other users are listed here - -= render 'snippets/snippets' += render partial: 'snippets/snippets', locals: { link_project: true } diff --git a/app/views/groups/group_members/update.js.haml b/app/views/groups/group_members/update.js.haml index de8f53b6b52..9d05bff6c4e 100644 --- a/app/views/groups/group_members/update.js.haml +++ b/app/views/groups/group_members/update.js.haml @@ -1,3 +1,4 @@ :plain var $listItem = $('#{escape_javascript(render('shared/members/member', member: @group_member))}'); $("##{dom_id(@group_member)} .list-item-name").replaceWith($listItem.find('.list-item-name')); + gl.utils.localTimeAgo($('.js-timeago'), $("##{dom_id(@group_member)}")); diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 324a116a50e..b4aa4f24d9e 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -6,13 +6,13 @@ - if group_issues(@group).exists? .top-area = render 'shared/issuable/nav', type: :issues - .nav-controls - - if current_user + - if current_user + .nav-controls = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn' do = icon('rss') %span.icon-label Subscribe - = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" + = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" = render 'shared/issuable/filter', type: :issues diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index e6953d94531..dbbdb583a24 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -2,8 +2,9 @@ .top-area = render 'shared/issuable/nav', type: :merge_requests - .nav-controls - = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request" + - if current_user + .nav-controls + = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request" = render 'shared/issuable/filter', type: :merge_requests diff --git a/app/views/help/show.html.haml b/app/views/help/show.html.haml index be257b51b9e..f6ebd76af9d 100644 --- a/app/views/help/show.html.haml +++ b/app/views/help/show.html.haml @@ -1,3 +1,3 @@ - page_title @path.split("/").reverse.map(&:humanize) .documentation.wiki - = markdown @markdown.gsub('$your_email', current_user.try(:email) || "email@example.com") + = markdown @markdown diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml index c0328fe8842..1579d8f1662 100644 --- a/app/views/layouts/nav/_group_settings.html.haml +++ b/app/views/layouts/nav/_group_settings.html.haml @@ -1,10 +1,8 @@ - if current_user - can_admin_group = can?(current_user, :admin_group, @group) - can_edit = can?(current_user, :admin_group, @group) - - member = @group.members.find_by(user_id: current_user.id) - - can_leave = member && can?(current_user, :destroy_group_member, member) - - if can_admin_group || can_edit || can_leave + - if can_admin_group || can_edit .controls .dropdown.group-settings-dropdown %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'} @@ -14,13 +12,7 @@ - if can_admin_group = nav_link(path: 'groups#projects') do = link_to 'Projects', projects_group_path(@group), title: 'Projects' - - if (can_edit || can_leave) && can_admin_group + - if can_edit && can_admin_group %li.divider - - if can_edit %li = link_to 'Edit Group', edit_group_path(@group) - - if can_leave - %li - = link_to polymorphic_path([:leave, @group, :members]), - data: { confirm: leave_confirmation_message(@group) }, method: :delete, title: 'Leave group' do - Leave Group diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 7bd11f5727a..904d11c2cf4 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -6,23 +6,14 @@ = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right - can_edit = can?(current_user, :admin_project, @project) - -# We don't use @project.team.find_member because it searches for group members too... - - member = @project.members.find_by(user_id: current_user.id) - - can_leave = member && can?(current_user, :destroy_project_member, member) = render 'layouts/nav/project_settings', can_edit: can_edit - - if can_edit || can_leave + - if can_edit %li.divider - - if can_edit - %li - = link_to edit_project_path(@project) do - Edit Project - - if can_leave - %li - = link_to polymorphic_path([:leave, @project, :members]), - data: { confirm: leave_confirmation_message(@project) }, method: :delete, title: 'Leave project' do - Leave Project + %li + = link_to edit_project_path(@project) do + Edit Project .scrolling-tabs-container{ class: nav_control_class } .fade-left diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 844fce59704..d79a1a9f368 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -30,7 +30,7 @@ %br .clearfix .form-group.pull-left.global-notification-setting - = render 'shared/notifications/button', notification_setting: @global_notification_setting, left_align: true + = render 'shared/notifications/button', notification_setting: @global_notification_setting .clearfix diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml index 85d1793ecb9..057a720a54a 100644 --- a/app/views/projects/builds/_header.html.haml +++ b/app/views/projects/builds/_header.html.haml @@ -1,6 +1,6 @@ .content-block.build-header .header-content - = render "ci/status/icon_with_description", subject: build + = render 'ci/status/badge', status: @build.detailed_status(current_user) Build %strong ##{@build.id} in pipeline diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 108674dbba6..cdeb81372ee 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -46,8 +46,7 @@ - else This build is creating a deployment to #{environment_link_for_build(@build.project, @build)} - if environment.try(:last_deployment) - and will overwrite the - = link_to 'latest deployment', deployment_link(environment.last_deployment) + and will overwrite the #{deployment_link(environment.last_deployment, text: 'latest deployment')} .prepend-top-default - if @build.erased? diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 4257bb86859..f1cb0201032 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -9,7 +9,7 @@ %tr.build.commit{class: ('retried' if retried)} %td.status - = render "ci/status/icon_with_description", subject: build + = render "ci/status/badge", status: build.detailed_status(current_user) %td.branch-commit - if can?(current_user, :read_build, build) diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml new file mode 100644 index 00000000000..ad1a7360a8b --- /dev/null +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -0,0 +1,13 @@ +- is_playable = subject.playable? && can?(current_user, :update_build, @project) +- if is_playable + = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, data: { toggle: 'tooltip', title: "#{subject.name} - play", container: '.js-pipeline-graph', placement: 'bottom' } do + = ci_icon_for_status('play') + .ci-status-text= subject.name +- elsif can?(current_user, :read_build, @project) + = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject), data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.js-pipeline-graph', placement: 'bottom' } do + %span{class: "ci-status-icon ci-status-icon-#{subject.status}"} + = ci_icon_for_status(subject.status) + .ci-status-text= subject.name +- else + %span{class: "ci-status-icon ci-status-icon-#{subject.status}"} + = ci_icon_for_status(subject.status) diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 6dff955ea3d..3f05a21990f 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -4,7 +4,7 @@ %tr.commit %td.commit-link - = render "ci/status/icon_with_description", subject: pipeline + = render 'ci/status/badge', status: pipeline.detailed_status(current_user) %td = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml index f6e3d5e76f5..782f558e8b0 100644 --- a/app/views/projects/commit/_change.html.haml +++ b/app/views/projects/commit/_change.html.haml @@ -13,7 +13,7 @@ %a.close{href: "#", "data-dismiss" => "modal"} × %h3.page-title== #{label} this #{commit.change_type_title(current_user)} .modal-body - = form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: 'form-horizontal js-#{type}-form js-requires-input' do + = form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: "form-horizontal js-#{type}-form js-requires-input" do .form-group.branch = label_tag 'target_branch', target_label, class: 'control-label' .col-sm-10 @@ -23,12 +23,11 @@ - if can?(current_user, :push_code, @project) .js-create-merge-request-container .checkbox - - nonce = SecureRandom.hex - = label_tag "create_merge_request-#{nonce}" do - = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}" + = label_tag do + = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: nil Start a <strong>new merge request</strong> with these changes - else - = hidden_field_tag 'create_merge_request', 1 + = hidden_field_tag 'create_merge_request', 1, id: nil .form-actions = submit_tag label, class: 'btn btn-create' = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 65151ac3a56..c08ed8f6c16 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -1,13 +1,8 @@ .page-content-header .header-main-content - %strong Commit - %strong.monospace.js-details-short= @commit.short_id - = link_to("#", class: "js-details-expand hidden-xs hidden-sm") do - %span.text-expander - \... - %span.js-details-content.hide - %strong.monospace.commit-hash-full= @commit.id + %strong = clipboard_button(clipboard_text: @commit.id) + = @commit.short_id %span.hidden-xs authored #{time_ago_with_tooltip(@commit.authored_date)} %span by diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index c7b5c1124b3..08d3443b3d0 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -24,7 +24,7 @@ in = time_interval_in_words pipeline.duration - .row-content-block.build-content.middle-block.hidden + .row-content-block.build-content.middle-block.js-pipeline-graph.hidden = render "projects/pipelines/graph", pipeline: pipeline - if pipeline.yaml_errors.present? diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 01cd8fa0938..38e7fc4279c 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -146,7 +146,7 @@ such as compressing file revisions and removing unreachable objects. .col-lg-9 = link_to 'Housekeeping', housekeeping_namespace_project_path(@project.namespace, @project), - method: :post, class: "btn btn-save" + method: :post, class: "btn btn-default" %hr .row.prepend-top-default .col-lg-3 diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index a9235d6af35..a65a630f2d0 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -17,4 +17,6 @@ "project-stopped-environments-path" => project_environments_path(@project, scope: :stopped), "new-environment-path" => new_namespace_project_environment_path(@project.namespace, @project), "help-page-path" => help_page_path("ci/environments"), - "css-class" => container_class}} + "css-class" => container_class, + "commit-icon-svg" => custom_icon("icon_commit"), + "play-icon-svg" => custom_icon("icon_play")}} diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index 1dd07ae1a2a..9f444f076c0 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -8,7 +8,7 @@ %tr.generic_commit_status{class: ('retried' if retried)} %td.status - = render "ci/status/icon_with_description", subject: generic_commit_status + = render 'ci/status/badge', status: generic_commit_status.detailed_status(current_user) %td.generic_commit_status-link - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml new file mode 100644 index 00000000000..1bba0443154 --- /dev/null +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml @@ -0,0 +1,10 @@ +%a{ data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.js-pipeline-graph', placement: 'bottom' } } + - if subject.target_url + = link_to subject.target_url do + %span{class: "ci-status-icon ci-status-icon-#{subject.status}"} + = ci_icon_for_status(subject.status) + %span.ci-status-text= subject.name + - else + %span{class: "ci-status-icon ci-status-icon-#{subject.status}"} + = ci_icon_for_status(subject.status) + %span.ci-status-text= subject.name diff --git a/app/views/projects/group_links/update.js.haml b/app/views/projects/group_links/update.js.haml index af9a5b19060..55520fda494 100644 --- a/app/views/projects/group_links/update.js.haml +++ b/app/views/projects/group_links/update.js.haml @@ -1,3 +1,4 @@ :plain var $listItem = $('#{escape_javascript(render('shared/members/group', group_link: @group_link))}'); $("#group_member_#{@group_link.id} .list-item-name").replaceWith($listItem.find('.list-item-name')); + gl.utils.localTimeAgo($('.js-timeago'), $("#group_member_#{@group_link.id}")); diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index ba8895438c5..778a32e6345 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -65,7 +65,7 @@ .note-text.md = preserve do = note.redacted_note_html - = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true) + = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true) - if note_editable = render 'projects/notes/edit_form', note: note .note-awards diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index d05697b4ee3..b00ba2d5307 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -1,6 +1,6 @@ .page-content-header .header-main-content - = render "ci/status/icon_with_description", subject: @pipeline + = render 'ci/status/badge', status: @pipeline.detailed_status(current_user) %strong Pipeline ##{@commit.pipelines.last.id} triggered #{time_ago_with_tooltip(@commit.authored_date)} by = author_avatar(@commit, size: 24) diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml index 739e5930822..88af41aa835 100644 --- a/app/views/projects/pipelines/_with_tabs.html.haml +++ b/app/views/projects/pipelines/_with_tabs.html.haml @@ -12,7 +12,7 @@ .tab-content #js-tab-pipeline.tab-pane - .build-content.middle-block + .build-content.middle-block.js-pipeline-graph = render "projects/pipelines/graph", pipeline: pipeline #js-tab-builds.tab-pane diff --git a/app/views/projects/project_members/update.js.haml b/app/views/projects/project_members/update.js.haml index 91927181efb..d15f4310ff5 100644 --- a/app/views/projects/project_members/update.js.haml +++ b/app/views/projects/project_members/update.js.haml @@ -1,3 +1,4 @@ :plain var $listItem = $('#{escape_javascript(render('shared/members/member', member: @project_member))}'); $("##{dom_id(@project_member)} .list-item-name").replaceWith($listItem.find('.list-item-name')); + gl.utils.localTimeAgo($('.js-timeago'), $("##{dom_id(@project_member)}")); diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml index 32e1f8a21b0..068a6610350 100644 --- a/app/views/projects/snippets/_actions.html.haml +++ b/app/views/projects/snippets/_actions.html.haml @@ -1,13 +1,13 @@ .hidden-xs - - if can?(current_user, :create_project_snippet, @project) - = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New snippet" do - New snippet - - if can?(current_user, :update_project_snippet, @snippet) - = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do - Delete - if can?(current_user, :update_project_snippet, @snippet) - = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do + = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped" do Edit + - if can?(current_user, :update_project_snippet, @snippet) + = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do + Delete + - if can?(current_user, :create_project_snippet, @project) + = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do + New snippet - if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet) .visible-xs-block.dropdown %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index e77e1b026f6..84e05cd6d88 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -1,11 +1,19 @@ - page_title "Snippets" -.sub-header-block - - if can?(current_user, :create_project_snippet, @project) - = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new btn-wide-on-sm pull-right", title: "New snippet" do - New snippet +- if current_user + .top-area + - include_private = @project.team.member?(current_user) || current_user.admin? + = render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private } + + .nav-controls.hidden-xs + - if can?(current_user, :create_project_snippet, @project) + = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New snippet" do + New snippet - .oneline - Share code pastes with others out of git repository +- if can?(current_user, :create_project_snippet, @project) + .visible-xs + + = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new btn-block", title: "New snippet" do + New snippet = render 'snippets/snippets' diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml index 21e378b8735..d37c376c36b 100644 --- a/app/views/projects/tree/_tree_content.html.haml +++ b/app/views/projects/tree/_tree_content.html.haml @@ -5,14 +5,11 @@ %tr %th Name %th.hidden-xs - .pull-left Last Commit + .pull-left Last commit .last-commit.hidden-sm.pull-left - - %i.fa.fa-angle-right - - %small.light + %small.light + = clipboard_button(clipboard_text: @commit.id) = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" - – = time_ago_with_tooltip(@commit.committed_date) = @commit.full_title %small.commit-history-link-spacer | diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml index c414acb6a11..027d42396b4 100644 --- a/app/views/search/results/_snippet_title.html.haml +++ b/app/views/search/results/_snippet_title.html.haml @@ -14,7 +14,7 @@ = link_to snippet_title.project.name_with_namespace, namespace_project_path(snippet_title.project.namespace, snippet_title.project) .snippet-info - = "##{snippet_title.id}" + = snippet_title.to_reference %span by = link_to user_snippets_path(snippet_title.author) do diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml index 67c145cef17..e50ab5fea09 100644 --- a/app/views/shared/_event_filter.html.haml +++ b/app/views/shared/_event_filter.html.haml @@ -6,6 +6,6 @@ = event_filter_link EventFilter.merged, 'Merge events' - if event_filter_visible(:issues) = event_filter_link EventFilter.issue, 'Issue events' - - if event_filter_visible(:issues) + - if comments_visible? = event_filter_link EventFilter.comments, 'Comments' = event_filter_link EventFilter.team, 'Team' diff --git a/app/views/shared/members/_access_request_buttons.html.haml b/app/views/shared/members/_access_request_buttons.html.haml index eff914398bb..e166dfab710 100644 --- a/app/views/shared/members/_access_request_buttons.html.haml +++ b/app/views/shared/members/_access_request_buttons.html.haml @@ -1,10 +1,16 @@ -- if can?(current_user, :request_access, source) - - if requester = source.requesters.find_by(user_id: current_user.id) - = link_to 'Withdraw Access Request', polymorphic_path([:leave, source, :members]), - method: :delete, - data: { confirm: remove_member_message(requester) }, - class: 'btn' - - else - = link_to 'Request Access', polymorphic_path([:request_access, source, :members]), - method: :post, - class: 'btn' +- model_name = source.model_name.to_s.downcase + +- if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id)) + = link_to "Leave #{model_name}", polymorphic_path([:leave, source, :members]), + method: :delete, + data: { confirm: leave_confirmation_message(source) }, + class: 'btn' +- elsif requester = source.requesters.find_by(user_id: current_user.id) + = link_to 'Withdraw Access Request', polymorphic_path([:leave, source, :members]), + method: :delete, + data: { confirm: remove_member_message(requester) }, + class: 'btn' +- elsif source.request_access_enabled && can?(current_user, :request_access, source) + = link_to 'Request Access', polymorphic_path([:request_access, source, :members]), + method: :post, + class: 'btn' diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index 1c0346bbc78..8928de9097b 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -1,7 +1,8 @@ - group_link = local_assigns[:group_link] - group = group_link.group - can_admin_member = can?(current_user, :admin_project_member, @project) -%li.member.group_member{ id: "group_member_#{group_link.id}" } +- dom_id = "group_member_#{group_link.id}" +%li.member.group_member{ id: dom_id } %span{ class: "list-item-name" } = image_tag group_icon(group), class: "avatar s40", alt: '' %strong @@ -14,7 +15,23 @@ Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)} .controls.member-controls = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do - = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can_admin_member + = hidden_field_tag "group_link[group_access]", group_link.group_access + .member-form-control.dropdown.append-right-5 + %button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button", + disabled: !can_admin_member, + data: { toggle: "dropdown", field_name: "group_link[group_access]" } } + %span.dropdown-toggle-text + = group_link.human_access + = icon("chevron-down") + .dropdown-menu.dropdown-select.dropdown-menu-align-right.dropdown-menu-selectable + = dropdown_title("Change permissions") + .dropdown-content + %ul + - Gitlab::Access.options.each do |role, role_id| + %li + = link_to role, "javascript:void(0)", + class: ("is-active" if group_link.group_access == role_id), + data: { id: role_id, el_id: dom_id } .prepend-left-5.clearable-input.member-form-control = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can_admin_member %i.clear-icon.js-clear-input diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index e67f7d5a352..659d4c905fc 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -48,9 +48,25 @@ - if show_controls && (member.respond_to?(:group) && @group) || (member.respond_to?(:project) && @project) - if user != current_user = form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f| - = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can_admin_member + = f.hidden_field :access_level + .member-form-control.dropdown.append-right-5 + %button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button", + disabled: !can_admin_member, + data: { toggle: "dropdown", field_name: "#{f.object_name}[access_level]" } } + %span.dropdown-toggle-text + = member.human_access + = icon("chevron-down") + .dropdown-menu.dropdown-select.dropdown-menu-align-right.dropdown-menu-selectable + = dropdown_title("Change permissions") + .dropdown-content + %ul + - Gitlab::Access.options.each do |role, role_id| + %li + = link_to role, "javascript:void(0)", + class: ("is-active" if member.access_level == role_id), + data: { id: role_id, el_id: dom_id(member) } .prepend-left-5.clearable-input.member-form-control - = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can_admin_member + = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can_admin_member, data: { el_id: dom_id(member) } %i.clear-icon.js-clear-input - else %span.member-access-text= member.human_access diff --git a/app/views/shared/milestones/_summary.html.haml b/app/views/shared/milestones/_summary.html.haml index 0a237136959..d27fba805a3 100644 --- a/app/views/shared/milestones/_summary.html.haml +++ b/app/views/shared/milestones/_summary.html.haml @@ -25,8 +25,10 @@ %span.milestone-stat %strong== #{milestone.percent_complete(current_user)}% complete - %span.milestone-stat - %span.remaining-days= milestone_remaining_days(milestone) + - remaining_days = milestone_remaining_days(milestone) + - if remaining_days.present? + %span.milestone-stat + %span.remaining-days= remaining_days .milestone-progress-buttons %span.tab-issues-buttons diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml index 1f7df0bcd19..fbad0d05de3 100644 --- a/app/views/shared/notifications/_button.html.haml +++ b/app/views/shared/notifications/_button.html.haml @@ -1,4 +1,3 @@ -- left_align = local_assigns[:left_align] - if notification_setting .dropdown.notification-dropdown = form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f| @@ -19,7 +18,7 @@ = notification_title(notification_setting.level) = icon("caret-down") - = render "shared/notifications/notification_dropdown", notification_setting: notification_setting, left_align: left_align + = render "shared/notifications/notification_dropdown", notification_setting: notification_setting = content_for :scripts_body do = render "shared/notifications/custom_notifications", notification_setting: notification_setting diff --git a/app/views/shared/notifications/_notification_dropdown.html.haml b/app/views/shared/notifications/_notification_dropdown.html.haml index d3258ee64cb..85ad74f9a39 100644 --- a/app/views/shared/notifications/_notification_dropdown.html.haml +++ b/app/views/shared/notifications/_notification_dropdown.html.haml @@ -1,5 +1,4 @@ -- left_align = local_assigns[:left_align] -%ul.dropdown-menu.dropdown-menu-no-wrap.dropdown-menu-selectable.dropdown-menu-large{ role: "menu", class: [notifications_menu_identifier("dropdown", notification_setting), ("dropdown-menu-align-right" unless left_align)] } +%ul.dropdown-menu.dropdown-menu-no-wrap.dropdown-menu-selectable.dropdown-menu-large{ role: "menu", class: [notifications_menu_identifier("dropdown", notification_setting)] } - NotificationSetting.levels.each_key do |level| - next if level == "custom" - next if level == "global" && notification_setting.source.nil? diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml index d7506e07ff6..d084f5e9684 100644 --- a/app/views/shared/snippets/_header.html.haml +++ b/app/views/shared/snippets/_header.html.haml @@ -8,10 +8,6 @@ %span.creator authored = time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago') - - if @snippet.updated_at != @snippet.created_at - %span - = icon('edit', title: 'edited') - = time_ago_with_tooltip(@snippet.updated_at, placement: 'bottom', html_class: 'snippet_edited_ago') by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title", avatar_class: "hidden-xs")} .snippet-actions @@ -20,5 +16,9 @@ - else = render "snippets/actions" -%h2.snippet-title.prepend-top-0.append-bottom-0 - = markdown_field(@snippet, :title) +.snippet-header + %h2.snippet-title.prepend-top-0.append-bottom-0 + = markdown_field(@snippet, :title) + + - if @snippet.updated_at != @snippet.created_at + = edited_time_ago_with_tooltip(@snippet, placement: 'bottom', html_class: 'snippet-edited-ago') diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml index ea17bec8677..5d2d2317f22 100644 --- a/app/views/shared/snippets/_snippet.html.haml +++ b/app/views/shared/snippets/_snippet.html.haml @@ -1,17 +1,16 @@ +- link_project = local_assigns.fetch(:link_project, false) + %li.snippet-row = image_tag avatar_icon(snippet.author_email), class: "avatar s40 hidden-xs", alt: '' .title = link_to reliable_snippet_path(snippet) do = snippet.title - - if snippet.private? - %span.label.label-gray.hidden-xs - = icon('lock') - private - %span.monospace.pull-right.hidden-xs - = snippet.file_name + - if snippet.file_name + %span.snippet-filename.monospace.hidden-xs + = snippet.file_name - %ul.controls.visible-xs + %ul.controls %li - note_count = snippet.notes.user.count = link_to reliable_snippet_path(snippet, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do @@ -22,11 +21,17 @@ = visibility_level_label(snippet.visibility_level) = visibility_level_icon(snippet.visibility_level, fw: false) - %small.pull-right.cgray.hidden-xs - - if snippet.project_id? - = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project) - - .snippet-info.hidden-xs + .snippet-info + #{snippet.to_reference} · + authored #{time_ago_with_tooltip(snippet.created_at, placement: 'bottom', html_class: 'snippet-created-ago')} + by = link_to user_snippets_path(snippet.author) do = snippet.author_name - authored #{time_ago_with_tooltip(snippet.created_at)} + - if link_project && snippet.project_id? + %span.hidden-xs + in + = link_to namespace_project_path(snippet.project.namespace, snippet.project) do + = snippet.project.name_with_namespace + + .pull-right.snippet-updated-at + %span updated #{time_ago_with_tooltip(snippet.updated_at, placement: 'bottom')} diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml index 1d0e549ed3d..95fc7198104 100644 --- a/app/views/snippets/_actions.html.haml +++ b/app/views/snippets/_actions.html.haml @@ -1,13 +1,13 @@ .hidden-xs - - if current_user - = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New snippet" do - New snippet - - if can?(current_user, :admin_personal_snippet, @snippet) - = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do - Delete - if can?(current_user, :update_personal_snippet, @snippet) - = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do + = link_to edit_snippet_path(@snippet), class: "btn btn-grouped" do Edit + - if can?(current_user, :admin_personal_snippet, @snippet) + = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do + Delete + - if current_user + = link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do + New snippet - if current_user .visible-xs-block.dropdown %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml index 77b66ca74b6..ac3701233ad 100644 --- a/app/views/snippets/_snippets.html.haml +++ b/app/views/snippets/_snippets.html.haml @@ -1,8 +1,9 @@ - remote = local_assigns.fetch(:remote, false) +- link_project = local_assigns.fetch(:link_project, false) .snippets-list-holder %ul.content-list - = render partial: 'shared/snippets/snippet', collection: @snippets + = render partial: 'shared/snippets/snippet', collection: @snippets, locals: { link_project: link_project } - if @snippets.empty? %li .nothing-here-block Nothing here. diff --git a/app/views/snippets/_snippets_scope_menu.html.haml b/app/views/snippets/_snippets_scope_menu.html.haml new file mode 100644 index 00000000000..2dda5fed647 --- /dev/null +++ b/app/views/snippets/_snippets_scope_menu.html.haml @@ -0,0 +1,31 @@ +- subject = local_assigns.fetch(:subject, current_user) +- include_private = local_assigns.fetch(:include_private, false) + +.nav-links.snippet-scope-menu + %li{ class: ("active" unless params[:scope]) } + = link_to subject_snippets_path(subject) do + All + %span.badge + - if include_private + = subject.snippets.count + - else + = subject.snippets.public_and_internal.count + + - if include_private + %li{ class: ("active" if params[:scope] == "are_private") } + = link_to subject_snippets_path(subject, scope: 'are_private') do + Private + %span.badge + = subject.snippets.are_private.count + + %li{ class: ("active" if params[:scope] == "are_internal") } + = link_to subject_snippets_path(subject, scope: 'are_internal') do + Internal + %span.badge + = subject.snippets.are_internal.count + + %li{ class: ("active" if params[:scope] == "are_public") } + = link_to subject_snippets_path(subject, scope: 'are_public') do + Public + %span.badge + = subject.snippets.are_public.count |