diff options
Diffstat (limited to 'app')
72 files changed, 480 insertions, 244 deletions
diff --git a/app/assets/javascripts/boards/utils/query_data.js b/app/assets/javascripts/boards/utils/query_data.js index 2cd3c146f11..65315979df7 100644 --- a/app/assets/javascripts/boards/utils/query_data.js +++ b/app/assets/javascripts/boards/utils/query_data.js @@ -5,7 +5,7 @@ export default (path, extraData) => path.split('&').reduce((dataParam, filterPar const paramSplit = filterParam.split('='); const paramKeyNormalized = paramSplit[0].replace('[]', ''); const isArray = paramSplit[0].indexOf('[]'); - const value = decodeURIComponent(paramSplit[1]).replace(/\+/g, ' '); + const value = decodeURIComponent(paramSplit[1].replace(/\+/g, ' ')); if (isArray !== -1) { if (!data[paramKeyNormalized]) { diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue index a9e819b8a3c..843564ce016 100644 --- a/app/assets/javascripts/deploy_keys/components/key.vue +++ b/app/assets/javascripts/deploy_keys/components/key.vue @@ -1,11 +1,15 @@ <script> import actionBtn from './action_btn.vue'; import { getTimeago } from '../../lib/utils/datetime_utility'; + import tooltip from '../../vue_shared/directives/tooltip'; export default { components: { actionBtn, }, + directives: { + tooltip, + }, props: { deployKey: { type: Object, @@ -32,6 +36,9 @@ isEnabled(id) { return this.store.findEnabledKey(id) !== undefined; }, + tooltipTitle(project) { + return project.can_push ? 'Write access allowed' : 'Read access only'; + }, }, }; </script> @@ -52,21 +59,23 @@ <div class="description"> {{ deployKey.fingerprint }} </div> - <div - v-if="deployKey.can_push" - class="write-access-allowed" - > - Write access allowed - </div> </div> <div class="deploy-key-content prepend-left-default deploy-key-projects"> <a - v-for="(project, i) in deployKey.projects" - class="label deploy-project-label" - :href="project.full_path" + v-for="(deployKeysProject, i) in deployKey.deploy_keys_projects" :key="i" + class="label deploy-project-label" + :href="deployKeysProject.project.full_path" + :title="tooltipTitle(deployKeysProject)" + v-tooltip > - {{ project.full_name }} + {{ deployKeysProject.project.full_name }} + <i + v-if="!deployKeysProject.can_push" + aria-hidden="true" + class="fa fa-lock" + > + </i> </a> </div> <div class="deploy-key-content"> diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 1c6336073e9..b7b0162e307 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -26,7 +26,6 @@ import ProjectsList from './projects_list'; import UserCallout from './user_callout'; import ShortcutsWiki from './shortcuts_wiki'; import BlobViewer from './blob/viewer/index'; -import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select'; import UsersSelect from './users_select'; import GfmAutoComplete from './gfm_auto_complete'; import Star from './star'; @@ -158,6 +157,11 @@ import Activities from './activities'; case 'dashboard:todos:index': import('./pages/dashboard/todos/index').then(callDefault).catch(fail); break; + case 'admin:jobs:index': + import('./pages/admin/jobs/index') + .then(callDefault) + .catch(fail); + break; case 'dashboard:projects:index': case 'dashboard:projects:starred': import('./pages/dashboard/projects') @@ -255,7 +259,6 @@ import Activities from './activities'; new LabelsSelect(); new MilestoneSelect(); new IssuableTemplateSelectors(); - new AutoWidthDropdownSelect($('.js-target-branch-select')).init(); break; case 'projects:tags:new': import('./pages/projects/tags/new') @@ -559,6 +562,8 @@ import Activities from './activities'; .catch(fail); break; case 'projects:clusters:show': + case 'projects:clusters:update': + case 'projects:clusters:destroy': import('./pages/projects/clusters/show') .then(callDefault) .catch(fail); diff --git a/app/assets/javascripts/issuable/auto_width_dropdown_select.js b/app/assets/javascripts/issuable/auto_width_dropdown_select.js index 2203a56315e..14a2bfbe4e0 100644 --- a/app/assets/javascripts/issuable/auto_width_dropdown_select.js +++ b/app/assets/javascripts/issuable/auto_width_dropdown_select.js @@ -11,6 +11,14 @@ class AutoWidthDropdownSelect { const dropdownClass = this.dropdownClass; this.$selectElement.select2({ dropdownCssClass: dropdownClass, + ...AutoWidthDropdownSelect.selectOptions(this.dropdownClass), + }); + + return this; + } + + static selectOptions(dropdownClass) { + return { dropdownCss() { let resultantWidth = 'auto'; const $dropdown = $(`.${dropdownClass}`); @@ -29,9 +37,7 @@ class AutoWidthDropdownSelect { maxWidth: offsetParentWidth, }; }, - }); - - return this; + }; } } diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js index 57dcaa0e1ac..fdfad0b6a4f 100644 --- a/app/assets/javascripts/issuable_form.js +++ b/app/assets/javascripts/issuable_form.js @@ -6,6 +6,7 @@ import Autosave from './autosave'; import UsersSelect from './users_select'; import GfmAutoComplete from './gfm_auto_complete'; import ZenMode from './zen_mode'; +import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select'; import { parsePikadayDate, pikadayToString } from './lib/utils/datefix'; export default class IssuableForm { @@ -46,6 +47,12 @@ export default class IssuableForm { }); calendar.setDate(parsePikadayDate($issuableDueDate.val())); } + + this.$targetBranchSelect = $('.js-target-branch-select', this.form); + + if (this.$targetBranchSelect.length) { + this.initTargetBranchDropdown(); + } } initAutosave() { @@ -104,4 +111,37 @@ export default class IssuableForm { addWip() { this.titleField.val(`WIP: ${(this.titleField.val())}`); } + + initTargetBranchDropdown() { + this.$targetBranchSelect.select2({ + ...AutoWidthDropdownSelect.selectOptions('js-target-branch-select'), + ajax: { + url: this.$targetBranchSelect.data('endpoint'), + dataType: 'JSON', + quietMillis: 250, + data(search) { + return { + search, + }; + }, + results(data) { + return { + // `data` keys are translated so we can't just access them with a string based key + results: data[Object.keys(data)[0]].map(name => ({ + id: name, + text: name, + })), + }; + }, + }, + initSelection(el, callback) { + const val = el.val(); + + callback({ + id: val, + text: val, + }); + }, + }); + } } diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js index 8f32dcc94e2..9b5092c5e3f 100644 --- a/app/assets/javascripts/job.js +++ b/app/assets/javascripts/job.js @@ -3,7 +3,6 @@ import { visitUrl } from './lib/utils/url_utility'; import bp from './breakpoints'; import { numberToHumanSize } from './lib/utils/number_utils'; import { setCiStatusFavicon } from './lib/utils/common_utils'; -import { timeFor } from './lib/utils/datetime_utility'; export default class Job { constructor(options) { @@ -71,7 +70,6 @@ export default class Job { .off('resize.build') .on('resize.build', _.throttle(this.sidebarOnResize.bind(this), 100)); - this.updateArtifactRemoveDate(); this.initAffixTopArea(); this.getBuildTrace(); @@ -261,16 +259,7 @@ export default class Job { sidebarOnClick() { if (this.shouldHideSidebarForViewport()) this.toggleSidebar(); } - // eslint-disable-next-line class-methods-use-this, consistent-return - updateArtifactRemoveDate() { - const $date = $('.js-artifacts-remove'); - if ($date.length) { - const date = $date.text(); - return $date.text( - timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3'))), - ); - } - } + // eslint-disable-next-line class-methods-use-this populateJobs(stage) { $('.build-job').hide(); diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index f7a1c9f1e40..664e793fc8e 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -231,7 +231,7 @@ export default class LabelsSelect { selectedClass.push('label-item'); $a.attr('data-label-id', label.id); } - $a.addClass(selectedClass.join(' ')).html(colorEl + " " + label.title); + $a.addClass(selectedClass.join(' ')).html(`${colorEl} ${_.escape(label.title)}`); // Return generated html return $li.html($a).prop('outerHTML'); }, diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js index e26bf437efc..bedd50de1bb 100644 --- a/app/assets/javascripts/merge_request.js +++ b/app/assets/javascripts/merge_request.js @@ -1,6 +1,7 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, max-len, prefer-arrow-callback */ import 'vendor/jquery.waitforimages'; +import { __ } from '~/locale'; import TaskList from './task_list'; import MergeRequestTabs from './merge_request_tabs'; import IssuablesHelper from './helpers/issuables_helper'; @@ -110,12 +111,12 @@ MergeRequest.prototype.initCommitMessageListeners = function() { }); }; -MergeRequest.updateStatusText = function(classToRemove, classToAdd, newStatusText) { +MergeRequest.setStatusBoxToMerged = function() { $('.detail-page-header .status-box') - .removeClass(classToRemove) - .addClass(classToAdd) + .removeClass('status-box-open') + .addClass('status-box-mr-merged') .find('span') - .text(newStatusText); + .text(__('Merged')); }; MergeRequest.decreaseCounter = function(by = 1) { diff --git a/app/assets/javascripts/notebook/cells/markdown.vue b/app/assets/javascripts/notebook/cells/markdown.vue index d0ec70f1fcf..3d09d24b6ab 100644 --- a/app/assets/javascripts/notebook/cells/markdown.vue +++ b/app/assets/javascripts/notebook/cells/markdown.vue @@ -1,6 +1,7 @@ <script> /* global katex */ import marked from 'marked'; + import sanitize from 'sanitize-html'; import Prompt from './prompt.vue'; const renderer = new marked.Renderer(); @@ -82,7 +83,12 @@ }, computed: { markdown() { - return marked(this.cell.source.join('').replace(/\\/g, '\\\\')); + return sanitize(marked(this.cell.source.join('').replace(/\\/g, '\\\\')), { + allowedTags: false, + allowedAttributes: { + '*': ['class'], + }, + }); }, }, }; diff --git a/app/assets/javascripts/notebook/cells/output/html.vue b/app/assets/javascripts/notebook/cells/output/html.vue index ebba5954de9..0535ee7afa8 100644 --- a/app/assets/javascripts/notebook/cells/output/html.vue +++ b/app/assets/javascripts/notebook/cells/output/html.vue @@ -1,4 +1,5 @@ <script> + import sanitize from 'sanitize-html'; import Prompt from '../prompt.vue'; export default { @@ -11,12 +12,24 @@ required: true, }, }, + computed: { + sanitizedOutput() { + return sanitize(this.rawCode, { + allowedTags: sanitize.defaults.allowedTags.concat([ + 'img', 'svg', + ]), + allowedAttributes: { + img: ['src'], + }, + }); + }, + }, }; </script> <template> <div class="output"> <prompt /> - <div v-html="rawCode"></div> + <div v-html="sanitizedOutput"></div> </div> </template> diff --git a/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue new file mode 100644 index 00000000000..555725cbe12 --- /dev/null +++ b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue @@ -0,0 +1,47 @@ +<script> + import axios from '~/lib/utils/axios_utils'; + import Flash from '~/flash'; + import modal from '~/vue_shared/components/modal.vue'; + import { s__ } from '~/locale'; + import { redirectTo } from '~/lib/utils/url_utility'; + + export default { + components: { + modal, + }, + props: { + url: { + type: String, + required: true, + }, + }, + computed: { + text() { + return s__('AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running.'); + }, + }, + methods: { + onSubmit() { + return axios.post(this.url) + .then((response) => { + // follow the rediect to refresh the page + redirectTo(response.request.responseURL); + }) + .catch((error) => { + Flash(s__('AdminArea|Stopping jobs failed')); + throw error; + }); + }, + }, + }; +</script> + +<template> + <modal + id="stop-jobs-modal" + :title="s__('AdminArea|Stop all jobs?')" + :text="text" + kind="danger" + :primary-button-label="s__('AdminArea|Stop jobs')" + @submit="onSubmit" /> +</template> diff --git a/app/assets/javascripts/pages/admin/jobs/index/index.js b/app/assets/javascripts/pages/admin/jobs/index/index.js new file mode 100644 index 00000000000..0e004bd9174 --- /dev/null +++ b/app/assets/javascripts/pages/admin/jobs/index/index.js @@ -0,0 +1,29 @@ +import Vue from 'vue'; + +import Translate from '~/vue_shared/translate'; + +import stopJobsModal from './components/stop_jobs_modal.vue'; + +Vue.use(Translate); + +export default () => { + const stopJobsButton = document.getElementById('stop-jobs-button'); + + // eslint-disable-next-line no-new + new Vue({ + el: '#stop-jobs-modal', + components: { + stopJobsModal, + }, + mounted() { + stopJobsButton.classList.remove('disabled'); + }, + render(createElement) { + return createElement('stop-jobs-modal', { + props: { + url: stopJobsButton.dataset.url, + }, + }); + }, + }); +}; diff --git a/app/assets/javascripts/pages/search/show/search.js b/app/assets/javascripts/pages/search/show/search.js index d44195f6b72..dc621bc87c0 100644 --- a/app/assets/javascripts/pages/search/show/search.js +++ b/app/assets/javascripts/pages/search/show/search.js @@ -15,6 +15,7 @@ export default class Search { $groupDropdown.glDropdown({ selectable: true, filterable: true, + filterRemote: true, fieldName: 'group_id', search: { fields: ['full_name'], @@ -43,6 +44,7 @@ export default class Search { $projectDropdown.glDropdown({ selectable: true, filterable: true, + filterRemote: true, fieldName: 'project_id', search: { fields: ['name'], diff --git a/app/assets/javascripts/pipelines/components/async_button.vue b/app/assets/javascripts/pipelines/components/async_button.vue index 4ad3f66ee8c..77553ca67cc 100644 --- a/app/assets/javascripts/pipelines/components/async_button.vue +++ b/app/assets/javascripts/pipelines/components/async_button.vue @@ -3,6 +3,7 @@ import eventHub from '../event_hub'; import loadingIcon from '../../vue_shared/components/loading_icon.vue'; + import icon from '../../vue_shared/components/icon.vue'; import tooltip from '../../vue_shared/directives/tooltip'; export default { @@ -11,6 +12,7 @@ }, components: { loadingIcon, + icon, }, props: { endpoint: { @@ -41,9 +43,6 @@ }; }, computed: { - iconClass() { - return `fa fa-${this.icon}`; - }, buttonClass() { return `btn ${this.cssClass}`; }, @@ -76,10 +75,9 @@ data-container="body" data-placement="top" :disabled="isLoading"> - <i - :class="iconClass" - aria-hidden="true"> - </i> + <icon + :name="icon" + /> <loading-icon v-if="isLoading" /> </button> </template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue index efda36c12d6..3297af7bde4 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue @@ -1,7 +1,7 @@ <script> - import playIconSvg from 'icons/_icon_play.svg'; import eventHub from '../event_hub'; import loadingIcon from '../../vue_shared/components/loading_icon.vue'; + import icon from '../../vue_shared/components/icon.vue'; import tooltip from '../../vue_shared/directives/tooltip'; export default { @@ -10,6 +10,7 @@ }, components: { loadingIcon, + icon, }, props: { actions: { @@ -19,7 +20,6 @@ }, data() { return { - playIconSvg, isLoading: false, }; }, @@ -52,7 +52,10 @@ aria-label="Manual job" :disabled="isLoading" > - <span v-html="playIconSvg"></span> + <icon + name="play" + class="icon-play" + /> <i class="fa fa-caret-down" aria-hidden="true"> diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue index 670b777199c..d87e24cc8a7 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue @@ -312,7 +312,7 @@ :endpoint="pipeline.cancel_path" css-class="js-pipelines-cancel-button btn-remove" title="Cancel" - icon="remove" + icon="close" confirm-action-message="Are you sure you want to cancel this pipeline?" /> </div> diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js index d2f0d7410da..5351873e945 100644 --- a/app/assets/javascripts/shortcuts.js +++ b/app/assets/javascripts/shortcuts.js @@ -62,7 +62,7 @@ export default class Shortcuts { e.preventDefault(); const performanceBarCookieName = 'perf_bar_enabled'; if (Cookies.get(performanceBarCookieName) === 'true') { - Cookies.remove(performanceBarCookieName, { path: '/' }); + Cookies.set(performanceBarCookieName, 'false', { path: '/' }); } else { Cookies.set(performanceBarCookieName, 'true', { path: '/' }); } diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js index 60f42c46ffe..f16414ad5c0 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js @@ -166,7 +166,7 @@ export default { // If state is merged we should update the widget and stop the polling eventHub.$emit('MRWidgetUpdateRequested'); eventHub.$emit('FetchActionsContent'); - MergeRequest.updateStatusText('status-box-open', 'status-box-merged', 'Merged'); + MergeRequest.setStatusBoxToMerged(); MergeRequest.hideCloseButton(); MergeRequest.decreaseCounter(); stopPolling(); diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 1d2303a3a2b..4c6b32630e1 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -30,7 +30,7 @@ @include set-visible; min-height: $dropdown-min-height; max-height: $dropdown-max-height; - overflow: auto; + overflow-y: auto; @media (max-width: $screen-xs-max) { width: 100%; diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 1e91db5af9b..d835d49d8b2 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -124,6 +124,10 @@ &.wiki { padding: $gl-padding; + + @media (min-width: $screen-md-min) { + padding: $gl-padding * 2; + } } &.blob-no-preview { diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 3b7256f3000..634593aefd0 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -303,6 +303,8 @@ .projects-dropdown-menu { padding: 0; + overflow-y: initial; + max-height: initial; } .dropdown-chevron { diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss index 1d8bd26cf1a..d8c57a0e2d9 100644 --- a/app/assets/stylesheets/framework/issue_box.scss +++ b/app/assets/stylesheets/framework/issue_box.scss @@ -24,15 +24,13 @@ font-size: $gl-font-size; line-height: 25px; + &.status-box-closed, &.status-box-mr-closed { background-color: $gl-danger; } - &.status-box-issue-closed { - background-color: $gl-primary; - } - - &.status-box-merged { + &.status-box-issue-closed, + &.status-box-mr-merged { background-color: $gl-primary; } diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index 924472f2d7e..51ae09777fd 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -12,6 +12,7 @@ min-height: $modal-body-height; position: relative; padding: #{3 * $grid-size} #{2 * $grid-size}; + text-align: left; .form-actions { margin: #{2 * $grid-size} #{-2 * $grid-size} #{-2 * $grid-size}; diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss index 3b35beb7695..cfef6476d4d 100644 --- a/app/assets/stylesheets/pages/cycle_analytics.scss +++ b/app/assets/stylesheets/pages/cycle_analytics.scss @@ -117,47 +117,6 @@ top: $gl-padding-top; } - .content-list { - li { - padding: 18px $gl-padding $gl-padding; - - .container-fluid { - padding: 0; - } - } - - .title-col { - p { - margin: 0; - - &.title { - line-height: 19px; - font-size: 14px; - font-weight: $gl-font-weight-bold; - color: $gl-text-color; - } - - &.text { - color: $layout-link-gray; - - &.value-col { - color: $gl-text-color; - } - } - } - } - - .value-col { - text-align: right; - - span { - position: relative; - vertical-align: middle; - top: 3px; - } - } - } - .fa-spinner { font-size: 28px; position: relative; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 370b07663fd..766e02b12ea 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -69,13 +69,6 @@ border-color: $border-white-normal; } } - - .btn { - .icon-play { - height: 13px; - width: 12px; - } - } } .btn .text-center { diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb index a7ab481519d..b0c4c31cffc 100644 --- a/app/controllers/admin/deploy_keys_controller.rb +++ b/app/controllers/admin/deploy_keys_controller.rb @@ -50,10 +50,10 @@ class Admin::DeployKeysController < Admin::ApplicationController end def create_params - params.require(:deploy_key).permit(:key, :title, :can_push) + params.require(:deploy_key).permit(:key, :title) end def update_params - params.require(:deploy_key).permit(:title, :can_push) + params.require(:deploy_key).permit(:title) end end diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb index 77e3c95d197..2b47819303e 100644 --- a/app/controllers/admin/hooks_controller.rb +++ b/app/controllers/admin/hooks_controller.rb @@ -59,11 +59,9 @@ class Admin::HooksController < Admin::ApplicationController def hook_params params.require(:hook).permit( :enable_ssl_verification, - :push_events, - :tag_push_events, - :repository_update_events, :token, - :url + :url, + *SystemHook.triggers.values ) end end diff --git a/app/controllers/admin/jobs_controller.rb b/app/controllers/admin/jobs_controller.rb index 5162273ef8a..ae7a7f6279c 100644 --- a/app/controllers/admin/jobs_controller.rb +++ b/app/controllers/admin/jobs_controller.rb @@ -20,6 +20,6 @@ class Admin::JobsController < Admin::ApplicationController def cancel_all Ci::Build.running_or_pending.each(&:cancel) - redirect_to admin_jobs_path + redirect_to admin_jobs_path, status: 303 end end diff --git a/app/controllers/concerns/with_performance_bar.rb b/app/controllers/concerns/with_performance_bar.rb index 230bbe4b1aa..6a8b1a4de7b 100644 --- a/app/controllers/concerns/with_performance_bar.rb +++ b/app/controllers/concerns/with_performance_bar.rb @@ -6,13 +6,22 @@ module WithPerformanceBar end def peek_enabled? - return true if Rails.env.development? return false unless Gitlab::PerformanceBar.enabled?(current_user) if RequestStore.active? - RequestStore.fetch(:peek_enabled) { cookies[:perf_bar_enabled].present? } + RequestStore.fetch(:peek_enabled) { cookie_or_default_value } else - cookies[:perf_bar_enabled].present? + cookie_or_default_value + end + end + + private + + def cookie_or_default_value + if cookies[:perf_bar_enabled].present? + cookies[:perf_bar_enabled] == 'true' + else + cookies[:perf_bar_enabled] = 'true' if Rails.env.development? end end end diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index f013d21275e..acf6aaf57f4 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -75,8 +75,6 @@ class Groups::MilestonesController < Groups::ApplicationController end def milestones - search_params = params.merge(group_ids: group.id) - milestones = MilestonesFinder.new(search_params).execute legacy_milestones = GroupMilestone.build_collection(group, group_projects, params) @@ -94,4 +92,8 @@ class Groups::MilestonesController < Groups::ApplicationController render_404 unless @milestone end + + def search_params + params.permit(:state).merge(group_ids: group.id) + end end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 689d2e3db22..d631d09f1b8 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -112,6 +112,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController continue_login_process end + rescue Gitlab::OAuth::SigninDisabledForProviderError + handle_disabled_provider rescue Gitlab::OAuth::SignupDisabledError handle_signup_error end @@ -168,6 +170,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController redirect_to new_user_session_path end + def handle_disabled_provider + label = Gitlab::OAuth::Provider.label_for(oauth['provider']) + flash[:alert] = "Signing in using #{label} has been disabled" + + redirect_to new_user_session_path + end + def log_audit_event(user, options = {}) AuditEventService.new(user, user, options) .for_authentication.security_event diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index e06dda1baa4..f43ef2e5f2f 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -24,7 +24,7 @@ class Projects::DeployKeysController < Projects::ApplicationController def create @key = DeployKeys::CreateService.new(current_user, create_params).execute - unless @key.valid? && @project.deploy_keys << @key + unless @key.valid? flash[:alert] = @key.errors.full_messages.join(', ').html_safe end @@ -71,11 +71,14 @@ class Projects::DeployKeysController < Projects::ApplicationController end def create_params - params.require(:deploy_key).permit(:key, :title, :can_push) + create_params = params.require(:deploy_key) + .permit(:key, :title, deploy_keys_projects_attributes: [:can_push]) + create_params.dig(:deploy_keys_projects_attributes, '0')&.merge!(project_id: @project.id) + create_params end def update_params - params.require(:deploy_key).permit(:title, :can_push) + params.require(:deploy_key).permit(:title, deploy_keys_projects_attributes: [:id, :can_push]) end def authorize_update_deploy_key! diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index 6f51e7b9b40..dd7aa1a67b9 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -64,18 +64,10 @@ class Projects::HooksController < Projects::ApplicationController def hook_params params.require(:hook).permit( - :job_events, - :pipeline_events, :enable_ssl_verification, - :issues_events, - :confidential_issues_events, - :merge_requests_events, - :note_events, - :push_events, - :tag_push_events, :token, :url, - :wiki_page_events + *ProjectHook.triggers.values ) end end diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index 980bbf699b6..0f70efbce40 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -92,12 +92,6 @@ class Projects::MilestonesController < Projects::ApplicationController def milestones @milestones ||= begin - if @project.group && can?(current_user, :read_group, @project.group) - group = @project.group - end - - search_params = params.merge(project_ids: @project.id, group_ids: group&.id) - MilestonesFinder.new(search_params).execute end end @@ -113,4 +107,12 @@ class Projects::MilestonesController < Projects::ApplicationController def milestone_params params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event) end + + def search_params + if @project.group && can?(current_user, :read_group, @project.group) + group = @project.group + end + + params.permit(:state).merge(project_ids: @project.id, group_ids: group&.id) + end end diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb index 0a5a0ea2f35..b4605fca193 100644 --- a/app/finders/milestones_finder.rb +++ b/app/finders/milestones_finder.rb @@ -46,11 +46,7 @@ class MilestonesFinder end def order(items) - if params.has_key?(:order) - items.reorder(params[:order]) - else - order_statement = Gitlab::Database.nulls_last_order('due_date', 'ASC') - items.reorder(order_statement) - end + order_statement = Gitlab::Database.nulls_last_order('due_date', 'ASC') + items.reorder(order_statement).order('title ASC') end end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 0f110bd25c5..64cd3032780 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -72,7 +72,7 @@ module IssuesHelper if item.try(:expired?) 'status-box-expired' elsif item.try(:merged?) - 'status-box-merged' + 'status-box-mr-merged' elsif item.closed? 'status-box-mr-closed' elsif item.try(:upcoming?) diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index e7c953e749e..ddb48371c79 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -54,8 +54,16 @@ module TodosHelper def todo_target_state_pill(todo) return unless show_todo_state?(todo) + type = + case todo.target + when MergeRequest + 'mr' + when Issue + 'issue' + end + content_tag(:span, nil, class: 'target-status') do - content_tag(:span, nil, class: "status-box status-box-#{todo.target.state.dasherize}") do + content_tag(:span, nil, class: "status-box status-box-#{type}-#{todo.target.state.dasherize}") do todo.target.state.capitalize end end diff --git a/app/models/concerns/triggerable_hooks.rb b/app/models/concerns/triggerable_hooks.rb new file mode 100644 index 00000000000..ec0ed3b795a --- /dev/null +++ b/app/models/concerns/triggerable_hooks.rb @@ -0,0 +1,40 @@ +module TriggerableHooks + AVAILABLE_TRIGGERS = { + repository_update_hooks: :repository_update_events, + push_hooks: :push_events, + tag_push_hooks: :tag_push_events, + issue_hooks: :issues_events, + confidential_issue_hooks: :confidential_issues_events, + note_hooks: :note_events, + merge_request_hooks: :merge_requests_events, + job_hooks: :job_events, + pipeline_hooks: :pipeline_events, + wiki_page_hooks: :wiki_page_events + }.freeze + + extend ActiveSupport::Concern + + class_methods do + attr_reader :triggerable_hooks + + attr_reader :triggers + + def hooks_for(trigger) + callable_scopes = triggers.keys + [:all] + return none unless callable_scopes.include?(trigger) + + public_send(trigger) # rubocop:disable GitlabSecurity/PublicSend + end + + private + + def triggerable_hooks(hooks) + triggers = AVAILABLE_TRIGGERS.slice(*hooks) + @triggers = triggers + + triggers.each do |trigger, event| + scope trigger, -> { where(event => true) } + end + end + end +end diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb index eae5eee4fee..c2e0a5fa126 100644 --- a/app/models/deploy_key.rb +++ b/app/models/deploy_key.rb @@ -1,10 +1,16 @@ class DeployKey < Key - has_many :deploy_keys_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + include IgnorableColumn + + has_many :deploy_keys_projects, inverse_of: :deploy_key, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :projects, through: :deploy_keys_projects scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) } scope :are_public, -> { where(public: true) } + ignore_column :can_push + + accepts_nested_attributes_for :deploy_keys_projects + def private? !public? end @@ -22,10 +28,18 @@ class DeployKey < Key end def has_access_to?(project) - projects.include?(project) + deploy_keys_project_for(project).present? end def can_push_to?(project) - can_push? && has_access_to?(project) + !!deploy_keys_project_for(project)&.can_push? + end + + def deploy_keys_project_for(project) + deploy_keys_projects.find_by(project: project) + end + + def projects_with_write_access + Project.preload(:route).where(id: deploy_keys_projects.with_write_access.select(:project_id)) end end diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb index b37b9bfbdac..6eef12c4373 100644 --- a/app/models/deploy_keys_project.rb +++ b/app/models/deploy_keys_project.rb @@ -1,8 +1,14 @@ class DeployKeysProject < ActiveRecord::Base belongs_to :project - belongs_to :deploy_key + belongs_to :deploy_key, inverse_of: :deploy_keys_projects - validates :deploy_key_id, presence: true + scope :without_project_deleted, -> { joins(:project).where(projects: { pending_delete: false }) } + scope :in_project, ->(project) { where(project: project) } + scope :with_write_access, -> { where(can_push: true) } + + accepts_nested_attributes_for :deploy_key + + validates :deploy_key, presence: true validates :deploy_key_id, uniqueness: { scope: [:project_id], message: "already exists in project" } validates :project_id, presence: true diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb index c0864769314..dc2f6817190 100644 --- a/app/models/global_milestone.rb +++ b/app/models/global_milestone.rb @@ -44,10 +44,10 @@ class GlobalMilestone def self.group_milestones_states_count(group) return STATE_COUNT_HASH unless group - params = { group_ids: [group.id], state: 'all', order: nil } + params = { group_ids: [group.id], state: 'all' } relation = MilestonesFinder.new(params).execute - grouped_by_state = relation.group(:state).count + grouped_by_state = relation.reorder(nil).group(:state).count { opened: grouped_by_state['active'] || 0, @@ -60,10 +60,10 @@ class GlobalMilestone def self.legacy_group_milestone_states_count(projects) return STATE_COUNT_HASH unless projects - params = { project_ids: projects.map(&:id), state: 'all', order: nil } + params = { project_ids: projects.map(&:id), state: 'all' } relation = MilestonesFinder.new(params).execute - project_milestones_by_state_and_title = relation.group(:state, :title).count + project_milestones_by_state_and_title = relation.reorder(nil).group(:state, :title).count opened = count_by_state(project_milestones_by_state_and_title, 'active') closed = count_by_state(project_milestones_by_state_and_title, 'closed') diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb index a8c424a6614..b6dd39b860b 100644 --- a/app/models/hooks/project_hook.rb +++ b/app/models/hooks/project_hook.rb @@ -1,19 +1,17 @@ class ProjectHook < WebHook - TRIGGERS = { - push_hooks: :push_events, - tag_push_hooks: :tag_push_events, - issue_hooks: :issues_events, - confidential_issue_hooks: :confidential_issues_events, - note_hooks: :note_events, - merge_request_hooks: :merge_requests_events, - job_hooks: :job_events, - pipeline_hooks: :pipeline_events, - wiki_page_hooks: :wiki_page_events - }.freeze + include TriggerableHooks - TRIGGERS.each do |trigger, event| - scope trigger, -> { where(event => true) } - end + triggerable_hooks [ + :push_hooks, + :tag_push_hooks, + :issue_hooks, + :confidential_issue_hooks, + :note_hooks, + :merge_request_hooks, + :job_hooks, + :pipeline_hooks, + :wiki_page_hooks + ] belongs_to :project validates :project, presence: true diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb index 180c479c41b..0528266e5b3 100644 --- a/app/models/hooks/system_hook.rb +++ b/app/models/hooks/system_hook.rb @@ -1,14 +1,14 @@ class SystemHook < WebHook - TRIGGERS = { - repository_update_hooks: :repository_update_events, - push_hooks: :push_events, - tag_push_hooks: :tag_push_events - }.freeze + include TriggerableHooks - TRIGGERS.each do |trigger, event| - scope trigger, -> { where(event => true) } - end + triggerable_hooks [ + :repository_update_hooks, + :push_hooks, + :tag_push_hooks, + :merge_request_hooks + ] default_value_for :push_events, false default_value_for :repository_update_events, true + default_value_for :merge_requests_events, false end diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 5a70e114f56..27729deeac9 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -4,6 +4,7 @@ class WebHook < ActiveRecord::Base has_many :web_hook_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent validates :url, presence: true, url: true + validates :token, format: { without: /\n/ } def execute(data, hook_name) WebHookService.new(self, data, hook_name).execute diff --git a/app/models/project.rb b/app/models/project.rb index d011b614c69..4017864f718 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -967,10 +967,12 @@ class Project < ActiveRecord::Base def execute_hooks(data, hooks_scope = :push_hooks) run_after_commit_or_now do - hooks.public_send(hooks_scope).each do |hook| # rubocop:disable GitlabSecurity/PublicSend + hooks.hooks_for(hooks_scope).each do |hook| hook.async_execute(data, hooks_scope.to_s) end end + + SystemHooksService.new.execute_hooks(data, hooks_scope) end def execute_services(data, hooks_scope = :push_hooks) diff --git a/app/models/service.rb b/app/models/service.rb index 7f260f7a96b..96a064697f0 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -118,6 +118,11 @@ class Service < ActiveRecord::Base nil end + def api_field_names + fields.map { |field| field[:name] } + .reject { |field_name| field_name =~ /(password|token|key)/ } + end + def global_fields fields end diff --git a/app/presenters/projects/settings/deploy_keys_presenter.rb b/app/presenters/projects/settings/deploy_keys_presenter.rb index 229311eb6ee..c226586fba5 100644 --- a/app/presenters/projects/settings/deploy_keys_presenter.rb +++ b/app/presenters/projects/settings/deploy_keys_presenter.rb @@ -7,7 +7,7 @@ module Projects delegate :size, to: :available_public_keys, prefix: true def new_key - @key ||= DeployKey.new + @key ||= DeployKey.new.tap { |dk| dk.deploy_keys_projects.build } end def enabled_keys diff --git a/app/serializers/deploy_key_entity.rb b/app/serializers/deploy_key_entity.rb index c75431a79ae..2678f99510c 100644 --- a/app/serializers/deploy_key_entity.rb +++ b/app/serializers/deploy_key_entity.rb @@ -3,19 +3,20 @@ class DeployKeyEntity < Grape::Entity expose :user_id expose :title expose :fingerprint - expose :can_push expose :destroyed_when_orphaned?, as: :destroyed_when_orphaned expose :almost_orphaned?, as: :almost_orphaned expose :created_at expose :updated_at - expose :projects, using: ProjectEntity do |deploy_key| - deploy_key.projects.without_deleted.select { |project| options[:user].can?(:read_project, project) } + expose :deploy_keys_projects, using: DeployKeysProjectEntity do |deploy_key| + deploy_key.deploy_keys_projects + .without_project_deleted + .select { |deploy_key_project| Ability.allowed?(options[:user], :read_project, deploy_key_project.project) } end expose :can_edit private def can_edit - options[:user].can?(:update_deploy_key, object) + Ability.allowed?(options[:user], :update_deploy_key, object) end end diff --git a/app/serializers/deploy_keys_project_entity.rb b/app/serializers/deploy_keys_project_entity.rb new file mode 100644 index 00000000000..568ef5ab75e --- /dev/null +++ b/app/serializers/deploy_keys_project_entity.rb @@ -0,0 +1,4 @@ +class DeployKeysProjectEntity < Grape::Entity + expose :can_push + expose :project, using: ProjectEntity +end diff --git a/app/services/labels/promote_service.rb b/app/services/labels/promote_service.rb index 997d247be46..74a85e5c9f0 100644 --- a/app/services/labels/promote_service.rb +++ b/app/services/labels/promote_service.rb @@ -13,6 +13,7 @@ module Labels update_issuables(new_label, batched_ids) update_issue_board_lists(new_label, batched_ids) update_priorities(new_label, batched_ids) + subscribe_users(new_label, batched_ids) # Order is important, project labels need to be last update_project_labels(batched_ids) end @@ -26,6 +27,15 @@ module Labels private + def subscribe_users(new_label, label_ids) + # users can be subscribed to multiple labels that will be merged into the group one + # we want to keep only one subscription / user + ids_to_update = Subscription.where(subscribable_id: label_ids, subscribable_type: 'Label') + .group(:user_id) + .pluck('MAX(id)') + Subscription.where(id: ids_to_update).update_all(subscribable_id: new_label.id) + end + def label_ids_for_merge(new_label) LabelsFinder .new(current_user, title: new_label.title, group_id: project.group.id) @@ -53,7 +63,7 @@ module Labels end def update_project_labels(label_ids) - Label.where(id: label_ids).delete_all + Label.where(id: label_ids).destroy_all end def clone_label_to_group_label(label) diff --git a/app/services/merge_requests/create_from_issue_service.rb b/app/services/merge_requests/create_from_issue_service.rb index 89dab1dd028..cf687b71d16 100644 --- a/app/services/merge_requests/create_from_issue_service.rb +++ b/app/services/merge_requests/create_from_issue_service.rb @@ -54,6 +54,7 @@ module MergeRequests source_project_id: project.id, source_branch: branch_name, target_project_id: project.id, + target_branch: ref, milestone_id: issue.milestone_id } end diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index 49cf534dc0d..634bf3bd690 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -1,15 +1,11 @@ module MergeRequests class CreateService < MergeRequests::BaseService def execute - # @project is used to determine whether the user can set the merge request's - # assignee, milestone and labels. Whether they can depends on their - # permissions on the target project. - source_project = @project - @project = Project.find(params[:target_project_id]) if params[:target_project_id] + set_projects! merge_request = MergeRequest.new merge_request.target_project = @project - merge_request.source_project = source_project + merge_request.source_project = @source_project merge_request.source_branch = params[:source_branch] merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) @@ -58,5 +54,25 @@ module MergeRequests pipelines.order(id: :desc).first end + + def set_projects! + # @project is used to determine whether the user can set the merge request's + # assignee, milestone and labels. Whether they can depends on their + # permissions on the target project. + @source_project = @project + @project = Project.find(params[:target_project_id]) if params[:target_project_id] + + # make sure that source/target project ids are not in + # params so it can't be overridden later when updating attributes + # from params when applying quick actions + params.delete(:source_project_id) + params.delete(:target_project_id) + + unless can?(current_user, :read_project, @source_project) && + can?(current_user, :read_project, @project) + + raise Gitlab::Access::AccessDeniedError + end + end end end diff --git a/app/services/projects/gitlab_projects_import_service.rb b/app/services/projects/gitlab_projects_import_service.rb index 4ca6414b73b..a3d7f5cbed5 100644 --- a/app/services/projects/gitlab_projects_import_service.rb +++ b/app/services/projects/gitlab_projects_import_service.rb @@ -26,7 +26,7 @@ module Projects end def tmp_filename - "#{SecureRandom.hex}_#{params[:path]}" + SecureRandom.hex end def file diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index af6d77ef5e8..a6b7a6e1416 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -8,7 +8,7 @@ class SystemHooksService end def execute_hooks(data, hooks_scope = :all) - SystemHook.public_send(hooks_scope).find_each do |hook| # rubocop:disable GitlabSecurity/PublicSend + SystemHook.hooks_for(hooks_scope).find_each do |hook| hook.async_execute(data, 'system_hooks') end end diff --git a/app/services/test_hooks/base_service.rb b/app/services/test_hooks/base_service.rb index 20d90504bd2..e9aefb1fb75 100644 --- a/app/services/test_hooks/base_service.rb +++ b/app/services/test_hooks/base_service.rb @@ -9,7 +9,7 @@ module TestHooks end def execute - trigger_key = hook.class::TRIGGERS.key(trigger.to_sym) + trigger_key = hook.class.triggers.key(trigger.to_sym) trigger_data_method = "#{trigger}_data" if trigger_key.nil? || !self.respond_to?(trigger_data_method, true) diff --git a/app/services/test_hooks/system_service.rb b/app/services/test_hooks/system_service.rb index 67552edefc9..9016c77b7f0 100644 --- a/app/services/test_hooks/system_service.rb +++ b/app/services/test_hooks/system_service.rb @@ -13,5 +13,12 @@ module TestHooks def repository_update_events_data Gitlab::DataBuilder::Repository.sample_data end + + def merge_requests_events_data + merge_request = MergeRequest.of_projects(current_user.projects.select(:id)).first + throw(:validation_error, 'Ensure one of your projects has merge requests.') unless merge_request.present? + + merge_request.to_hook_data(current_user) + end end end diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb index 6ebc7c89500..36e589d5aa8 100644 --- a/app/services/web_hook_service.rb +++ b/app/services/web_hook_service.rb @@ -113,7 +113,7 @@ class WebHookService 'Content-Type' => 'application/json', 'X-Gitlab-Event' => hook_name.singularize.titleize }.tap do |hash| - hash['X-Gitlab-Token'] = hook.token if hook.token.present? + hash['X-Gitlab-Token'] = Gitlab::Utils.remove_line_breaks(hook.token) if hook.token.present? end end end diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml index 92370034baa..1420163fd5a 100644 --- a/app/views/admin/deploy_keys/index.html.haml +++ b/app/views/admin/deploy_keys/index.html.haml @@ -12,7 +12,7 @@ %tr %th.col-sm-2 Title %th.col-sm-4 Fingerprint - %th.col-sm-2 Write access allowed + %th.col-sm-2 Projects with write access %th.col-sm-2 Added at %th.col-sm-2 %tbody @@ -23,10 +23,8 @@ %td %code.key-fingerprint= deploy_key.fingerprint %td - - if deploy_key.can_push? - Yes - - else - No + - deploy_key.projects_with_write_access.each do |project| + = link_to project.full_name, admin_project_path(project), class: 'label deploy-project-label' %td %span.cgray added #{time_ago_with_tooltip(deploy_key.created_at)} diff --git a/app/views/admin/hooks/_form.html.haml b/app/views/admin/hooks/_form.html.haml index 645005c6deb..d8f96ed5b0d 100644 --- a/app/views/admin/hooks/_form.html.haml +++ b/app/views/admin/hooks/_form.html.haml @@ -38,6 +38,13 @@ %strong Tag push events %p.light This URL will be triggered when a new tag is pushed to the repository + %div + = form.check_box :merge_requests_events, class: 'pull-left' + .prepend-left-20 + = form.label :merge_requests_events, class: 'list-label' do + %strong Merge request events + %p.light + This URL will be triggered when a merge request is created/updated/merged .form-group = form.label :enable_ssl_verification, 'SSL verification', class: 'control-label checkbox' .col-sm-10 diff --git a/app/views/admin/hooks/edit.html.haml b/app/views/admin/hooks/edit.html.haml index efb15ccc8df..629b1a9940f 100644 --- a/app/views/admin/hooks/edit.html.haml +++ b/app/views/admin/hooks/edit.html.haml @@ -13,7 +13,7 @@ = render partial: 'form', locals: { form: f, hook: @hook } .form-actions = f.submit 'Save changes', class: 'btn btn-create' - = render 'shared/web_hooks/test_button', triggers: SystemHook::TRIGGERS, hook: @hook + = render 'shared/web_hooks/test_button', triggers: SystemHook.triggers, hook: @hook = link_to 'Remove', admin_hook_path(@hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' } %hr diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index b6e1df5f3ac..bc02d9969d6 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -22,12 +22,12 @@ - @hooks.each do |hook| %li .controls - = render 'shared/web_hooks/test_button', triggers: SystemHook::TRIGGERS, hook: hook, button_class: 'btn-sm' + = render 'shared/web_hooks/test_button', triggers: SystemHook.triggers, hook: hook, button_class: 'btn-sm' = link_to 'Edit', edit_admin_hook_path(hook), class: 'btn btn-sm' = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: 'btn btn-remove btn-sm' .monospace= hook.url %div - - SystemHook::TRIGGERS.each_value do |event| + - SystemHook.triggers.each_value do |event| - if hook.public_send(event) %span.label.label-gray= event.to_s.titleize %span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'} diff --git a/app/views/admin/jobs/index.html.haml b/app/views/admin/jobs/index.html.haml index 7066ed12b95..a01676d82a8 100644 --- a/app/views/admin/jobs/index.html.haml +++ b/app/views/admin/jobs/index.html.haml @@ -9,7 +9,12 @@ .nav-controls - if @all_builds.running_or_pending.any? - = link_to 'Cancel all', cancel_all_admin_jobs_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post + #stop-jobs-modal + + %button#stop-jobs-button.btn.btn-danger{ data: { toggle: 'modal', + target: '#stop-jobs-modal', + url: cancel_all_admin_jobs_path } } + = s_('AdminArea|Stop all jobs') .row-content-block.second-block #{(@scope || 'all').capitalize} jobs diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml index bd99eb93cc8..d367bd6be7b 100644 --- a/app/views/projects/_new_project_fields.html.haml +++ b/app/views/projects/_new_project_fields.html.haml @@ -34,7 +34,7 @@ .form-group.visibility-level-setting = f.label :visibility_level, class: 'label-light' do Visibility Level - = link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: 'Documentation for Visibility Level' } + = link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: 'Documentation for Visibility Level' }, target: '_blank', rel: 'noopener noreferrer' = render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false = f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4 diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index a94d9c14722..dab94d10bb1 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -9,34 +9,27 @@ - can_create_snippet = can?(current_user, :create_snippet, @project) - if can_create_issue - %li - = link_to _('New issue'), new_project_issue_path(@project) + %li= link_to _('New issue'), new_project_issue_path(@project) + - if merge_project - %li - = link_to _('New merge request'), project_new_merge_request_path(merge_project) + %li= link_to _('New merge request'), project_new_merge_request_path(merge_project) + - if can_create_snippet - %li - = link_to _('New snippet'), new_project_snippet_path(@project) + %li= link_to _('New snippet'), new_project_snippet_path(@project) - if can_create_issue || merge_project || can_create_snippet %li.divider - if can?(current_user, :push_code, @project) - %li - = link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master') + %li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master') - unless @project.empty_repo? - %li - = link_to _('New branch'), new_project_branch_path(@project) - %li - = link_to _('New tag'), new_project_tag_path(@project) + %li= link_to _('New branch'), new_project_branch_path(@project) + %li= link_to _('New tag'), new_project_tag_path(@project) - elsif current_user && current_user.already_forked?(@project) - %li - = link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master') + %li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master') - elsif can?(current_user, :fork_project, @project) - %li - - continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'), - notice: edit_in_new_fork_notice, - notice_now: edit_in_new_fork_notice_now } - - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, - continue: continue_params) - = link_to _('New file'), fork_path, method: :post + - continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'), + notice: edit_in_new_fork_notice, + notice_now: edit_in_new_fork_notice_now } + - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params) + %li= link_to _('New file'), fork_path, method: :post diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml index edaa3a1119e..c363180d0db 100644 --- a/app/views/projects/deploy_keys/_form.html.haml +++ b/app/views/projects/deploy_keys/_form.html.haml @@ -10,13 +10,15 @@ %p.light.append-bottom-0 Paste a machine public key here. Read more about how to generate it = link_to "here", help_page_path("ssh/README") - .form-group - .checkbox - = f.label :can_push do - = f.check_box :can_push - %strong Write access allowed - .form-group - %p.light.append-bottom-0 - Allow this key to push to repository as well? (Default only allows pull access.) + + = f.fields_for :deploy_keys_projects do |deploy_keys_project_form| + .form-group + .checkbox + = deploy_keys_project_form.label :can_push do + = deploy_keys_project_form.check_box :can_push + %strong Write access allowed + .form-group + %p.light.append-bottom-0 + Allow this key to push to repository as well? (Default only allows pull access.) = f.submit "Add key", class: "btn-create btn" diff --git a/app/views/projects/hooks/edit.html.haml b/app/views/projects/hooks/edit.html.haml index b1219f019d7..dcc1f0e3fbe 100644 --- a/app/views/projects/hooks/edit.html.haml +++ b/app/views/projects/hooks/edit.html.haml @@ -12,7 +12,7 @@ = render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook } = f.submit 'Save changes', class: 'btn btn-create' - = render 'shared/web_hooks/test_button', triggers: ProjectHook::TRIGGERS, hook: @hook + = render 'shared/web_hooks/test_button', triggers: ProjectHook.triggers, hook: @hook = link_to 'Remove', project_hook_path(@project, @hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' } %hr diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml index a71333497e6..e779473c239 100644 --- a/app/views/projects/jobs/_sidebar.html.haml +++ b/app/views/projects/jobs/_sidebar.html.haml @@ -24,7 +24,7 @@ - elsif @build.has_expiring_artifacts? %p.build-detail-row The artifacts will be removed in - %span.js-artifacts-remove= @build.artifacts_expire_at + %span= time_ago_in_words @build.artifacts_expire_at - if @build.artifacts? .btn-group.btn-group-justified{ role: :group } diff --git a/app/views/projects/merge_requests/creations/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml index 4b5fa28078a..376ac377562 100644 --- a/app/views/projects/merge_requests/creations/_new_submit.html.haml +++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml @@ -15,7 +15,7 @@ = f.hidden_field :source_project_id = f.hidden_field :source_branch = f.hidden_field :target_project_id - = f.hidden_field :target_branch + = f.hidden_field :target_branch, id: '' .mr-compare.merge-request.js-merge-request-new-submit{ 'data-mr-submit-action': "#{j params[:tab].presence || 'new'}" } - if @commits.empty? diff --git a/app/views/projects/settings/integrations/_project_hook.html.haml b/app/views/projects/settings/integrations/_project_hook.html.haml index 82516cb4bcf..cd003107d66 100644 --- a/app/views/projects/settings/integrations/_project_hook.html.haml +++ b/app/views/projects/settings/integrations/_project_hook.html.haml @@ -3,14 +3,14 @@ .col-md-8.col-lg-7 %strong.light-header= hook.url %div - - ProjectHook::TRIGGERS.each_value do |event| + - ProjectHook.triggers.each_value do |event| - if hook.public_send(event) %span.label.label-gray.deploy-project-label= event.to_s.titleize .col-md-4.col-lg-5.text-right-lg.prepend-top-5 %span.append-right-10.inline SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'} = link_to 'Edit', edit_project_hook_path(@project, hook), class: 'btn btn-sm' - = render 'shared/web_hooks/test_button', triggers: ProjectHook::TRIGGERS, hook: hook, button_class: 'btn-sm' + = render 'shared/web_hooks/test_button', triggers: ProjectHook.triggers, hook: hook, button_class: 'btn-sm' = link_to project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-transparent' do %span.sr-only Remove = icon('trash') diff --git a/app/views/shared/deploy_keys/_form.html.haml b/app/views/shared/deploy_keys/_form.html.haml index e6075c3ae3a..87c2965bb21 100644 --- a/app/views/shared/deploy_keys/_form.html.haml +++ b/app/views/shared/deploy_keys/_form.html.haml @@ -1,5 +1,6 @@ - form = local_assigns.fetch(:form) - deploy_key = local_assigns.fetch(:deploy_key) +- deploy_keys_project = deploy_key.deploy_keys_project_for(@project) = form_errors(deploy_key) @@ -20,11 +21,13 @@ .col-sm-10 = form.text_field :fingerprint, class: 'form-control', readonly: 'readonly' -.form-group - .control-label - .col-sm-10 - = form.label :can_push do - = form.check_box :can_push - %strong Write access allowed - %p.light.append-bottom-0 - Allow this key to push to repository as well? (Default only allows pull access.) +- if deploy_keys_project.present? + = form.fields_for :deploy_keys_projects, deploy_keys_project do |deploy_keys_project_form| + .form-group + .control-label + .col-sm-10 + = deploy_keys_project_form.label :can_push do + = deploy_keys_project_form.check_box :can_push + %strong Write access allowed + %p.light.append-bottom-0 + Allow this key to push to repository as well? (Default only allows pull access.) diff --git a/app/views/shared/issuable/form/_branch_chooser.html.haml b/app/views/shared/issuable/form/_branch_chooser.html.haml index 203d2adc8db..9a589387255 100644 --- a/app/views/shared/issuable/form/_branch_chooser.html.haml +++ b/app/views/shared/issuable/form/_branch_chooser.html.haml @@ -15,11 +15,10 @@ = form.label :target_branch, class: 'control-label' .col-sm-10.target-branch-select-dropdown-container .issuable-form-select-holder - = form.select(:target_branch, issuable.target_branches, - { include_blank: true }, + = form.hidden_field(:target_branch, { class: 'target_branch js-target-branch-select ref-name', disabled: issuable.new_record?, - data: { placeholder: "Select branch" }}) + data: { placeholder: "Select branch", endpoint: refs_project_path(@project, sort: 'updated_desc', find: 'branches') }}) - if issuable.new_record? = link_to 'Change branches', mr_change_branches_path(issuable) diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml index 1f0e7629fb4..ad4d39b4aa1 100644 --- a/app/views/shared/web_hooks/_form.html.haml +++ b/app/views/shared/web_hooks/_form.html.haml @@ -50,7 +50,7 @@ = form.check_box :merge_requests_events, class: 'pull-left' .prepend-left-20 = form.label :merge_requests_events, class: 'list-label' do - %strong Merge Request events + %strong Merge request events %p.light This URL will be triggered when a merge request is created/updated/merged %li |