From c44dff9984d4ee055a40b9c3354888193b3d5f57 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Fri, 20 Oct 2017 13:28:30 +0100 Subject: Remove page-specific GLForm init and add support_autocomplete: false local to groups/milestones/_form --- app/assets/javascripts/dispatcher.js | 1 - app/views/groups/milestones/_form.html.haml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 2885923aeda..eb576672d25 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -234,7 +234,6 @@ import DueDateSelectors from './due_date_select'; case 'groups:milestones:update': new ZenMode(); new DueDateSelectors(); - new GLForm($('.milestone-form'), true); break; case 'projects:compare:show': new gl.Diff(); diff --git a/app/views/groups/milestones/_form.html.haml b/app/views/groups/milestones/_form.html.haml index cc879e5a308..a1be0d3220a 100644 --- a/app/views/groups/milestones/_form.html.haml +++ b/app/views/groups/milestones/_form.html.haml @@ -11,7 +11,7 @@ = f.label :description, "Description", class: "control-label" .col-sm-10 = render layout: 'projects/md_preview', locals: { url: group_preview_markdown_path } do - = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...' + = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...', supports_autocomplete: false .clearfix .error-alert -- cgit v1.2.1 From c8d29d17aef6ac4fd0620dc0d69df5ef454fd102 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Fri, 20 Oct 2017 17:11:31 +0100 Subject: Added group milestones form spec --- spec/features/groups/milestone_spec.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb index 12aa54a3da1..1b41b3842c8 100644 --- a/spec/features/groups/milestone_spec.rb +++ b/spec/features/groups/milestone_spec.rb @@ -19,9 +19,9 @@ feature 'Group milestones', :js do end it 'renders description preview' do - form = find('.gfm-form') + description = find('.note-textarea') - form.fill_in(:milestone_description, with: '') + description.native.send_keys('') click_link('Preview') @@ -31,7 +31,7 @@ feature 'Group milestones', :js do click_link('Write') - form.fill_in(:milestone_description, with: ':+1: Nice') + description.native.send_keys(':+1: Nice') click_link('Preview') @@ -51,6 +51,13 @@ feature 'Group milestones', :js do expect(find('.start_date')).to have_content(Date.today.at_beginning_of_month.strftime('%b %-d, %Y')) end + + it 'description input does not support autocomplete' do + description = find('.note-textarea') + description.native.send_keys('!') + + expect(page).not_to have_selector('.atwho-view') + end end context 'milestones list' do -- cgit v1.2.1 From 3e5bfe5abd70f11224fa934f78a3e68f2038359a Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Mon, 23 Oct 2017 13:41:52 +0100 Subject: Prefer dispatcher glform init over global init --- app/assets/javascripts/dispatcher.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index eb576672d25..07535508d59 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -234,6 +234,7 @@ import DueDateSelectors from './due_date_select'; case 'groups:milestones:update': new ZenMode(); new DueDateSelectors(); + new GLForm($('.milestone-form'), false); break; case 'projects:compare:show': new gl.Diff(); -- cgit v1.2.1 From bb943537adbeaec64b6b9a703ffb17dfa1af5fd5 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 24 Oct 2017 07:16:57 +0300 Subject: Update i18n in FE docs, marking, interpolation --- changelogs/unreleased/update-fe-i18n-guide.yml | 5 ++++ doc/development/fe_guide/index.md | 4 +++ doc/development/i18n/externalization.md | 40 +++++++++++++++++++------- 3 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 changelogs/unreleased/update-fe-i18n-guide.yml diff --git a/changelogs/unreleased/update-fe-i18n-guide.yml b/changelogs/unreleased/update-fe-i18n-guide.yml new file mode 100644 index 00000000000..10bcf7836c6 --- /dev/null +++ b/changelogs/unreleased/update-fe-i18n-guide.yml @@ -0,0 +1,5 @@ +--- +title: Update i18n section in FE docs for marking and interpolation +merge_request: +author: +type: changed diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md index 73366eb9f3f..8f956681693 100644 --- a/doc/development/fe_guide/index.md +++ b/doc/development/fe_guide/index.md @@ -106,6 +106,10 @@ Frontend security practices. ## [Accessibility](accessibility.md) Our accessibility standards and resources. +## [Internationalization (i18n) and Translations](../i18n/externalization.md) +Frontend internationalization support is described in [this document](../i18n/). +The [externalization part of the guide](../i18n/externalization.md) explains the helpers/methods available. + [rails]: http://rubyonrails.org/ [haml]: http://haml.info/ diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md index 167260b6e0e..7c38260406d 100644 --- a/doc/development/i18n/externalization.md +++ b/doc/development/i18n/externalization.md @@ -180,15 +180,43 @@ aren't in the message with id `1 pipeline`. ## Working with special content + +### Just marking content for parsing + +- In Ruby/HAML: + + ```ruby + _('Subscribe') + ``` + +- In JavaScript: + + ```js + import { __ } from '../../../locale'; + const label = __('Subscribe'); + ``` + + +Sometimes there are some dynamic translations that can't be found by the +parser when running `bundle exec rake gettext:find`. For these scenarios you can +use the [`_N` method](https://github.com/grosser/gettext_i18n_rails/blob/c09e38d481e0899ca7d3fc01786834fa8e7aab97/Readme.md#unfound-translations-with-rake-gettextfind). + +There is also and alternative method to [translate messages from validation errors](https://github.com/grosser/gettext_i18n_rails/blob/c09e38d481e0899ca7d3fc01786834fa8e7aab97/Readme.md#option-a). + ### Interpolation - In Ruby/HAML: ```ruby - _("Hello %{name}") % { name: 'Joe' } + _("Hello %{name}") % { name: 'Joe' } => 'Hello Joe' ``` -- In JavaScript: Not supported at this moment. +- In JavaScript: + + ```js + import { __, sprintf } from '../../../locale'; + sprintf(__('Hello %{username}'), { username: 'Joe' }) => 'Hello Joe' + ``` ### Plurals @@ -234,14 +262,6 @@ Sometimes you need to add some context to the text that you want to translate s__('OpenedNDaysAgo|Opened') ``` -### Just marking content for parsing - -Sometimes there are some dynamic translations that can't be found by the -parser when running `bundle exec rake gettext:find`. For these scenarios you can -use the [`_N` method](https://github.com/grosser/gettext_i18n_rails/blob/c09e38d481e0899ca7d3fc01786834fa8e7aab97/Readme.md#unfound-translations-with-rake-gettextfind). - -There is also and alternative method to [translate messages from validation errors](https://github.com/grosser/gettext_i18n_rails/blob/c09e38d481e0899ca7d3fc01786834fa8e7aab97/Readme.md#option-a). - ## Adding a new language Let's suppose you want to add translations for a new language, let's say French. -- cgit v1.2.1 From e6b0f4420059285e1ad6e98d98321381ebd9d671 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Tue, 24 Oct 2017 23:01:49 +0100 Subject: Split project and group milestone dispatcher branch --- app/assets/javascripts/dispatcher.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 07535508d59..98b843279aa 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -229,6 +229,10 @@ import DueDateSelectors from './due_date_select'; case 'projects:milestones:new': case 'projects:milestones:edit': case 'projects:milestones:update': + new ZenMode(); + new DueDateSelectors(); + new GLForm($('.milestone-form'), true); + break; case 'groups:milestones:new': case 'groups:milestones:edit': case 'groups:milestones:update': -- cgit v1.2.1 From 6e2b842202ff7dfc7b2d249b5d0ca4d064caa3f2 Mon Sep 17 00:00:00 2001 From: Andrew Newdigate Date: Thu, 26 Oct 2017 12:39:43 +0100 Subject: Upgrade Gitaly to v0.50.0 --- GITALY_SERVER_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index fbaaafa001b..564edf82ddf 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.49.0 \ No newline at end of file +0.50.0 -- cgit v1.2.1 From 8df62a3a6481ca04fa2debc57d41b36f0b8f3945 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 26 Oct 2017 21:03:48 +0300 Subject: Remove old code --- app/assets/javascripts/dispatcher.js | 4 +- app/assets/javascripts/importer_status.js | 144 ++++++++------- .../javascripts/issuable_bulk_update_actions.js | 1 - app/assets/javascripts/issuable_index.js | 202 ++++----------------- app/assets/javascripts/main.js | 3 +- 5 files changed, 112 insertions(+), 242 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index f20162c48e9..d5749f650b6 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -1,6 +1,6 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */ /* global ProjectSelect */ -/* global IssuableIndex */ +import IssuableIndex from './issuable_index'; /* global Milestone */ /* global IssuableForm */ /* global LabelsSelect */ @@ -173,7 +173,7 @@ import Diff from './diff'; filteredSearchManager.setup(); } const pagePrefix = page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_'; - IssuableIndex.init(pagePrefix); + new IssuableIndex(pagePrefix); shortcut_handler = new ShortcutsNavigation(); new UsersSelect(); diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js index 5b4ca94ed30..99f229cc047 100644 --- a/app/assets/javascripts/importer_status.js +++ b/app/assets/javascripts/importer_status.js @@ -1,83 +1,81 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, camelcase, no-var, one-var, one-var-declaration-per-line, prefer-template, quotes, object-shorthand, comma-dangle, no-unused-vars, prefer-arrow-callback, no-else-return, vars-on-top, no-new, max-len */ +class ImporterStatus { + constructor(jobsUrl, importUrl) { + this.jobsUrl = jobsUrl; + this.importUrl = importUrl; + this.initStatusPage(); + this.setAutoUpdate(); + } -(function() { - window.ImporterStatus = (function() { - function ImporterStatus(jobs_url, import_url) { - this.jobs_url = jobs_url; - this.import_url = import_url; - this.initStatusPage(); - this.setAutoUpdate(); - } + initStatusPage() { + $('.js-add-to-import') + .off('click') + .on('click', (event) => { + const $btn = $(event.currentTarget); + const $tr = $btn.closest('tr'); + const $targetField = $tr.find('.import-target'); + const $namespaceInput = $targetField.find('.js-select-namespace option:selected'); + const id = $tr.attr('id').replace('repo_', ''); + let targetNamespace; + let newName; + if ($namespaceInput.length > 0) { + targetNamespace = $namespaceInput[0].innerHTML; + newName = $targetField.find('#path').prop('value'); + $targetField.empty().append(`${targetNamespace}/${newName}`); + } + $btn.disable().addClass('is-loading'); - ImporterStatus.prototype.initStatusPage = function() { - $('.js-add-to-import').off('click').on('click', (function(_this) { - return function(e) { - var $btn, $namespace_input, $target_field, $tr, id, target_namespace, newName; - $btn = $(e.currentTarget); - $tr = $btn.closest('tr'); - $target_field = $tr.find('.import-target'); - $namespace_input = $target_field.find('.js-select-namespace option:selected'); - id = $tr.attr('id').replace('repo_', ''); - target_namespace = null; - newName = null; - if ($namespace_input.length > 0) { - target_namespace = $namespace_input[0].innerHTML; - newName = $target_field.find('#path').prop('value'); - $target_field.empty().append(target_namespace + "/" + newName); - } - $btn.disable().addClass('is-loading'); - return $.post(_this.import_url, { - repo_id: id, - target_namespace: target_namespace, - new_name: newName - }, { - dataType: 'script' - }); - }; - })(this)); - return $('.js-import-all').off('click').on('click', function(e) { - var $btn; - $btn = $(this); + return $.post(this.importUrl, { + repo_id: id, + target_namespace: targetNamespace, + new_name: newName, + }, { + dataType: 'script', + }); + }); + + $('.js-import-all') + .off('click') + .on('click', function onClickImportAll() { + const $btn = $(this); $btn.disable().addClass('is-loading'); - return $('.js-add-to-import').each(function() { + return $('.js-add-to-import').each(function triggerAddImport() { return $(this).trigger('click'); }); }); - }; + } + + setAutoUpdate() { + return setInterval(() => $.get(this.jobsUrl, data => $.each(data, (i, job) => { + const jobItem = $(`#project_${job.id}`); + const statusField = jobItem.find('.job-status'); - ImporterStatus.prototype.setAutoUpdate = function() { - return setInterval(((function(_this) { - return function() { - return $.get(_this.jobs_url, function(data) { - return $.each(data, function(i, job) { - var job_item, status_field; - job_item = $("#project_" + job.id); - status_field = job_item.find(".job-status"); - if (job.import_status === 'finished') { - job_item.removeClass("active").addClass("success"); - return status_field.html(' done'); - } else if (job.import_status === 'scheduled') { - return status_field.html(" scheduled"); - } else if (job.import_status === 'started') { - return status_field.html(" started"); - } else { - return status_field.html(job.import_status); - } - }); - }); - }; - })(this)), 4000); - }; + const spinner = ''; - return ImporterStatus; - })(); + switch (job.import_status) { + case 'finished': + jobItem.removeClass('active').addClass('success'); + statusField.html(' done'); + break; + case 'scheduled': + statusField.html(`${spinner} scheduled`); + break; + case 'started': + statusField.html(`${spinner} started`); + break; + default: + statusField.html(job.import_status); + break; + } + })), 4000); + } +} - $(function() { - if ($('.js-importer-status').length) { - var jobsImportPath = $('.js-importer-status').data('jobs-import-path'); - var importPath = $('.js-importer-status').data('import-path'); +// eslint-disable-next-line consistent-return +export default function initImporterStatus() { + const importerStatus = $('.js-importer-status'); + const data = importerStatus.dataset; - new window.ImporterStatus(jobsImportPath, importPath); - } - }); -}).call(window); + if (importerStatus.length) { + return new ImporterStatus(data.jobsImportPath, data.importPath); + } +} diff --git a/app/assets/javascripts/issuable_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js index eb15949603f..b124fafec70 100644 --- a/app/assets/javascripts/issuable_bulk_update_actions.js +++ b/app/assets/javascripts/issuable_bulk_update_actions.js @@ -1,5 +1,4 @@ /* eslint-disable comma-dangle, quotes, consistent-return, func-names, array-callback-return, space-before-function-paren, prefer-arrow-callback, max-len, no-unused-expressions, no-sequences, no-underscore-dangle, no-unused-vars, no-param-reassign */ -/* global IssuableIndex */ import _ from 'underscore'; import Flash from './flash'; diff --git a/app/assets/javascripts/issuable_index.js b/app/assets/javascripts/issuable_index.js index ece0220c927..e5c1dd50c11 100644 --- a/app/assets/javascripts/issuable_index.js +++ b/app/assets/javascripts/issuable_index.js @@ -1,171 +1,43 @@ -/* eslint-disable no-param-reassign, func-names, no-var, camelcase, no-unused-vars, object-shorthand, space-before-function-paren, no-return-assign, comma-dangle, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, wrap-iife, max-len */ -/* global IssuableIndex */ -import _ from 'underscore'; import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar'; import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; -((global) => { - var issuable_created; - - issuable_created = false; - - global.IssuableIndex = { - init: function(pagePrefix) { - IssuableIndex.initTemplates(); - IssuableIndex.initSearch(); - IssuableIndex.initBulkUpdate(pagePrefix); - IssuableIndex.initResetFilters(); - IssuableIndex.resetIncomingEmailToken(); - IssuableIndex.initLabelFilterRemove(); - }, - initTemplates: function() { - return IssuableIndex.labelRow = _.template('<% _.each(labels, function(label){ %> <%- label.title %> <% }); %>'); - }, - initSearch: function() { - const $searchInput = $('#issuable_search'); - - IssuableIndex.initSearchState($searchInput); - - // `immediate` param set to false debounces on the `trailing` edge, lets user finish typing - const debouncedExecSearch = _.debounce(IssuableIndex.executeSearch, 1000, false); - - $searchInput.off('keyup').on('keyup', debouncedExecSearch); - - // ensures existing filters are preserved when manually submitted - $('#issuable_search_form').on('submit', (e) => { - e.preventDefault(); - debouncedExecSearch(e); - }); - }, - initSearchState: function($searchInput) { - const currentSearchVal = $searchInput.val(); - - IssuableIndex.searchState = { - elem: $searchInput, - current: currentSearchVal - }; - - IssuableIndex.maybeFocusOnSearch(); - }, - accessSearchPristine: function(set) { - // store reference to previous value to prevent search on non-mutating keyup - const state = IssuableIndex.searchState; - const currentSearchVal = state.elem.val(); - - if (set) { - state.current = currentSearchVal; - } else { - return state.current === currentSearchVal; - } - }, - maybeFocusOnSearch: function() { - const currentSearchVal = IssuableIndex.searchState.current; - if (currentSearchVal && currentSearchVal !== '') { - const queryLength = currentSearchVal.length; - const $searchInput = IssuableIndex.searchState.elem; - - /* The following ensures that the cursor is initially placed at - * the end of search input when focus is applied. It accounts - * for differences in browser implementations of `setSelectionRange` - * and cursor placement for elements in focus. - */ - $searchInput.focus(); - if ($searchInput.setSelectionRange) { - $searchInput.setSelectionRange(queryLength, queryLength); - } else { - $searchInput.val(currentSearchVal); - } - } - }, - executeSearch: function(e) { - const $search = $('#issuable_search'); - const $searchName = $search.attr('name'); - const $searchValue = $search.val(); - const $filtersForm = $('.js-filter-form'); - const $input = $(`input[name='${$searchName}']`, $filtersForm); - const isPristine = IssuableIndex.accessSearchPristine(); - - if (isPristine) { - return; - } - - if (!$input.length) { - $filtersForm.append(``); - } else { - $input.val($searchValue); - } - - IssuableIndex.filterResults($filtersForm); - }, - initLabelFilterRemove: function() { - return $(document).off('click', '.js-label-filter-remove').on('click', '.js-label-filter-remove', function(e) { - var $button; - $button = $(this); - // Remove the label input box - $('input[name="label_name[]"]').filter(function() { - return this.value === $button.data('label'); - }).remove(); - // Submit the form to get new data - IssuableIndex.filterResults($('.filter-form')); - }); - }, - filterResults: (function(_this) { - return function(form) { - var formAction, formData, issuesUrl; - formData = form.serializeArray(); - formData = formData.filter(function(data) { - return data.value !== ''; - }); - formData = $.param(formData); - formAction = form.attr('action'); - issuesUrl = formAction; - issuesUrl += "" + (formAction.indexOf('?') === -1 ? '?' : '&'); - issuesUrl += formData; - return gl.utils.visitUrl(issuesUrl); - }; - })(this), - initResetFilters: function() { - $('.reset-filters').on('click', function(e) { - e.preventDefault(); - const target = e.target; - const $form = $(target).parents('.js-filter-form'); - const baseIssuesUrl = target.href; - - $form.attr('action', baseIssuesUrl); - gl.utils.visitUrl(baseIssuesUrl); +export default class IssuableIndex { + constructor(pagePrefix) { + this.initBulkUpdate(pagePrefix); + IssuableIndex.resetIncomingEmailToken(); + this.initLabelFilterRemove(); + } + initBulkUpdate(pagePrefix) { + const userCanBulkUpdate = $('.issues-bulk-update').length > 0; + const alreadyInitialized = !!this.bulkUpdateSidebar; + + if (userCanBulkUpdate && !alreadyInitialized) { + IssuableBulkUpdateActions.init({ + prefixId: pagePrefix, }); - }, - initBulkUpdate: function(pagePrefix) { - const userCanBulkUpdate = $('.issues-bulk-update').length > 0; - const alreadyInitialized = !!this.bulkUpdateSidebar; - - if (userCanBulkUpdate && !alreadyInitialized) { - IssuableBulkUpdateActions.init({ - prefixId: pagePrefix, - }); - - this.bulkUpdateSidebar = new IssuableBulkUpdateSidebar(); - } - }, - resetIncomingEmailToken: function() { - $('.incoming-email-token-reset').on('click', function(e) { - e.preventDefault(); - $.ajax({ - type: 'PUT', - url: $('.incoming-email-token-reset').attr('href'), - dataType: 'json', - success: function(response) { - $('#issue_email').val(response.new_issue_address).focus(); - }, - beforeSend: function() { - $('.incoming-email-token-reset').text('resetting...'); - }, - complete: function() { - $('.incoming-email-token-reset').text('reset it'); - } - }); - }); + this.bulkUpdateSidebar = new IssuableBulkUpdateSidebar(); } - }; -})(window); + } + + static resetIncomingEmailToken() { + $('.incoming-email-token-reset').on('click', (e) => { + e.preventDefault(); + + $.ajax({ + type: 'PUT', + url: $('.incoming-email-token-reset').attr('href'), + dataType: 'json', + success(response) { + $('#issue_email').val(response.new_issue_address).focus(); + }, + beforeSend() { + $('.incoming-email-token-reset').text('resetting...'); + }, + complete() { + $('.incoming-email-token-reset').text('reset it'); + }, + }); + }); + } +} diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 52715fba43f..00a94c45b47 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -57,7 +57,7 @@ import './gl_field_errors'; import './gl_form'; import './groups_select'; import './header'; -import './importer_status'; +import initImporterStatus from './importer_status'; import './issuable_index'; import './issuable_context'; import './issuable_form'; @@ -140,6 +140,7 @@ $(function () { var fitSidebarForSize; initBreadcrumbs(); + initImporterStatus(); // Set the default path for all cookies to GitLab's root directory Cookies.defaults.path = gon.relative_url_root || '/'; -- cgit v1.2.1 From cf542f9ed0ff54e9072eca250244662808b80eab Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 26 Oct 2017 23:02:57 +0300 Subject: Improves issuable tests Fixes missing dependencies --- .../javascripts/issuable_bulk_update_sidebar.js | 4 + app/assets/javascripts/issuable_index.js | 1 - spec/javascripts/issuable_spec.js | 102 +++++++-------------- 3 files changed, 37 insertions(+), 70 deletions(-) diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js index 0e8a0519928..bb509089b1d 100644 --- a/app/assets/javascripts/issuable_bulk_update_sidebar.js +++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js @@ -5,6 +5,10 @@ /* global SubscriptionSelect */ import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; +import './milestone_select'; +import './issue_status_select'; +import './subscription_select'; +import './labels_select'; const HIDDEN_CLASS = 'hidden'; const DISABLED_CONTENT_CLASS = 'disabled-content'; diff --git a/app/assets/javascripts/issuable_index.js b/app/assets/javascripts/issuable_index.js index e5c1dd50c11..0b123a11a3b 100644 --- a/app/assets/javascripts/issuable_index.js +++ b/app/assets/javascripts/issuable_index.js @@ -5,7 +5,6 @@ export default class IssuableIndex { constructor(pagePrefix) { this.initBulkUpdate(pagePrefix); IssuableIndex.resetIncomingEmailToken(); - this.initLabelFilterRemove(); } initBulkUpdate(pagePrefix) { const userCanBulkUpdate = $('.issues-bulk-update').length > 0; diff --git a/spec/javascripts/issuable_spec.js b/spec/javascripts/issuable_spec.js index 45f55395d3a..ceee08d47c5 100644 --- a/spec/javascripts/issuable_spec.js +++ b/spec/javascripts/issuable_spec.js @@ -1,80 +1,44 @@ -/* global IssuableIndex */ - -import '~/lib/utils/url_utility'; -import '~/issuable_index'; - -(() => { - const BASE_URL = '/user/project/issues?scope=all&state=closed'; - const DEFAULT_PARAMS = '&utf8=%E2%9C%93'; - - function updateForm(formValues, form) { - $.each(formValues, (id, value) => { - $(`#${id}`, form).val(value); - }); - } - - function resetForm(form) { - $('input[name!="utf8"]', form).each((index, input) => { - input.setAttribute('value', ''); +import IssuableIndex from '~/issuable_index'; + +describe('Issuable', () => { + let Issuable; + describe('initBulkUpdate', () => { + it('should not set bulkUpdateSidebar', () => { + Issuable = new IssuableIndex('issue_'); + expect(Issuable.bulkUpdateSidebar).not.toBeDefined(); }); - } - describe('Issuable', () => { - preloadFixtures('static/issuable_filter.html.raw'); + it('should set bulkUpdateSidebar', () => { + const element = document.createElement('div'); + element.classList.add('issues-bulk-update'); + document.body.appendChild(element); - beforeEach(() => { - loadFixtures('static/issuable_filter.html.raw'); - IssuableIndex.init(); - }); - - it('should be defined', () => { - expect(window.IssuableIndex).toBeDefined(); + Issuable = new IssuableIndex('issue_'); + expect(Issuable.bulkUpdateSidebar).toBeDefined(); }); + }); - describe('filtering', () => { - let $filtersForm; - - beforeEach(() => { - $filtersForm = $('.js-filter-form'); - loadFixtures('static/issuable_filter.html.raw'); - resetForm($filtersForm); - }); - - it('should contain only the default parameters', () => { - spyOn(gl.utils, 'visitUrl'); - - IssuableIndex.filterResults($filtersForm); - - expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + DEFAULT_PARAMS); - }); - - it('should filter for the phrase "broken"', () => { - spyOn(gl.utils, 'visitUrl'); - - updateForm({ search: 'broken' }, $filtersForm); - IssuableIndex.filterResults($filtersForm); - const params = `${DEFAULT_PARAMS}&search=broken`; - - expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + params); - }); - - it('should keep query parameters after modifying filter', () => { - spyOn(gl.utils, 'visitUrl'); + describe('resetIncomingEmailToken', () => { + beforeEach(() => { + const element = document.createElement('a'); + element.classList.add('incoming-email-token-reset'); + element.setAttribute('href', 'foo'); + document.body.appendChild(element); - // initial filter - updateForm({ milestone_title: 'v1.0' }, $filtersForm); + const input = document.createElement('input'); + input.setAttribute('id', 'issue_email'); + document.body.appendChild(input); - IssuableIndex.filterResults($filtersForm); - let params = `${DEFAULT_PARAMS}&milestone_title=v1.0`; - expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + params); + Issuable = new IssuableIndex('issue_'); + }); - // update filter - updateForm({ label_name: 'Frontend' }, $filtersForm); + it('should send request to reset email token', () => { + spyOn(jQuery, 'ajax').and.callThrough(); + document.querySelector('.incoming-email-token-reset').click(); - IssuableIndex.filterResults($filtersForm); - params = `${DEFAULT_PARAMS}&milestone_title=v1.0&label_name=Frontend`; - expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + params); - }); + expect(jQuery.ajax).toHaveBeenCalled(); + expect(jQuery.ajax.calls.argsFor(0)[0].url).toEqual('foo'); }); }); -})(); +}); + -- cgit v1.2.1 From a665a35438fe1ddf96228918fd99f790de9b6517 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 26 Oct 2017 23:14:18 +0300 Subject: Remove issuableContext from global namespace --- .../javascripts/boards/components/board_sidebar.js | 2 +- app/assets/javascripts/init_issuable_sidebar.js | 2 +- app/assets/javascripts/issuable_context.js | 113 ++++++++++----------- app/assets/javascripts/main.js | 2 - spec/javascripts/issuable_context_spec.js | 3 +- spec/javascripts/labels_issue_sidebar_spec.js | 2 +- 6 files changed, 60 insertions(+), 64 deletions(-) diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index c1f902a785a..6fa41dbd0dd 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -1,5 +1,5 @@ /* eslint-disable comma-dangle, space-before-function-paren, no-new */ -/* global IssuableContext */ +import IssuableContext from '../../issuable_context'; /* global MilestoneSelect */ /* global LabelsSelect */ /* global Sidebar */ diff --git a/app/assets/javascripts/init_issuable_sidebar.js b/app/assets/javascripts/init_issuable_sidebar.js index 32a1a269f9a..6f476f96f72 100644 --- a/app/assets/javascripts/init_issuable_sidebar.js +++ b/app/assets/javascripts/init_issuable_sidebar.js @@ -1,7 +1,7 @@ /* eslint-disable no-new */ /* global MilestoneSelect */ /* global LabelsSelect */ -/* global IssuableContext */ +import IssuableContext from './issuable_context'; /* global Sidebar */ import DueDateSelectors from './due_date_select'; diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js index 73791edaebb..5bc7f8d9cb9 100644 --- a/app/assets/javascripts/issuable_context.js +++ b/app/assets/javascripts/issuable_context.js @@ -1,33 +1,35 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, comma-dangle, quotes, prefer-arrow-callback, consistent-return, one-var, no-var, one-var-declaration-per-line, no-underscore-dangle, max-len */ import Cookies from 'js-cookie'; import bp from './breakpoints'; import UsersSelect from './users_select'; const PARTICIPANTS_ROW_COUNT = 7; -(function() { - this.IssuableContext = (function() { - function IssuableContext(currentUser) { - this.initParticipants(); - new UsersSelect(currentUser); - $('select.select2').select2({ - width: 'resolve', - dropdownAutoWidth: true - }); - $(".issuable-sidebar .inline-update").on("change", "select", function() { - return $(this).submit(); - }); - $(".issuable-sidebar .inline-update").on("change", ".js-assignee", function() { - return $(this).submit(); - }); - $(document).off('click', '.issuable-sidebar .dropdown-content a').on('click', '.issuable-sidebar .dropdown-content a', function(e) { - return e.preventDefault(); - }); - $(document).off('click', '.edit-link').on('click', '.edit-link', function(e) { - var $block, $selectbox; +export default class IssuableContext { + constructor(currentUser) { + this.initParticipants(); + this.userSelect = new UsersSelect(currentUser); + + $('select.select2').select2({ + width: 'resolve', + dropdownAutoWidth: true, + }); + + $('.issuable-sidebar .inline-update').on('change', 'select', function onClickSelect() { + return $(this).submit(); + }); + $('.issuable-sidebar .inline-update').on('change', '.js-assignee', function onClickAssignee() { + return $(this).submit(); + }); + $(document) + .off('click', '.issuable-sidebar .dropdown-content a') + .on('click', '.issuable-sidebar .dropdown-content a', e => e.preventDefault()); + + $(document) + .off('click', '.edit-link') + .on('click', '.edit-link', function onClickEdit(e) { e.preventDefault(); - $block = $(this).parents('.block'); - $selectbox = $block.find('.selectbox'); + const $block = $(this).parents('.block'); + const $selectbox = $block.find('.selectbox'); if ($selectbox.is(':visible')) { $selectbox.hide(); $block.find('.value').show(); @@ -35,46 +37,43 @@ const PARTICIPANTS_ROW_COUNT = 7; $selectbox.show(); $block.find('.value').hide(); } + if ($selectbox.is(':visible')) { - return setTimeout(function() { - return $block.find('.dropdown-menu-toggle').trigger('click'); - }, 0); + setTimeout(() => $block.find('.dropdown-menu-toggle').trigger('click'), 0); } }); - window.addEventListener('beforeunload', function() { - // collapsed_gutter cookie hides the sidebar - var bpBreakpoint = bp.getBreakpointSize(); - if (bpBreakpoint === 'xs' || bpBreakpoint === 'sm') { - Cookies.set('collapsed_gutter', true); - } - }); - } - IssuableContext.prototype.initParticipants = function() { - $(document).on('click', '.js-participants-more', this.toggleHiddenParticipants); - return $('.js-participants-author').each(function(i) { - if (i >= PARTICIPANTS_ROW_COUNT) { - return $(this).addClass('js-participants-hidden').hide(); - } - }); - }; + window.addEventListener('beforeunload', () => { + // collapsed_gutter cookie hides the sidebar + const bpBreakpoint = bp.getBreakpointSize(); + if (bpBreakpoint === 'xs' || bpBreakpoint === 'sm') { + Cookies.set('collapsed_gutter', true); + } + }); + } - IssuableContext.prototype.toggleHiddenParticipants = function() { - const currentText = $(this).text().trim(); - const lessText = $(this).data('less-text'); - const originalText = $(this).data('original-text'); + initParticipants() { + $(document).on('click', '.js-participants-more', this.toggleHiddenParticipants); + return $('.js-participants-author').each(function forEachAuthor(i) { + if (i >= PARTICIPANTS_ROW_COUNT) { + $(this).addClass('js-participants-hidden').hide(); + } + }); + } - if (currentText === originalText) { - $(this).text(lessText); + toggleHiddenParticipants() { + const currentText = $(this).text().trim(); + const lessText = $(this).data('less-text'); + const originalText = $(this).data('original-text'); - if (gl.lazyLoader) gl.lazyLoader.loadCheck(); - } else { - $(this).text(originalText); - } + if (currentText === originalText) { + $(this).text(lessText); - $('.js-participants-hidden').toggle(); - }; + if (gl.lazyLoader) gl.lazyLoader.loadCheck(); + } else { + $(this).text(originalText); + } - return IssuableContext; - })(); -}).call(window); + $('.js-participants-hidden').toggle(); + } +} diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 00a94c45b47..94951cfbdfe 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -58,8 +58,6 @@ import './gl_form'; import './groups_select'; import './header'; import initImporterStatus from './importer_status'; -import './issuable_index'; -import './issuable_context'; import './issuable_form'; import './issue'; import './issue_status_select'; diff --git a/spec/javascripts/issuable_context_spec.js b/spec/javascripts/issuable_context_spec.js index bcb2b7b24a0..f266209027a 100644 --- a/spec/javascripts/issuable_context_spec.js +++ b/spec/javascripts/issuable_context_spec.js @@ -1,6 +1,5 @@ -/* global IssuableContext */ -import '~/issuable_context'; import $ from 'jquery'; +import IssuableContext from '~/issuable_context'; describe('IssuableContext', () => { describe('toggleHiddenParticipants', () => { diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js index e47adc49224..775a81d3d78 100644 --- a/spec/javascripts/labels_issue_sidebar_spec.js +++ b/spec/javascripts/labels_issue_sidebar_spec.js @@ -1,5 +1,5 @@ /* eslint-disable no-new */ -/* global IssuableContext */ +import IssuableContext from '~/issuable_context'; /* global LabelsSelect */ import '~/gl_dropdown'; -- cgit v1.2.1 From 94743e0e8fa80f9dc2f2200f33c7047e859ee12d Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 26 Oct 2017 23:21:55 +0300 Subject: Remove issuable form from global namespace --- .../javascripts/boards/components/board_sidebar.js | 2 +- app/assets/javascripts/dispatcher.js | 2 +- app/assets/javascripts/issuable_form.js | 195 ++++++++++----------- spec/javascripts/labels_issue_sidebar_spec.js | 1 - 4 files changed, 98 insertions(+), 102 deletions(-) diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index 6fa41dbd0dd..5d27518ac85 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -1,5 +1,4 @@ /* eslint-disable comma-dangle, space-before-function-paren, no-new */ -import IssuableContext from '../../issuable_context'; /* global MilestoneSelect */ /* global LabelsSelect */ /* global Sidebar */ @@ -11,6 +10,7 @@ import AssigneeTitle from '../../sidebar/components/assignees/assignee_title'; import Assignees from '../../sidebar/components/assignees/assignees'; import DueDateSelectors from '../../due_date_select'; import './sidebar/remove_issue'; +import IssuableContext from '../../issuable_context'; const Store = gl.issueBoards.BoardsStore; diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index d5749f650b6..4ae23f0da55 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -2,7 +2,7 @@ /* global ProjectSelect */ import IssuableIndex from './issuable_index'; /* global Milestone */ -/* global IssuableForm */ +import IssuableForm from './issuable_form'; /* global LabelsSelect */ /* global MilestoneSelect */ /* global NewBranchForm */ diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js index cd2562bc6a9..765c84f673a 100644 --- a/app/assets/javascripts/issuable_form.js +++ b/app/assets/javascripts/issuable_form.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-useless-escape, no-new, quotes, object-shorthand, no-unused-vars, comma-dangle, no-alert, consistent-return, no-else-return, prefer-template, one-var, one-var-declaration-per-line, curly, max-len */ +/* eslint-disable func-names, prefer-rest-params, wrap-iife, no-use-before-define, no-useless-escape, no-new, object-shorthand, no-unused-vars, comma-dangle, no-alert, consistent-return, no-else-return, prefer-template, one-var, one-var-declaration-per-line, curly, max-len */ /* global GitLab */ /* global Autosave */ @@ -8,103 +8,100 @@ import GfmAutoComplete from './gfm_auto_complete'; import ZenMode from './zen_mode'; import { parsePikadayDate, pikadayToString } from './lib/utils/datefix'; -(function() { - this.IssuableForm = (function() { - IssuableForm.prototype.wipRegex = /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i; - - function IssuableForm(form) { - var $issuableDueDate, calendar; - this.form = form; - this.toggleWip = this.toggleWip.bind(this); - this.renderWipExplanation = this.renderWipExplanation.bind(this); - this.resetAutosave = this.resetAutosave.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); - new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources).setup(); - new UsersSelect(); - new ZenMode(); - this.titleField = this.form.find("input[name*='[title]']"); - this.descriptionField = this.form.find("textarea[name*='[description]']"); - if (!(this.titleField.length && this.descriptionField.length)) { - return; - } - this.initAutosave(); - this.form.on("submit", this.handleSubmit); - this.form.on("click", ".btn-cancel", this.resetAutosave); - this.initWip(); - $issuableDueDate = $('#issuable-due-date'); - if ($issuableDueDate.length) { - calendar = new Pikaday({ - field: $issuableDueDate.get(0), - theme: 'gitlab-theme animate-picker', - format: 'yyyy-mm-dd', - container: $issuableDueDate.parent().get(0), - parse: dateString => parsePikadayDate(dateString), - toString: date => pikadayToString(date), - onSelect: function(dateText) { - $issuableDueDate.val(calendar.toString(dateText)); - } - }); - calendar.setDate(parsePikadayDate($issuableDueDate.val())); - } +export default class IssuableForm { + constructor(form) { + this.form = form; + this.toggleWip = this.toggleWip.bind(this); + this.renderWipExplanation = this.renderWipExplanation.bind(this); + this.resetAutosave = this.resetAutosave.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + this.wipRegex = /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i; + + new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources).setup(); + new UsersSelect(); + new ZenMode(); + + this.titleField = this.form.find('input[name*="[title]"]'); + this.descriptionField = this.form.find('textarea[name*="[description]"]'); + if (!(this.titleField.length && this.descriptionField.length)) { + return; } - IssuableForm.prototype.initAutosave = function() { - new Autosave(this.titleField, [document.location.pathname, document.location.search, "title"]); - return new Autosave(this.descriptionField, [document.location.pathname, document.location.search, "description"]); - }; - - IssuableForm.prototype.handleSubmit = function() { - return this.resetAutosave(); - }; - - IssuableForm.prototype.resetAutosave = function() { - this.titleField.data("autosave").reset(); - return this.descriptionField.data("autosave").reset(); - }; - - IssuableForm.prototype.initWip = function() { - this.$wipExplanation = this.form.find(".js-wip-explanation"); - this.$noWipExplanation = this.form.find(".js-no-wip-explanation"); - if (!(this.$wipExplanation.length && this.$noWipExplanation.length)) { - return; - } - this.form.on("click", ".js-toggle-wip", this.toggleWip); - this.titleField.on("keyup blur", this.renderWipExplanation); - return this.renderWipExplanation(); - }; - - IssuableForm.prototype.workInProgress = function() { - return this.wipRegex.test(this.titleField.val()); - }; - - IssuableForm.prototype.renderWipExplanation = function() { - if (this.workInProgress()) { - this.$wipExplanation.show(); - return this.$noWipExplanation.hide(); - } else { - this.$wipExplanation.hide(); - return this.$noWipExplanation.show(); - } - }; - - IssuableForm.prototype.toggleWip = function(event) { - event.preventDefault(); - if (this.workInProgress()) { - this.removeWip(); - } else { - this.addWip(); - } - return this.renderWipExplanation(); - }; - - IssuableForm.prototype.removeWip = function() { - return this.titleField.val(this.titleField.val().replace(this.wipRegex, "")); - }; - - IssuableForm.prototype.addWip = function() { - return this.titleField.val("WIP: " + (this.titleField.val())); - }; - - return IssuableForm; - })(); -}).call(window); + this.initAutosave(); + this.form.on('submit', this.handleSubmit); + this.form.on('click', '.btn-cancel', this.resetAutosave); + this.initWip(); + + const $issuableDueDate = $('#issuable-due-date'); + + if ($issuableDueDate.length) { + const calendar = new Pikaday({ + field: $issuableDueDate.get(0), + theme: 'gitlab-theme animate-picker', + format: 'yyyy-mm-dd', + container: $issuableDueDate.parent().get(0), + parse: dateString => parsePikadayDate(dateString), + toString: date => pikadayToString(date), + onSelect: dateText => $issuableDueDate.val(calendar.toString(dateText)), + }); + calendar.setDate(parsePikadayDate($issuableDueDate.val())); + } + } + + initAutosave() { + new Autosave(this.titleField, [document.location.pathname, document.location.search, 'title']); + return new Autosave(this.descriptionField, [document.location.pathname, document.location.search, 'description']); + } + + handleSubmit() { + return this.resetAutosave(); + } + + resetAutosave() { + this.titleField.data('autosave').reset(); + return this.descriptionField.data('autosave').reset(); + } + + initWip() { + this.$wipExplanation = this.form.find('.js-wip-explanation'); + this.$noWipExplanation = this.form.find('.js-no-wip-explanation'); + if (!(this.$wipExplanation.length && this.$noWipExplanation.length)) { + return; + } + this.form.on('click', '.js-toggle-wip', this.toggleWip); + this.titleField.on('keyup blur', this.renderWipExplanation); + return this.renderWipExplanation(); + } + + workInProgress() { + return this.wipRegex.test(this.titleField.val()); + } + + renderWipExplanation() { + if (this.workInProgress()) { + this.$wipExplanation.show(); + return this.$noWipExplanation.hide(); + } else { + this.$wipExplanation.hide(); + return this.$noWipExplanation.show(); + } + } + + toggleWip(event) { + event.preventDefault(); + if (this.workInProgress()) { + this.removeWip(); + } else { + this.addWip(); + } + return this.renderWipExplanation(); + } + + removeWip() { + return this.titleField.val(this.titleField.val().replace(this.wipRegex, '')); + } + + addWip() { + this.titleField.val(`WIP: ${(this.titleField.val())}`); + } +} diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js index 775a81d3d78..4e66304e0ad 100644 --- a/spec/javascripts/labels_issue_sidebar_spec.js +++ b/spec/javascripts/labels_issue_sidebar_spec.js @@ -6,7 +6,6 @@ import '~/gl_dropdown'; import 'select2'; import '~/api'; import '~/create_label'; -import '~/issuable_context'; import '~/users_select'; import '~/labels_select'; -- cgit v1.2.1 From 9ced16d914861933293402cde33b079536e49325 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Fri, 27 Oct 2017 13:12:47 +0200 Subject: Add missing circuitbreaker metrics to prometheus documentation --- doc/administration/monitoring/prometheus/gitlab_metrics.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index 6baae20d16a..11d5e077a36 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -20,7 +20,7 @@ it, the client IP needs to be [included in a whitelist][whitelist]. Currently the embedded Prometheus server is not automatically configured to collect metrics from this endpoint. We recommend setting up another Prometheus server, because the embedded server configuration is overwritten once every -[reconfigure of GitLab][reconfigure]. In the future this will not be required. +[reconfigure of GitLab][reconfigure]. In the future this will not be required. ## Metrics available @@ -45,6 +45,8 @@ In this experimental phase, only a few metrics are available: | redis_ping_success | Gauge | 9.4 | Whether or not the last redis ping succeeded | | redis_ping_latency_seconds | Gauge | 9.4 | Round trip time of the redis ping | | user_session_logins_total | Counter | 9.4 | Counter of how many users have logged in | +| filesystem_circuitbreaker_latency_seconds | Histogram | 9.5 | Latency of the stat check the circuitbreaker uses to probe a shard | +| filesystem_circuitbreaker | Gauge | 9.5 | Wether or not the circuit for a certain shard is broken or not | ## Metrics shared directory -- cgit v1.2.1 From ba9b4c4de86aa816e5ddc7a9cde9193c43835223 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 27 Oct 2017 14:20:55 +0100 Subject: Avoid hitting statement timeout finding MR pipelines For MRs with many thousands of commits, `SELECT DISTINCT(sha)` will be very slow. What we can't do to fix this: 1. Add an index. Postgres won't use it for DISTINCT without a lot of ceremony. 2. Do the `uniq` in Ruby. That can still be very slow with hundreds of thousands of commits. 3. Use a subquery. We haven't removed the `st_commits` column yet, but we will soon. Until 3 is available to us, we can just do 2, but also add a limit clause. There is no ordering, so this may return different results, but our goal with these MRs is just to get them to load, so it's not a huge deal. --- app/models/merge_request.rb | 2 +- ...rycanceled-error-canceling-statement-due-to-statement-timeout.yml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/39054-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index c3fae16d109..b0a0c753c09 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -874,7 +874,7 @@ class MergeRequest < ActiveRecord::Base # def all_commit_shas if persisted? - column_shas = MergeRequestDiffCommit.where(merge_request_diff: merge_request_diffs).pluck('DISTINCT(sha)') + column_shas = MergeRequestDiffCommit.where(merge_request_diff: merge_request_diffs).limit(10_000).pluck('sha') serialised_shas = merge_request_diffs.where.not(st_commits: nil).flat_map(&:commit_shas) (column_shas + serialised_shas).uniq diff --git a/changelogs/unreleased/39054-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml b/changelogs/unreleased/39054-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml new file mode 100644 index 00000000000..47bf30ecb5a --- /dev/null +++ b/changelogs/unreleased/39054-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml @@ -0,0 +1,5 @@ +--- +title: Stop merge requests with thousands of commits from timing out +merge_request: 15063 +author: +type: performance -- cgit v1.2.1 From d6cd7006aef0199fd346ead55bf094afcf696d40 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 25 Oct 2017 21:15:35 +0300 Subject: Fix broken Members link when relative URL root paths are used Navigating to the "Members" section of a project would omit the relative URL root path. Fix the redirection and eliminate the need for a redirection in the first place. Fix other redirections failing to work with relative roots --- app/views/layouts/nav/sidebar/_project.html.haml | 2 +- .../sh-fix-broken-redirection-relative-url-root.yml | 5 +++++ config/routes/ci.rb | 2 +- config/routes/project.rb | 2 +- config/routes/snippets.rb | 2 +- config/routes/user.rb | 12 ++++++------ 6 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 changelogs/unreleased/sh-fix-broken-redirection-relative-url-root.yml diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index f82207559a3..66146e61263 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -274,7 +274,7 @@ Members %ul.sidebar-sub-level-items.is-fly-out-only = nav_link(path: %w[members#show], html_options: { class: "fly-out-top-item" } ) do - = link_to project_settings_members_path(@project) do + = link_to project_project_members_path(@project) do %strong.fly-out-top-item-name #{ _('Members') } diff --git a/changelogs/unreleased/sh-fix-broken-redirection-relative-url-root.yml b/changelogs/unreleased/sh-fix-broken-redirection-relative-url-root.yml new file mode 100644 index 00000000000..96e5195d247 --- /dev/null +++ b/changelogs/unreleased/sh-fix-broken-redirection-relative-url-root.yml @@ -0,0 +1,5 @@ +--- +title: Fix broken Members link when relative URL root paths are used +merge_request: +author: +type: fixed diff --git a/config/routes/ci.rb b/config/routes/ci.rb index cbd4c2db852..60c1724bc05 100644 --- a/config/routes/ci.rb +++ b/config/routes/ci.rb @@ -1,5 +1,5 @@ namespace :ci do resource :lint, only: [:show, :create] - root to: redirect('/') + root to: redirect('') end diff --git a/config/routes/project.rb b/config/routes/project.rb index d05fe11f233..9f553085d50 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -393,7 +393,7 @@ constraints(ProjectUrlConstrainer.new) do end end namespace :settings do - get :members, to: redirect('/%{namespace_id}/%{project_id}/project_members') + get :members, to: redirect("%{namespace_id}/%{project_id}/project_members") resource :ci_cd, only: [:show], controller: 'ci_cd' resource :integrations, only: [:show] resource :repository, only: [:show], controller: :repository diff --git a/config/routes/snippets.rb b/config/routes/snippets.rb index 0a4ebac3ca3..81bc890d86b 100644 --- a/config/routes/snippets.rb +++ b/config/routes/snippets.rb @@ -17,5 +17,5 @@ resources :snippets, concerns: :awardable do end end -get '/s/:username', to: redirect('/u/%{username}/snippets'), +get '/s/:username', to: redirect('u/%{username}/snippets'), constraints: { username: /[a-zA-Z.0-9_\-]+(? Date: Mon, 30 Oct 2017 00:24:11 +0900 Subject: Enable NestingDepth (level 6) on scss-lint --- .scss-lint.yml | 3 ++- app/assets/stylesheets/framework/gitlab-theme.scss | 7 ++----- app/assets/stylesheets/framework/header.scss | 6 ++---- app/assets/stylesheets/pages/notes.scss | 12 +----------- changelogs/unreleased/39582-nestingdepth-6.yml | 5 +++++ 5 files changed, 12 insertions(+), 21 deletions(-) create mode 100644 changelogs/unreleased/39582-nestingdepth-6.yml diff --git a/.scss-lint.yml b/.scss-lint.yml index 73f8d27f78c..d2c972fa9c4 100644 --- a/.scss-lint.yml +++ b/.scss-lint.yml @@ -121,7 +121,8 @@ linters: # Avoid nesting selectors too deeply. NestingDepth: - enabled: false + enabled: true + max_depth: 6 # Always use placeholder selectors in @extend. PlaceholderInExtend: diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss index 52b87de7a3d..dc591c06c88 100644 --- a/app/assets/stylesheets/framework/gitlab-theme.scss +++ b/app/assets/stylesheets/framework/gitlab-theme.scss @@ -216,12 +216,9 @@ body { color: $theme-gray-900; } - &.active > a { + &.active > a, + &.active > a:hover { color: $white-light; - - &:hover { - color: $white-light; - } } } } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index d79444fad79..62ba74ff582 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -239,10 +239,8 @@ fill: currentColor; } - &.header-user-dropdown-toggle { - .header-user-avatar { - border-color: $white-light; - } + &.header-user-dropdown-toggle .header-user-avatar { + border-color: $white-light; } } } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 3bd0e3ad535..312917bd13a 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -269,7 +269,7 @@ ul.notes { display: none; } - &.system-note-commit-list { + &.system-note-commit-list:not(.hide-shade) { max-height: 70px; overflow: hidden; display: block; @@ -291,16 +291,6 @@ ul.notes { bottom: 0; background: linear-gradient(rgba($white-light, 0.1) -100px, $white-light 100%); } - - &.hide-shade { - max-height: 100%; - overflow: auto; - - &::after { - display: none; - background: transparent; - } - } } } } diff --git a/changelogs/unreleased/39582-nestingdepth-6.yml b/changelogs/unreleased/39582-nestingdepth-6.yml new file mode 100644 index 00000000000..efe15f0a5f3 --- /dev/null +++ b/changelogs/unreleased/39582-nestingdepth-6.yml @@ -0,0 +1,5 @@ +--- +title: Enable NestingDepth (level 6) on scss-lint +merge_request: 15073 +author: Takuya Noguchi +type: other -- cgit v1.2.1 From 3c454b80ef14f975ece751049e10240adce2895f Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 30 Oct 2017 14:48:45 +0800 Subject: Add docs for backing up to Google Cloud Storage --- doc/raketasks/backup_restore.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index e4c09b2b507..2fff6ab13f1 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -126,6 +126,25 @@ sudo gitlab-rake gitlab:backup:create SKIP=db,uploads sudo -u git -H bundle exec rake gitlab:backup:create SKIP=db,uploads RAILS_ENV=production ``` +#### Using Google Cloud Storage + +If you want to use Google Cloud Storage to save backups, go to the storage +settings page, select "Interoperability" and create an access key there. +Copy "Access Key" and "Secret", fill the configurations in +`/etc/gitlab/gitlab.rb`: + +```ruby +gitlab_rails['backup_upload_connection'] = { + 'provider' => 'Google', + 'google_storage_access_key_id' => 'Access Key', + 'google_storage_secret_access_key' => 'Secret' +} +gitlab_rails['backup_upload_remote_directory'] = 'my.google.bucket' +``` + +Make sure you have project and bucket setting up correctly, and run +`sudo gitlab-ctl reconfigure` after making the changes. + ### Uploading backups to a remote (cloud) storage Starting with GitLab 7.4 you can let the backup script upload the '.tar' file it creates. -- cgit v1.2.1 From 5e0308d5b920f4210f2f00da1e8f3e95f2ce1ab4 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 30 Oct 2017 13:43:54 +0200 Subject: Split S3 and Google Cloud Storage sections in backup doc --- doc/raketasks/backup_restore.md | 142 +++++++++++++++++++++++++--------------- 1 file changed, 90 insertions(+), 52 deletions(-) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 2fff6ab13f1..54c3e20d61d 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -126,25 +126,6 @@ sudo gitlab-rake gitlab:backup:create SKIP=db,uploads sudo -u git -H bundle exec rake gitlab:backup:create SKIP=db,uploads RAILS_ENV=production ``` -#### Using Google Cloud Storage - -If you want to use Google Cloud Storage to save backups, go to the storage -settings page, select "Interoperability" and create an access key there. -Copy "Access Key" and "Secret", fill the configurations in -`/etc/gitlab/gitlab.rb`: - -```ruby -gitlab_rails['backup_upload_connection'] = { - 'provider' => 'Google', - 'google_storage_access_key_id' => 'Access Key', - 'google_storage_secret_access_key' => 'Secret' -} -gitlab_rails['backup_upload_remote_directory'] = 'my.google.bucket' -``` - -Make sure you have project and bucket setting up correctly, and run -`sudo gitlab-ctl reconfigure` after making the changes. - ### Uploading backups to a remote (cloud) storage Starting with GitLab 7.4 you can let the backup script upload the '.tar' file it creates. @@ -155,44 +136,54 @@ In the example below we use Amazon S3 for storage, but Fog also lets you use for AWS, Google, OpenStack Swift, Rackspace and Aliyun as well. A local driver is [also available](#uploading-to-locally-mounted-shares). -For omnibus packages, add the following to `/etc/gitlab/gitlab.rb`: +#### Using Amazon S3 -```ruby -gitlab_rails['backup_upload_connection'] = { - 'provider' => 'AWS', - 'region' => 'eu-west-1', - 'aws_access_key_id' => 'AKIAKIAKI', - 'aws_secret_access_key' => 'secret123' - # If using an IAM Profile, don't configure aws_access_key_id & aws_secret_access_key - # 'use_iam_profile' => true -} -gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket' -``` +For Omnibus GitLab packages: + +1. Add the following to `/etc/gitlab/gitlab.rb`: + + ```ruby + gitlab_rails['backup_upload_connection'] = { + 'provider' => 'AWS', + 'region' => 'eu-west-1', + 'aws_access_key_id' => 'AKIAKIAKI', + 'aws_secret_access_key' => 'secret123' + # If using an IAM Profile, don't configure aws_access_key_id & aws_secret_access_key + # 'use_iam_profile' => true + } + gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket' + ``` + +1. [Reconfigure GitLab] for the changes to take effect -Make sure to run `sudo gitlab-ctl reconfigure` after editing `/etc/gitlab/gitlab.rb` to reflect the changes. +--- For installations from source: -```yaml - backup: - # snip - upload: - # Fog storage connection settings, see http://fog.io/storage/ . - connection: - provider: AWS - region: eu-west-1 - aws_access_key_id: AKIAKIAKI - aws_secret_access_key: 'secret123' - # If using an IAM Profile, leave aws_access_key_id & aws_secret_access_key empty - # ie. aws_access_key_id: '' - # use_iam_profile: 'true' - # The remote 'directory' to store your backups. For S3, this would be the bucket name. - remote_directory: 'my.s3.bucket' - # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional - # encryption: 'AES256' - # Specifies Amazon S3 storage class to use for backups, this is optional - # storage_class: 'STANDARD' -``` +1. Edit `home/git/gitlab/config/gitlab.yml`: + + ```yaml + backup: + # snip + upload: + # Fog storage connection settings, see http://fog.io/storage/ . + connection: + provider: AWS + region: eu-west-1 + aws_access_key_id: AKIAKIAKI + aws_secret_access_key: 'secret123' + # If using an IAM Profile, leave aws_access_key_id & aws_secret_access_key empty + # ie. aws_access_key_id: '' + # use_iam_profile: 'true' + # The remote 'directory' to store your backups. For S3, this would be the bucket name. + remote_directory: 'my.s3.bucket' + # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional + # encryption: 'AES256' + # Specifies Amazon S3 storage class to use for backups, this is optional + # storage_class: 'STANDARD' + ``` + +1. [Restart GitLab] for the changes to take effect If you are uploading your backups to S3 you will probably want to create a new IAM user with restricted access rights. To give the upload user access only for @@ -245,6 +236,50 @@ with the name of your bucket: } ``` +#### Using Google Cloud Storage + +If you want to use Google Cloud Storage to save backups, you'll have to create +an access key from the Google console first: + +1. Go to the storage settings page https://console.cloud.google.com/storage/settings +1. Select "Interoperability" and create an access key +1. Make note of the "Access Key" and "Secret" and replace them in the + configurations below +1. Make sure you already have a bucket created + +For Omnibus GitLab packages: + +1. Edit `/etc/gitlab/gitlab.rb`: + + ```ruby + gitlab_rails['backup_upload_connection'] = { + 'provider' => 'Google', + 'google_storage_access_key_id' => 'Access Key', + 'google_storage_secret_access_key' => 'Secret' + } + gitlab_rails['backup_upload_remote_directory'] = 'my.google.bucket' + ``` + +1. [Reconfigure GitLab] for the changes to take effect + +--- + +For installations from source: + +1. Edit `home/git/gitlab/config/gitlab.yml`: + + ```yaml + backup: + upload: + connection: + provider: 'Google' + google_storage_access_key_id: 'Access Key' + google_storage_secret_access_key: 'Secret' + remote_directory: 'my.google.bucket' + ``` + +1. [Restart GitLab] for the changes to take effect + ### Uploading to locally mounted shares You may also send backups to a mounted share (`NFS` / `CIFS` / `SMB` / etc.) by @@ -573,3 +608,6 @@ The rake task runs this as the `gitlab` user which does not have the superuser a Those objects have no influence on the database backup/restore but they give this annoying warning. For more information see similar questions on postgresql issue tracker[here](http://www.postgresql.org/message-id/201110220712.30886.adrian.klaver@gmail.com) and [here](http://www.postgresql.org/message-id/2039.1177339749@sss.pgh.pa.us) as well as [stack overflow](http://stackoverflow.com/questions/4368789/error-must-be-owner-of-language-plpgsql). + +[reconfigure GitLab]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure +[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source -- cgit v1.2.1 From ab64a7f21bd6944d9c4e5400ecdc4a6c854788fe Mon Sep 17 00:00:00 2001 From: Pawel Chojnacki Date: Mon, 30 Oct 2017 13:20:28 +0100 Subject: Update Prometheus Gem to fix problem caused by reading bad data. --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 5adba4d58df..8c9edf5c733 100644 --- a/Gemfile +++ b/Gemfile @@ -281,7 +281,7 @@ group :metrics do gem 'influxdb', '~> 0.2', require: false # Prometheus - gem 'prometheus-client-mmap', '~>0.7.0.beta17' + gem 'prometheus-client-mmap', '~>0.7.0.beta18' gem 'raindrops', '~> 0.18' end diff --git a/Gemfile.lock b/Gemfile.lock index 53efb1c76c2..34f4e6af7e7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -623,7 +623,7 @@ GEM parser unparser procto (0.0.3) - prometheus-client-mmap (0.7.0.beta17) + prometheus-client-mmap (0.7.0.beta18) mmap2 (~> 2.2, >= 2.2.7) pry (0.10.4) coderay (~> 1.1.0) @@ -1106,7 +1106,7 @@ DEPENDENCIES pg (~> 0.18.2) poltergeist (~> 1.9.0) premailer-rails (~> 1.9.7) - prometheus-client-mmap (~> 0.7.0.beta17) + prometheus-client-mmap (~> 0.7.0.beta18) pry-byebug (~> 3.4.1) pry-rails (~> 0.3.4) rack-attack (~> 4.4.1) -- cgit v1.2.1 From 3a844b7978eb956316c9979949ddb7669a06aca6 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 30 Oct 2017 12:23:52 +0000 Subject: Fix queried element --- app/assets/javascripts/importer_status.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js index 99f229cc047..1dc70872d92 100644 --- a/app/assets/javascripts/importer_status.js +++ b/app/assets/javascripts/importer_status.js @@ -72,10 +72,10 @@ class ImporterStatus { // eslint-disable-next-line consistent-return export default function initImporterStatus() { - const importerStatus = $('.js-importer-status'); - const data = importerStatus.dataset; + const importerStatus = document.querySelector('.js-importer-status'); - if (importerStatus.length) { + if (importerStatus) { + const data = importerStatus.dataset; return new ImporterStatus(data.jobsImportPath, data.importPath); } } -- cgit v1.2.1 From 7ba7fa5048f26373baf3524af0612e9f353488ec Mon Sep 17 00:00:00 2001 From: AlexWayfer Date: Mon, 30 Oct 2017 12:30:31 +0000 Subject: Fix 500 error for old (somewhat) MRs --- .../unreleased/fix-500-on-old-merge-requests.yml | 5 +++ lib/gitlab/diff/position.rb | 8 +++-- spec/lib/gitlab/diff/position_spec.rb | 37 ++++++++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/fix-500-on-old-merge-requests.yml diff --git a/changelogs/unreleased/fix-500-on-old-merge-requests.yml b/changelogs/unreleased/fix-500-on-old-merge-requests.yml new file mode 100644 index 00000000000..765d7466819 --- /dev/null +++ b/changelogs/unreleased/fix-500-on-old-merge-requests.yml @@ -0,0 +1,5 @@ +--- +title: Fix 500 errors caused by empty diffs in some discussions +merge_request: 14945 +author: Alexander Popov +type: fixed diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb index bd0a9502a5e..ccfb908bcca 100644 --- a/lib/gitlab/diff/position.rb +++ b/lib/gitlab/diff/position.rb @@ -94,7 +94,9 @@ module Gitlab end def diff_file(repository) - @diff_file ||= begin + return @diff_file if defined?(@diff_file) + + @diff_file = begin if RequestStore.active? key = { project_id: repository.project.id, @@ -122,8 +124,8 @@ module Gitlab def find_diff_file(repository) return unless diff_refs.complete? - - diff_refs.compare_in(repository.project).diffs(paths: paths, expanded: true).diff_files.first + return unless comparison = diff_refs.compare_in(repository.project) + comparison.diffs(paths: paths, expanded: true).diff_files.first end def get_formatter_class(type) diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb index 245f24e96d4..677eb373d22 100644 --- a/spec/lib/gitlab/diff/position_spec.rb +++ b/spec/lib/gitlab/diff/position_spec.rb @@ -364,6 +364,43 @@ describe Gitlab::Diff::Position do end end + describe "position for a missing ref" do + let(:diff_refs) do + Gitlab::Diff::DiffRefs.new( + base_sha: "not_existing_sha", + head_sha: "existing_sha" + ) + end + + subject do + described_class.new( + old_path: "files/ruby/feature.rb", + new_path: "files/ruby/feature.rb", + old_line: 3, + new_line: nil, + diff_refs: diff_refs + ) + end + + describe "#diff_file" do + it "does not raise exception" do + expect { subject.diff_file(project.repository) }.not_to raise_error + end + end + + describe "#diff_line" do + it "does not raise exception" do + expect { subject.diff_line(project.repository) }.not_to raise_error + end + end + + describe "#line_code" do + it "does not raise exception" do + expect { subject.line_code(project.repository) }.not_to raise_error + end + end + end + describe "position for a file in the initial commit" do let(:commit) { project.commit("1a0b36b3cdad1d2ee32457c102a8c0b7056fa863") } -- cgit v1.2.1 From 0cf7afac4f00f6720b6fbb617211ea035bfa518e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Javier=20L=C3=B3pez?= Date: Mon, 30 Oct 2017 12:44:28 +0000 Subject: Changed project select to use it independently and not onlt as a combobutton --- app/assets/javascripts/project_select.js | 24 +++++++++++++--------- .../fix-project-select-js-without-button.yml | 5 +++++ 2 files changed, 19 insertions(+), 10 deletions(-) create mode 100644 changelogs/unreleased/fix-project-select-js-without-button.yml diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js index fb01390f91c..bffc85e6315 100644 --- a/app/assets/javascripts/project_select.js +++ b/app/assets/javascripts/project_select.js @@ -2,13 +2,15 @@ import Api from './api'; import ProjectSelectComboButton from './project_select_combo_button'; -(function() { - this.ProjectSelect = (function() { +(function () { + this.ProjectSelect = (function () { function ProjectSelect() { $('.ajax-project-select').each(function(i, select) { var placeholder; + const simpleFilter = $(select).data('simple-filter') || false; this.groupId = $(select).data('group-id'); this.includeGroups = $(select).data('include-groups'); + this.allProjects = $(select).data('all-projects') || false; this.orderBy = $(select).data('order-by') || 'id'; this.withIssuesEnabled = $(select).data('with-issues-enabled'); this.withMergeRequestsEnabled = $(select).data('with-merge-requests-enabled'); @@ -21,10 +23,10 @@ import ProjectSelectComboButton from './project_select_combo_button'; $(select).select2({ placeholder: placeholder, minimumInputLength: 0, - query: (function(_this) { - return function(query) { + query: (function (_this) { + return function (query) { var finalCallback, projectsCallback; - finalCallback = function(projects) { + finalCallback = function (projects) { var data; data = { results: projects @@ -32,9 +34,9 @@ import ProjectSelectComboButton from './project_select_combo_button'; return query.callback(data); }; if (_this.includeGroups) { - projectsCallback = function(projects) { + projectsCallback = function (projects) { var groupsCallback; - groupsCallback = function(groups) { + groupsCallback = function (groups) { var data; data = groups.concat(projects); return finalCallback(data); @@ -50,23 +52,25 @@ import ProjectSelectComboButton from './project_select_combo_button'; return Api.projects(query.term, { order_by: _this.orderBy, with_issues_enabled: _this.withIssuesEnabled, - with_merge_requests_enabled: _this.withMergeRequestsEnabled + with_merge_requests_enabled: _this.withMergeRequestsEnabled, + membership: !_this.allProjects, }, projectsCallback); } }; })(this), id: function(project) { + if (simpleFilter) return project.id; return JSON.stringify({ name: project.name, url: project.web_url, }); }, - text: function(project) { + text: function (project) { return project.name_with_namespace || project.name; }, dropdownCssClass: "ajax-project-dropdown" }); - + if (simpleFilter) return select; return new ProjectSelectComboButton(select); }); } diff --git a/changelogs/unreleased/fix-project-select-js-without-button.yml b/changelogs/unreleased/fix-project-select-js-without-button.yml new file mode 100644 index 00000000000..389ca2394f0 --- /dev/null +++ b/changelogs/unreleased/fix-project-select-js-without-button.yml @@ -0,0 +1,5 @@ +--- +title: Use project select dropdown not only as a combobutton +merge_request: 15043 +author: +type: fixed -- cgit v1.2.1 From d7942f223cd30c28933c65bb8efa0cb9277b808e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 30 Oct 2017 15:01:37 +0100 Subject: Allow to disable the Performance Bar and document the `p b` shortcut in its doc page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/models/application_setting.rb | 2 +- ...ppears-enabled-even-though-it-won-t-show-up.yml | 5 +++++ .../monitoring/performance/performance_bar.md | 6 ++++++ doc/workflow/shortcuts.md | 2 +- spec/features/admin/admin_settings_spec.rb | 23 ++++++++++++++++++++++ 5 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/39570-performance-bar-appears-enabled-even-though-it-won-t-show-up.yml diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index f266e7db6da..5e16badabec 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -420,7 +420,7 @@ class ApplicationSetting < ActiveRecord::Base # the enabling/disabling is `performance_bar_allowed_group_id` # - If `enable` is false, we set `performance_bar_allowed_group_id` to `nil` def performance_bar_enabled=(enable) - return if enable + return if Gitlab::Utils.to_boolean(enable) self.performance_bar_allowed_group_id = nil end diff --git a/changelogs/unreleased/39570-performance-bar-appears-enabled-even-though-it-won-t-show-up.yml b/changelogs/unreleased/39570-performance-bar-appears-enabled-even-though-it-won-t-show-up.yml new file mode 100644 index 00000000000..66939d89d69 --- /dev/null +++ b/changelogs/unreleased/39570-performance-bar-appears-enabled-even-though-it-won-t-show-up.yml @@ -0,0 +1,5 @@ +--- +title: Allow to disable the Performance Bar +merge_request: 15084 +author: +type: fixed diff --git a/doc/administration/monitoring/performance/performance_bar.md b/doc/administration/monitoring/performance/performance_bar.md index 68efe0aae5c..b9464945cea 100644 --- a/doc/administration/monitoring/performance/performance_bar.md +++ b/doc/administration/monitoring/performance/performance_bar.md @@ -28,6 +28,12 @@ will be allowed to display the Performance Bar. Make sure _Enable the Performance Bar_ is checked and hit **Save** to save the changes. +Once the Performance Bar is enabled, you will need to press the [p + +b keyboard shortcut](../../../workflow/shortcuts.md) to actually +display it. + +You can toggle the Bar using the same shortcut. + --- ![GitLab Performance Bar Admin Settings](img/performance_bar_configuration_settings.png) diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md index 87416008e98..2e1bd6bfe5c 100644 --- a/doc/workflow/shortcuts.md +++ b/doc/workflow/shortcuts.md @@ -9,7 +9,7 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?' | n | Main navigation | | s | Focus search | | f | Focus filter | -| p b | Show/hide the Performance Bar | +| p + b | Show/hide the Performance Bar | | ? | Show/hide this dialog | | + shift + p | Toggle markdown preview | | | Edit last comment (when focused on an empty textarea) | diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index c490dce7ab0..85561511101 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -95,6 +95,29 @@ feature 'Admin updates settings' do expect(find_field('ED25519 SSH keys').value).to eq(forbidden) end + scenario 'Change Performance Bar settings' do + group = create(:group) + + check 'Enable the Performance Bar' + fill_in 'Allowed group', with: group.path + + click_on 'Save' + + expect(page).to have_content 'Application settings saved successfully' + + expect(find_field('Enable the Performance Bar')).to be_checked + expect(find_field('Allowed group').value).to eq group.path + + uncheck 'Enable the Performance Bar' + + click_on 'Save' + + expect(page).to have_content 'Application settings saved successfully' + + expect(find_field('Enable the Performance Bar')).not_to be_checked + expect(find_field('Allowed group').value).to be_nil + end + def check_all_events page.check('Active') page.check('Push') -- cgit v1.2.1 From 4d70f42055d4393d11ab19d50d504dd5e897789e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 30 Oct 2017 15:37:02 +0100 Subject: Add .gitlab/route-map.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .gitlab/route-map.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitlab/route-map.yml diff --git a/.gitlab/route-map.yml b/.gitlab/route-map.yml new file mode 100644 index 00000000000..0b37dc68f8b --- /dev/null +++ b/.gitlab/route-map.yml @@ -0,0 +1,3 @@ +# Documentation +- source: /doc/(.+?)\.md/ # doc/administration/build_artifacts.md + public: '\1.html' # doc/administration/build_artifacts.html -- cgit v1.2.1 From e80f056712be9013262981ad1bfb1978f7ffbbde Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 30 Oct 2017 16:20:29 +0000 Subject: Refresh open Issue and Merge Request project counter caches when re-opening --- app/services/issues/reopen_service.rb | 1 + app/services/merge_requests/reopen_service.rb | 1 + changelogs/unreleased/39583-reopen-issue-count-cache.yml | 5 +++++ 3 files changed, 7 insertions(+) create mode 100644 changelogs/unreleased/39583-reopen-issue-count-cache.yml diff --git a/app/services/issues/reopen_service.rb b/app/services/issues/reopen_service.rb index 35de4337b15..62b4b4b6a1e 100644 --- a/app/services/issues/reopen_service.rb +++ b/app/services/issues/reopen_service.rb @@ -9,6 +9,7 @@ module Issues notification_service.reopen_issue(issue, current_user) execute_hooks(issue, 'reopen') invalidate_cache_counts(issue, users: issue.assignees) + issue.update_project_counter_caches end issue diff --git a/app/services/merge_requests/reopen_service.rb b/app/services/merge_requests/reopen_service.rb index b9c65be36ec..c599a90f9fe 100644 --- a/app/services/merge_requests/reopen_service.rb +++ b/app/services/merge_requests/reopen_service.rb @@ -11,6 +11,7 @@ module MergeRequests merge_request.reload_diff(current_user) merge_request.mark_as_unchecked invalidate_cache_counts(merge_request, users: merge_request.assignees) + merge_request.update_project_counter_caches end merge_request diff --git a/changelogs/unreleased/39583-reopen-issue-count-cache.yml b/changelogs/unreleased/39583-reopen-issue-count-cache.yml new file mode 100644 index 00000000000..ee35bcbcdae --- /dev/null +++ b/changelogs/unreleased/39583-reopen-issue-count-cache.yml @@ -0,0 +1,5 @@ +--- +title: Refresh open Issue and Merge Request project counter caches when re-opening. +merge_request: 15085 +author: Rob Ede @robjtede +type: fixed -- cgit v1.2.1 From c7f0d2b2af38e1e357f0cebd3a82d562894afe9c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 30 Oct 2017 19:53:14 +0000 Subject: Revert "Merge branch '2518-saved-configuration-for-issue-board' into 'master'" This reverts merge request !15009 --- .../javascripts/boards/filtered_search_boards.js | 9 +- .../javascripts/boards/stores/boards_store.js | 8 +- .../javascripts/filtered_search/dropdown_utils.js | 10 -- .../filtered_search/filtered_search_manager.js | 10 +- .../filtered_search_visual_tokens.js | 15 ++- app/assets/javascripts/labels_select.js | 13 +-- app/assets/javascripts/milestone_select.js | 17 +--- app/assets/javascripts/users_select.js | 7 +- .../vue_shared/components/loading_icon.vue | 8 +- .../vue_shared/components/popup_dialog.vue | 101 ++++++++------------- app/assets/stylesheets/framework/common.scss | 3 - app/assets/stylesheets/framework/dropdowns.scss | 1 - app/assets/stylesheets/framework/modal.scss | 8 -- .../framework/tw_bootstrap_variables.scss | 33 ------- app/assets/stylesheets/pages/repo.scss | 13 +++ app/helpers/boards_helper.rb | 11 +++ 16 files changed, 93 insertions(+), 174 deletions(-) diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js index 184665f395c..3f083655f95 100644 --- a/app/assets/javascripts/boards/filtered_search_boards.js +++ b/app/assets/javascripts/boards/filtered_search_boards.js @@ -11,8 +11,7 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager { // Issue boards is slightly different, we handle all the requests async // instead or reloading the page, we just re-fire the list ajax requests this.isHandledAsync = true; - this.cantEdit = cantEdit.filter(i => typeof i === 'string'); - this.cantEditWithValue = cantEdit.filter(i => typeof i === 'object'); + this.cantEdit = cantEdit; } updateObject(path) { @@ -43,9 +42,7 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager { this.filteredSearchInput.dispatchEvent(new Event('input')); } - canEdit(tokenName, tokenValue) { - if (this.cantEdit.includes(tokenName)) return false; - return this.cantEditWithValue.findIndex(token => token.name === tokenName && - token.value === tokenValue) === -1; + canEdit(tokenName) { + return this.cantEdit.indexOf(tokenName) === -1; } } diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 798d7e0d147..ea82958e80d 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -14,18 +14,16 @@ gl.issueBoards.BoardsStore = { }, state: {}, detail: { - issue: {}, + issue: {} }, moving: { issue: {}, - list: {}, + list: {} }, create () { this.state.lists = []; this.filter.path = getUrlParamsArray().join('&'); - this.detail = { - issue: {}, - }; + this.detail = { issue: {} }; }, addList (listObj, defaultAvatar) { const list = new List(listObj, defaultAvatar); diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js index cf8a9b0402b..8d711e3213c 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js @@ -147,16 +147,6 @@ class DropdownUtils { return dataValue !== null; } - static getVisualTokenValues(visualToken) { - const tokenName = visualToken && visualToken.querySelector('.name').textContent.trim(); - let tokenValue = visualToken && visualToken.querySelector('.value') && visualToken.querySelector('.value').textContent.trim(); - if (tokenName === 'label' && tokenValue) { - // remove leading symbol and wrapping quotes - tokenValue = tokenValue.replace(/^~("|')?(.*)/, '$2').replace(/("|')$/, ''); - } - return { tokenName, tokenValue }; - } - // Determines the full search query (visual tokens + input) static getSearchQuery(untilInput = false) { const container = FilteredSearchContainer.container; diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index 69c57f923b6..7b233842d5a 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -185,8 +185,8 @@ class FilteredSearchManager { if (e.keyCode === 8 || e.keyCode === 46) { const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); - const { tokenName, tokenValue } = gl.DropdownUtils.getVisualTokenValues(lastVisualToken); - const canEdit = tokenName && this.canEdit && this.canEdit(tokenName, tokenValue); + const sanitizedTokenName = lastVisualToken && lastVisualToken.querySelector('.name').textContent.trim(); + const canEdit = sanitizedTokenName && this.canEdit && this.canEdit(sanitizedTokenName); if (this.filteredSearchInput.value === '' && lastVisualToken && canEdit) { this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial(); gl.FilteredSearchVisualTokens.removeLastTokenPartial(); @@ -336,8 +336,8 @@ class FilteredSearchManager { let canClearToken = t.classList.contains('js-visual-token'); if (canClearToken) { - const { tokenName, tokenValue } = gl.DropdownUtils.getVisualTokenValues(t); - canClearToken = this.canEdit && this.canEdit(tokenName, tokenValue); + const tokenKey = t.querySelector('.name').textContent.trim(); + canClearToken = this.canEdit && this.canEdit(tokenKey); } if (canClearToken) { @@ -469,7 +469,7 @@ class FilteredSearchManager { } hasFilteredSearch = true; - const canEdit = this.canEdit && this.canEdit(sanitizedKey, sanitizedValue); + const canEdit = this.canEdit && this.canEdit(sanitizedKey); gl.FilteredSearchVisualTokens.addFilterVisualToken( sanitizedKey, `${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`, diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js index 6139e81fe6d..d2f92929b8a 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js +++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js @@ -38,14 +38,21 @@ class FilteredSearchVisualTokens { } static createVisualTokenElementHTML(canEdit = true) { + let removeTokenMarkup = ''; + if (canEdit) { + removeTokenMarkup = ` +
+ +
+ `; + } + return ` -
+
-
- -
+ ${removeTokenMarkup}
`; diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 1e52963b1dd..84602cf9207 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -8,7 +8,7 @@ import CreateLabelDropdown from './create_label'; (function() { this.LabelsSelect = (function() { - function LabelsSelect(els, options = {}) { + function LabelsSelect(els) { var _this, $els; _this = this; @@ -58,7 +58,6 @@ import CreateLabelDropdown from './create_label'; labelHTMLTemplate = _.template('<% _.each(labels, function(label){ %> issues?label_name[]=<%- encodeURIComponent(label.title) %>"> <%- label.title %> <% }); %>'); labelNoneHTMLTemplate = 'None'; } - const handleClick = options.handleClick; $sidebarLabelTooltip.tooltip(); @@ -317,9 +316,9 @@ import CreateLabelDropdown from './create_label'; }, multiSelect: $dropdown.hasClass('js-multiselect'), vue: $dropdown.hasClass('js-issue-board-sidebar'), - clicked: function(clickEvent) { - const { $el, e, isMarking } = clickEvent; - const label = clickEvent.selectedObj; + clicked: function(options) { + const { $el, e, isMarking } = options; + const label = options.selectedObj; var isIssueIndex, isMRIndex, page, boardsModel; var fadeOutLoader = () => { @@ -392,10 +391,6 @@ import CreateLabelDropdown from './create_label'; .then(fadeOutLoader) .catch(fadeOutLoader); } - else if (handleClick) { - e.preventDefault(); - handleClick(label); - } else { if ($dropdown.hasClass('js-multiselect')) { diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 74e5a4f1cea..e7d5325a509 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -5,7 +5,7 @@ import _ from 'underscore'; (function() { this.MilestoneSelect = (function() { - function MilestoneSelect(currentProject, els, options = {}) { + function MilestoneSelect(currentProject, els) { var _this, $els; if (currentProject != null) { _this = this; @@ -136,26 +136,19 @@ import _ from 'underscore'; }, opened: function(e) { const $el = $(e.currentTarget); - if ($dropdown.hasClass('js-issue-board-sidebar') || options.handleClick) { + if ($dropdown.hasClass('js-issue-board-sidebar')) { selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault; } $('a.is-active', $el).removeClass('is-active'); $(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active'); }, vue: $dropdown.hasClass('js-issue-board-sidebar'), - clicked: function(clickEvent) { - const { $el, e } = clickEvent; - let selected = clickEvent.selectedObj; + clicked: function(options) { + const { $el, e } = options; + let selected = options.selectedObj; var data, isIssueIndex, isMRIndex, isSelecting, page, boardsStore; if (!selected) return; - - if (options.handleClick) { - e.preventDefault(); - options.handleClick(selected); - return; - } - page = $('body').attr('data-page'); isIssueIndex = page === 'projects:issues:index'; isMRIndex = (page === page && page === 'projects:merge_requests:index'); diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index 759cc9925f4..a0883b32593 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -6,7 +6,7 @@ import _ from 'underscore'; // TODO: remove eventHub hack after code splitting refactor window.emitSidebarEvent = window.emitSidebarEvent || $.noop; -function UsersSelect(currentUser, els, options = {}) { +function UsersSelect(currentUser, els) { var $els; this.users = this.users.bind(this); this.user = this.user.bind(this); @@ -20,8 +20,6 @@ function UsersSelect(currentUser, els, options = {}) { } } - const { handleClick } = options; - $els = $(els); if (!els) { @@ -444,9 +442,6 @@ function UsersSelect(currentUser, els, options = {}) { } if ($el.closest('.add-issues-modal').length) { gl.issueBoards.ModalStore.store.filter[$dropdown.data('field-name')] = user.id; - } else if (handleClick) { - e.preventDefault(); - handleClick(user, isMarking); } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { return Issuable.filterResults($dropdown.closest('form')); } else if ($dropdown.hasClass('js-filter-submit')) { diff --git a/app/assets/javascripts/vue_shared/components/loading_icon.vue b/app/assets/javascripts/vue_shared/components/loading_icon.vue index 494fe4468d9..15581d5c2a0 100644 --- a/app/assets/javascripts/vue_shared/components/loading_icon.vue +++ b/app/assets/javascripts/vue_shared/components/loading_icon.vue @@ -18,12 +18,6 @@ required: false, default: false, }, - - class: { - type: String, - required: false, - default: '', - }, }, computed: { @@ -31,7 +25,7 @@ return this.inline ? 'span' : 'div'; }, cssClass() { - return `fa-${this.size}x ${this.class}`.trim(); + return `fa-${this.size}x`; }, }, }; diff --git a/app/assets/javascripts/vue_shared/components/popup_dialog.vue b/app/assets/javascripts/vue_shared/components/popup_dialog.vue index fc6421fecb9..9e8c10bdc1a 100644 --- a/app/assets/javascripts/vue_shared/components/popup_dialog.vue +++ b/app/assets/javascripts/vue_shared/components/popup_dialog.vue @@ -5,27 +5,17 @@ export default { props: { title: { type: String, - required: false, + required: true, }, text: { type: String, required: false, }, - hideFooter: { - type: Boolean, - required: false, - default: false, - }, kind: { type: String, required: false, default: 'primary', }, - modalDialogClass: { - type: String, - required: false, - default: '', - }, closeKind: { type: String, required: false, @@ -40,11 +30,6 @@ export default { type: String, required: true, }, - submitDisabled: { - type: Boolean, - required: false, - default: false, - }, }, computed: { @@ -72,57 +57,43 @@ export default { diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 1cfd7ef01a8..96f9dda26c4 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -4,9 +4,6 @@ .cred { color: $common-red; } .cgreen { color: $common-green; } .cdark { color: $common-gray-dark; } -.text-secondary { - color: $gl-text-color-secondary; -} /** COMMON CLASSES **/ .prepend-top-0 { margin-top: 0; } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 63697fd38a7..a9d804e735d 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -37,7 +37,6 @@ .dropdown-menu-nav { @include set-visible; display: block; - min-height: 40px; @media (max-width: $screen-xs-max) { width: 100%; diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index d218fb6d702..1cebd02df48 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -42,11 +42,3 @@ body.modal-open { width: 98%; } } - -.modal.popup-dialog { - display: block; -} - -.modal-body { - background-color: $modal-body-bg; -} diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss index a23131e0818..3ea77eb7a43 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss @@ -164,36 +164,3 @@ $pre-border-color: $border-color; $table-bg-accent: $gray-light; $zindex-popover: 900; - -//== Modals -// -//## - -//** Padding applied to the modal body -$modal-inner-padding: $gl-padding; - -//** Padding applied to the modal title -$modal-title-padding: $gl-padding; -//** Modal title line-height -// $modal-title-line-height: $line-height-base - -//** Background color of modal content area -$modal-content-bg: $gray-light; -$modal-body-bg: $white-light; -//** Modal content border color -// $modal-content-border-color: rgba(0,0,0,.2) -//** Modal content border color **for IE8** -// $modal-content-fallback-border-color: #999 - -//** Modal backdrop background color -// $modal-backdrop-bg: #000 -//** Modal backdrop opacity -// $modal-backdrop-opacity: .5 -//** Modal header border color -// $modal-header-border-color: #e5e5e5 -//** Modal footer border color -// $modal-footer-border-color: $modal-header-border-color - -// $modal-lg: 900px -// $modal-md: 600px -// $modal-sm: 300px diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index e8c7f8a8fc0..6a363b1710e 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -7,6 +7,19 @@ background: $black-transparent; } +.modal.popup-dialog { + display: block; + background-color: $black-transparent; + z-index: 2100; + + @media (min-width: $screen-md-min) { + .modal-dialog { + width: 600px; + margin: 30px auto; + } + } +} + .project-refs-form, .project-refs-target-form { display: inline-block; diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb index c4a621160af..7112c6ee470 100644 --- a/app/helpers/boards_helper.rb +++ b/app/helpers/boards_helper.rb @@ -20,6 +20,17 @@ module BoardsHelper project_issues_path(@project) end + def current_board_json + board = @board || @boards.first + + board.to_json( + only: [:id, :name, :milestone_id], + include: { + milestone: { only: [:title] } + } + ) + end + def board_base_url project_boards_path(@project) end -- cgit v1.2.1