Skip to content
Snippets Groups Projects
Commit 58d1ad56 authored by Lin Jen-Shin's avatar Lin Jen-Shin
Browse files

Merge remote-tracking branch 'upstream/master' into qa-clone-with-deploy-key

* upstream/master: (69 commits)
  Change issue show page to group MRs by projects and namespaces
  Merge branch 'master-i18n' into 'master'
  Update sidekiq_style_guide.md
  Document all_queues.yml in sidekiq_style_guide.md
  Fix artifact creation
  Fix Error 500s creating merge requests with external issue tracker
  Addressed mr observations
  Clean new Flash() and stop disabling no-new (eslint) when possible
  Disable query limiting warnings for now on GitLab.com
  Dry up spec
  Add changelog entry
  Schedule PopulateUntrackedUploads if needed
  Fix orphan temp table untracked_files_for_uploads
  Fix last batch size equals max batch size error
  Revert "Merge branch 'rd-40552-gitlab-should-check-if-keys-are-valid-before-saving' into 'master'"
  Fix warning messages for promoting labels and milestones
  Fixed missing js selector for the realtime pipelines commit comp
  Reuse getter Add loading button for better UX
  Honour workhorse provided file name
  Fix a transient failure in db/post_migrate/20170717111152_cleanup_move_system_upload_folder_symlink.rb where symlink already exists
  ...
parents ab4f8032 bf5e617a
No related branches found
No related tags found
No related merge requests found
Showing
with 235 additions and 121 deletions
Loading
@@ -2,6 +2,7 @@
Loading
@@ -2,6 +2,7 @@
   
import Vue from 'vue'; import Vue from 'vue';
import Flash from '../../flash'; import Flash from '../../flash';
import { __ } from '../../locale';
import Sidebar from '../../right_sidebar'; import Sidebar from '../../right_sidebar';
import eventHub from '../../sidebar/event_hub'; import eventHub from '../../sidebar/event_hub';
import assigneeTitle from '../../sidebar/components/assignees/assignee_title'; import assigneeTitle from '../../sidebar/components/assignees/assignee_title';
Loading
@@ -95,7 +96,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({
Loading
@@ -95,7 +96,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({
}) })
.catch(() => { .catch(() => {
this.loadingAssignees = false; this.loadingAssignees = false;
return new Flash('An error occurred while saving assignees'); Flash(__('An error occurred while saving assignees'));
}); });
}, },
}, },
Loading
Loading
/* eslint-disable no-new */
import Vue from 'vue'; import Vue from 'vue';
import Flash from '../../../flash'; import Flash from '../../../flash';
import { __ } from '../../../locale';
import './lists_dropdown'; import './lists_dropdown';
import { pluralize } from '../../../lib/utils/text_utility'; import { pluralize } from '../../../lib/utils/text_utility';
   
Loading
@@ -36,7 +35,7 @@ gl.issueBoards.ModalFooter = Vue.extend({
Loading
@@ -36,7 +35,7 @@ gl.issueBoards.ModalFooter = Vue.extend({
gl.boardService.bulkUpdate(issueIds, { gl.boardService.bulkUpdate(issueIds, {
add_label_ids: [list.label.id], add_label_ids: [list.label.id],
}).catch(() => { }).catch(() => {
new Flash('Failed to update issues, please try again.', 'alert'); Flash(__('Failed to update issues, please try again.'));
   
selectedIssues.forEach((issue) => { selectedIssues.forEach((issue) => {
list.removeIssue(issue); list.removeIssue(issue);
Loading
Loading
/* eslint-disable no-new */
import Vue from 'vue'; import Vue from 'vue';
import Flash from '../../../flash'; import Flash from '../../../flash';
import { __ } from '../../../locale';
   
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
   
Loading
@@ -45,7 +44,7 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
Loading
@@ -45,7 +44,7 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
}, },
}; };
Vue.http.patch(this.updateUrl, data).catch(() => { Vue.http.patch(this.updateUrl, data).catch(() => {
new Flash('Failed to remove issue from board, please try again.', 'alert'); Flash(__('Failed to remove issue from board, please try again.'));
   
lists.forEach((list) => { lists.forEach((list) => {
list.addIssue(issue); list.addIssue(issue);
Loading
Loading
Loading
@@ -39,7 +39,7 @@ export default class VariableList {
Loading
@@ -39,7 +39,7 @@ export default class VariableList {
}, },
protected: { protected: {
selector: '.js-ci-variable-input-protected', selector: '.js-ci-variable-input-protected',
default: 'true', default: 'false',
}, },
environment_scope: { environment_scope: {
// We can't use a `.js-` class here because // We can't use a `.js-` class here because
Loading
Loading
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, max-len */
import 'vendor/jquery.waitforimages';
   
// Width where images must fits in, for 2-up this gets divided by 2 // Width where images must fits in, for 2-up this gets divided by 2
const availWidth = 900; const availWidth = 900;
Loading
Loading
Loading
@@ -6,5 +6,5 @@ import 'vendor/jquery.endless-scroll';
Loading
@@ -6,5 +6,5 @@ import 'vendor/jquery.endless-scroll';
import 'vendor/jquery.caret'; import 'vendor/jquery.caret';
import 'vendor/jquery.atwho'; import 'vendor/jquery.atwho';
import 'vendor/jquery.scrollTo'; import 'vendor/jquery.scrollTo';
import 'vendor/jquery.waitforimages'; import 'jquery.waitforimages';
import 'select2/select2'; import 'select2/select2';
Loading
@@ -180,6 +180,7 @@ export default class CreateMergeRequestDropdown {
Loading
@@ -180,6 +180,7 @@ export default class CreateMergeRequestDropdown {
valueAttribute: 'data-text', valueAttribute: 'data-text',
}, },
], ],
hideOnClick: false,
}; };
} }
   
Loading
Loading
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
import { getLocationHash } from './lib/utils/url_utility'; import { getLocationHash } from './lib/utils/url_utility';
import FilesCommentButton from './files_comment_button'; import FilesCommentButton from './files_comment_button';
import SingleFileDiff from './single_file_diff'; import SingleFileDiff from './single_file_diff';
Loading
@@ -69,7 +72,9 @@ export default class Diff {
Loading
@@ -69,7 +72,9 @@ export default class Diff {
const view = file.data('view'); const view = file.data('view');
   
const params = { since, to, bottom, offset, unfold, view }; const params = { since, to, bottom, offset, unfold, view };
$.get(link, params, response => $target.parent().replaceWith(response)); axios.get(link, { params })
.then(({ data }) => $target.parent().replaceWith(data))
.catch(() => flash(__('An error occurred while loading diff')));
} }
   
openAnchoredDiff(cb) { openAnchoredDiff(cb) {
Loading
Loading
Loading
@@ -3,7 +3,6 @@ const DATA_DROPDOWN = 'data-dropdown';
Loading
@@ -3,7 +3,6 @@ const DATA_DROPDOWN = 'data-dropdown';
const SELECTED_CLASS = 'droplab-item-selected'; const SELECTED_CLASS = 'droplab-item-selected';
const ACTIVE_CLASS = 'droplab-item-active'; const ACTIVE_CLASS = 'droplab-item-active';
const IGNORE_CLASS = 'droplab-item-ignore'; const IGNORE_CLASS = 'droplab-item-ignore';
const IGNORE_HIDING_CLASS = 'droplab-item-ignore-hiding';
// Matches `{{anything}}` and `{{ everything }}`. // Matches `{{anything}}` and `{{ everything }}`.
const TEMPLATE_REGEX = /\{\{(.+?)\}\}/g; const TEMPLATE_REGEX = /\{\{(.+?)\}\}/g;
   
Loading
@@ -14,5 +13,4 @@ export {
Loading
@@ -14,5 +13,4 @@ export {
ACTIVE_CLASS, ACTIVE_CLASS,
TEMPLATE_REGEX, TEMPLATE_REGEX,
IGNORE_CLASS, IGNORE_CLASS,
IGNORE_HIDING_CLASS,
}; };
import utils from './utils'; import utils from './utils';
import { SELECTED_CLASS, IGNORE_CLASS, IGNORE_HIDING_CLASS } from './constants'; import { SELECTED_CLASS, IGNORE_CLASS } from './constants';
   
class DropDown { class DropDown {
constructor(list, config = {}) { constructor(list, config = { }) {
this.currentIndex = 0; this.currentIndex = 0;
this.hidden = true; this.hidden = true;
this.list = typeof list === 'string' ? document.querySelector(list) : list; this.list = typeof list === 'string' ? document.querySelector(list) : list;
this.items = []; this.items = [];
this.eventWrapper = {}; this.eventWrapper = {};
this.hideOnClick = config.hideOnClick !== false;
   
if (config.addActiveClassToDropdownButton) { if (config.addActiveClassToDropdownButton) {
this.dropdownToggle = this.list.parentNode.querySelector('.js-dropdown-toggle'); this.dropdownToggle = this.list.parentNode.querySelector('.js-dropdown-toggle');
Loading
@@ -37,15 +38,17 @@ class DropDown {
Loading
@@ -37,15 +38,17 @@ class DropDown {
   
clickEvent(e) { clickEvent(e) {
if (e.target.tagName === 'UL') return; if (e.target.tagName === 'UL') return;
if (e.target.classList.contains(IGNORE_CLASS)) return; if (e.target.closest(`.${IGNORE_CLASS}`)) return;
   
const selected = utils.closest(e.target, 'LI'); const selected = e.target.closest('li');
if (!selected) return; if (!selected) return;
   
this.addSelectedClass(selected); this.addSelectedClass(selected);
   
e.preventDefault(); e.preventDefault();
if (!e.target.classList.contains(IGNORE_HIDING_CLASS)) this.hide(); if (this.hideOnClick) {
this.hide();
}
   
const listEvent = new CustomEvent('click.dl', { const listEvent = new CustomEvent('click.dl', {
detail: { detail: {
Loading
Loading
<script> <script>
import Timeago from 'timeago.js'; import Timeago from 'timeago.js';
import _ from 'underscore'; import _ from 'underscore';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import tooltip from '~/vue_shared/directives/tooltip';
import { humanize } from '../../lib/utils/text_utility'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import { humanize } from '~/lib/utils/text_utility';
import ActionsComponent from './environment_actions.vue'; import ActionsComponent from './environment_actions.vue';
import ExternalUrlComponent from './environment_external_url.vue'; import ExternalUrlComponent from './environment_external_url.vue';
import StopComponent from './environment_stop.vue'; import StopComponent from './environment_stop.vue';
Loading
@@ -21,14 +22,18 @@
Loading
@@ -21,14 +22,18 @@
   
export default { export default {
components: { components: {
userAvatarLink, UserAvatarLink,
'commit-component': CommitComponent, CommitComponent,
'actions-component': ActionsComponent, ActionsComponent,
'external-url-component': ExternalUrlComponent, ExternalUrlComponent,
'stop-component': StopComponent, StopComponent,
'rollback-component': RollbackComponent, RollbackComponent,
'terminal-button-component': TerminalButtonComponent, TerminalButtonComponent,
'monitoring-button-component': MonitoringButtonComponent, MonitoringButtonComponent,
},
directives: {
tooltip,
}, },
   
props: { props: {
Loading
@@ -443,7 +448,11 @@
Loading
@@ -443,7 +448,11 @@
v-if="!model.isFolder" v-if="!model.isFolder"
class="environment-name flex-truncate-parent table-mobile-content" class="environment-name flex-truncate-parent table-mobile-content"
:href="environmentPath"> :href="environmentPath">
<span class="flex-truncate-child">{{ model.name }}</span> <span
class="flex-truncate-child"
v-tooltip
:title="model.name"
>{{ model.name }}</span>
</a> </a>
<span <span
v-else v-else
Loading
Loading
import { parseQueryStringIntoObject } from '~/lib/utils/common_utils';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
export default class GpgBadges { export default class GpgBadges {
static fetch() { static fetch() {
const badges = $('.js-loading-gpg-badge'); const badges = $('.js-loading-gpg-badge');
Loading
@@ -5,13 +10,13 @@ export default class GpgBadges {
Loading
@@ -5,13 +10,13 @@ export default class GpgBadges {
   
badges.html('<i class="fa fa-spinner fa-spin"></i>'); badges.html('<i class="fa fa-spinner fa-spin"></i>');
   
$.get({ const params = parseQueryStringIntoObject(form.serialize());
url: form.data('signatures-path'), return axios.get(form.data('signatures-path'), { params })
data: form.serialize(), .then(({ data }) => {
}).done((response) => { data.signatures.forEach((signature) => {
response.signatures.forEach((signature) => {
badges.filter(`[data-commit-sha="${signature.commit_sha}"]`).replaceWith(signature.html); badges.filter(`[data-commit-sha="${signature.commit_sha}"]`).replaceWith(signature.html);
}); });
}); })
.catch(() => flash(__('An error occurred while loading commits')));
} }
} }
import axios from './lib/utils/axios_utils';
import Api from './api'; import Api from './api';
import { normalizeCRLFHeaders } from './lib/utils/common_utils'; import { normalizeHeaders } from './lib/utils/common_utils';
   
export default function groupsSelect() { export default function groupsSelect() {
// Needs to be accessible in rspec // Needs to be accessible in rspec
Loading
@@ -17,24 +18,23 @@ export default function groupsSelect() {
Loading
@@ -17,24 +18,23 @@ export default function groupsSelect() {
dataType: 'json', dataType: 'json',
quietMillis: 250, quietMillis: 250,
transport(params) { transport(params) {
return $.ajax(params) axios[params.type.toLowerCase()](params.url, {
.then((data, status, xhr) => { params: params.data,
const results = data || []; })
.then((res) => {
const headers = normalizeCRLFHeaders(xhr.getAllResponseHeaders()); const results = res.data || [];
const headers = normalizeHeaders(res.headers);
const currentPage = parseInt(headers['X-PAGE'], 10) || 0; const currentPage = parseInt(headers['X-PAGE'], 10) || 0;
const totalPages = parseInt(headers['X-TOTAL-PAGES'], 10) || 0; const totalPages = parseInt(headers['X-TOTAL-PAGES'], 10) || 0;
const more = currentPage < totalPages; const more = currentPage < totalPages;
   
return { params.success({
results, results,
pagination: { pagination: {
more, more,
}, },
}; });
}) }).catch(params.error);
.then(params.success)
.fail(params.error);
}, },
data(search, page) { data(search, page) {
return { return {
Loading
Loading
import { __ } from './locale';
import axios from './lib/utils/axios_utils';
import flash from './flash';
class ImporterStatus { class ImporterStatus {
constructor(jobsUrl, importUrl) { constructor(jobsUrl, importUrl) {
this.jobsUrl = jobsUrl; this.jobsUrl = jobsUrl;
Loading
@@ -9,29 +13,7 @@ class ImporterStatus {
Loading
@@ -9,29 +13,7 @@ class ImporterStatus {
initStatusPage() { initStatusPage() {
$('.js-add-to-import') $('.js-add-to-import')
.off('click') .off('click')
.on('click', (event) => { .on('click', this.addToImport.bind(this));
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');
return $.post(this.importUrl, {
repo_id: id,
target_namespace: targetNamespace,
new_name: newName,
}, {
dataType: 'script',
});
});
   
$('.js-import-all') $('.js-import-all')
.off('click') .off('click')
Loading
@@ -44,6 +26,39 @@ class ImporterStatus {
Loading
@@ -44,6 +26,39 @@ class ImporterStatus {
}); });
} }
   
addToImport(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');
return axios.post(this.importUrl, {
repo_id: id,
target_namespace: targetNamespace,
new_name: newName,
})
.then(({ data }) => {
const job = $(`tr#repo_${id}`);
job.attr('id', `project_${data.id}`);
job.find('.import-target').html(`<a href="${data.full_path}">${data.full_path}</a>`);
$('table.import-jobs tbody').prepend(job);
job.addClass('active');
job.find('.import-actions').html('<i class="fa fa-spinner fa-spin" aria-label="importing"></i> started');
})
.catch(() => flash(__('An error occurred while importing project')));
}
setAutoUpdate() { setAutoUpdate() {
return setInterval(() => $.get(this.jobsUrl, data => $.each(data, (i, job) => { return setInterval(() => $.get(this.jobsUrl, data => $.each(data, (i, job) => {
const jobItem = $(`#project_${job.id}`); const jobItem = $(`#project_${job.id}`);
Loading
@@ -71,7 +86,7 @@ class ImporterStatus {
Loading
@@ -71,7 +86,7 @@ class ImporterStatus {
} }
   
// eslint-disable-next-line consistent-return // eslint-disable-next-line consistent-return
export default function initImporterStatus() { function initImporterStatus() {
const importerStatus = document.querySelector('.js-importer-status'); const importerStatus = document.querySelector('.js-importer-status');
   
if (importerStatus) { if (importerStatus) {
Loading
@@ -79,3 +94,8 @@ export default function initImporterStatus() {
Loading
@@ -79,3 +94,8 @@ export default function initImporterStatus() {
return new ImporterStatus(data.jobsImportPath, data.importPath); return new ImporterStatus(data.jobsImportPath, data.importPath);
} }
} }
export {
initImporterStatus as default,
ImporterStatus,
};
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */
import 'vendor/jquery.waitforimages';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import { addDelimiter } from './lib/utils/text_utility'; import { addDelimiter } from './lib/utils/text_utility';
import flash from './flash'; import flash from './flash';
Loading
@@ -25,6 +24,51 @@ export default class Issue {
Loading
@@ -25,6 +24,51 @@ export default class Issue {
if (Issue.createMrDropdownWrap) { if (Issue.createMrDropdownWrap) {
this.createMergeRequestDropdown = new CreateMergeRequestDropdown(Issue.createMrDropdownWrap); this.createMergeRequestDropdown = new CreateMergeRequestDropdown(Issue.createMrDropdownWrap);
} }
// Listen to state changes in the Vue app
document.addEventListener('issuable_vue_app:change', (event) => {
this.updateTopState(event.detail.isClosed, event.detail.data);
});
}
/**
* This method updates the top area of the issue.
*
* Once the issue state changes, either through a click on the top area (jquery)
* or a click on the bottom area (Vue) we need to update the top area.
*
* @param {Boolean} isClosed
* @param {Array} data
* @param {String} issueFailMessage
*/
updateTopState(isClosed, data, issueFailMessage = 'Unable to update this issue at this time.') {
if ('id' in data) {
const isClosedBadge = $('div.status-box-issue-closed');
const isOpenBadge = $('div.status-box-open');
const projectIssuesCounter = $('.issue_counter');
isClosedBadge.toggleClass('hidden', !isClosed);
isOpenBadge.toggleClass('hidden', isClosed);
$(document).trigger('issuable:change', isClosed);
this.toggleCloseReopenButton(isClosed);
let numProjectIssues = Number(projectIssuesCounter.first().text().trim().replace(/[^\d]/, ''));
numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1;
projectIssuesCounter.text(addDelimiter(numProjectIssues));
if (this.createMergeRequestDropdown) {
if (isClosed) {
this.createMergeRequestDropdown.unavailable();
this.createMergeRequestDropdown.disable();
} else {
// We should check in case a branch was created in another tab
this.createMergeRequestDropdown.checkAbilityToCreateBranch();
}
}
} else {
flash(issueFailMessage);
}
} }
   
initIssueBtnEventListeners() { initIssueBtnEventListeners() {
Loading
@@ -45,34 +89,8 @@ export default class Issue {
Loading
@@ -45,34 +89,8 @@ export default class Issue {
url = $button.attr('href'); url = $button.attr('href');
return axios.put(url) return axios.put(url)
.then(({ data }) => { .then(({ data }) => {
const isClosedBadge = $('div.status-box-issue-closed'); const isClosed = $button.hasClass('btn-close');
const isOpenBadge = $('div.status-box-open'); this.updateTopState(isClosed, data);
const projectIssuesCounter = $('.issue_counter');
if ('id' in data) {
const isClosed = $button.hasClass('btn-close');
isClosedBadge.toggleClass('hidden', !isClosed);
isOpenBadge.toggleClass('hidden', isClosed);
$(document).trigger('issuable:change', isClosed);
this.toggleCloseReopenButton(isClosed);
let numProjectIssues = Number(projectIssuesCounter.first().text().trim().replace(/[^\d]/, ''));
numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1;
projectIssuesCounter.text(addDelimiter(numProjectIssues));
if (this.createMergeRequestDropdown) {
if (isClosed) {
this.createMergeRequestDropdown.unavailable();
this.createMergeRequestDropdown.disable();
} else {
// We should check in case a branch was created in another tab
this.createMergeRequestDropdown.checkAbilityToCreateBranch();
}
}
} else {
flash(issueFailMessage);
}
}) })
.catch(() => flash(issueFailMessage)) .catch(() => flash(issueFailMessage))
.then(() => { .then(() => {
Loading
Loading
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, max-len, prefer-arrow-callback */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, max-len, prefer-arrow-callback */
import 'vendor/jquery.waitforimages';
import { __ } from '~/locale'; import { __ } from '~/locale';
import TaskList from './task_list'; import TaskList from './task_list';
import MergeRequestTabs from './merge_request_tabs'; import MergeRequestTabs from './merge_request_tabs';
Loading
Loading
Loading
@@ -2,16 +2,18 @@
Loading
@@ -2,16 +2,18 @@
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import _ from 'underscore'; import _ from 'underscore';
import Autosize from 'autosize'; import Autosize from 'autosize';
import { __ } from '~/locale';
import Flash from '../../flash'; import Flash from '../../flash';
import Autosave from '../../autosave'; import Autosave from '../../autosave';
import TaskList from '../../task_list'; import TaskList from '../../task_list';
import * as constants from '../constants'; import * as constants from '../constants';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue'; import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue';
import discussionLockedWidget from './discussion_locked_widget.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue'; import markdownField from '../../vue_shared/components/markdown/field.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import loadingButton from '../../vue_shared/components/loading_button.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue';
import discussionLockedWidget from './discussion_locked_widget.vue';
import issuableStateMixin from '../mixins/issuable_state'; import issuableStateMixin from '../mixins/issuable_state';
   
export default { export default {
Loading
@@ -22,6 +24,7 @@
Loading
@@ -22,6 +24,7 @@
discussionLockedWidget, discussionLockedWidget,
markdownField, markdownField,
userAvatarLink, userAvatarLink,
loadingButton,
}, },
mixins: [ mixins: [
issuableStateMixin, issuableStateMixin,
Loading
@@ -30,9 +33,6 @@
Loading
@@ -30,9 +33,6 @@
return { return {
note: '', note: '',
noteType: constants.COMMENT, noteType: constants.COMMENT,
// Can't use mapGetters,
// this needs to be in the data object because it belongs to the state
issueState: this.$store.getters.getNoteableData.state,
isSubmitting: false, isSubmitting: false,
isSubmitButtonDisabled: true, isSubmitButtonDisabled: true,
}; };
Loading
@@ -43,6 +43,7 @@
Loading
@@ -43,6 +43,7 @@
'getUserData', 'getUserData',
'getNoteableData', 'getNoteableData',
'getNotesData', 'getNotesData',
'issueState',
]), ]),
isLoggedIn() { isLoggedIn() {
return this.getUserData.id; return this.getUserData.id;
Loading
@@ -105,7 +106,7 @@
Loading
@@ -105,7 +106,7 @@
mounted() { mounted() {
// jQuery is needed here because it is a custom event being dispatched with jQuery. // jQuery is needed here because it is a custom event being dispatched with jQuery.
$(document).on('issuable:change', (e, isClosed) => { $(document).on('issuable:change', (e, isClosed) => {
this.issueState = isClosed ? constants.CLOSED : constants.REOPENED; this.toggleIssueLocalState(isClosed ? constants.CLOSED : constants.REOPENED);
}); });
   
this.initAutoSave(); this.initAutoSave();
Loading
@@ -117,6 +118,9 @@
Loading
@@ -117,6 +118,9 @@
'stopPolling', 'stopPolling',
'restartPolling', 'restartPolling',
'removePlaceholderNotes', 'removePlaceholderNotes',
'closeIssue',
'reopenIssue',
'toggleIssueLocalState',
]), ]),
setIsSubmitButtonDisabled(note, isSubmitting) { setIsSubmitButtonDisabled(note, isSubmitting) {
if (!_.isEmpty(note) && !isSubmitting) { if (!_.isEmpty(note) && !isSubmitting) {
Loading
@@ -126,6 +130,8 @@
Loading
@@ -126,6 +130,8 @@
} }
}, },
handleSave(withIssueAction) { handleSave(withIssueAction) {
this.isSubmitting = true;
if (this.note.length) { if (this.note.length) {
const noteData = { const noteData = {
endpoint: this.endpoint, endpoint: this.endpoint,
Loading
@@ -142,7 +148,6 @@
Loading
@@ -142,7 +148,6 @@
if (this.noteType === constants.DISCUSSION) { if (this.noteType === constants.DISCUSSION) {
noteData.data.note.type = constants.DISCUSSION_NOTE; noteData.data.note.type = constants.DISCUSSION_NOTE;
} }
this.isSubmitting = true;
this.note = ''; // Empty textarea while being requested. Repopulate in catch this.note = ''; // Empty textarea while being requested. Repopulate in catch
this.resizeTextarea(); this.resizeTextarea();
this.stopPolling(); this.stopPolling();
Loading
@@ -184,13 +189,25 @@ Please check your network connection and try again.`;
Loading
@@ -184,13 +189,25 @@ Please check your network connection and try again.`;
this.toggleIssueState(); this.toggleIssueState();
} }
}, },
enableButton() {
this.isSubmitting = false;
},
toggleIssueState() { toggleIssueState() {
this.issueState = this.isIssueOpen ? constants.CLOSED : constants.REOPENED; if (this.isIssueOpen) {
this.closeIssue()
// This is out of scope for the Notes Vue component. .then(() => this.enableButton())
// It was the shortest path to update the issue state and relevant places. .catch(() => {
const btnClass = this.isIssueOpen ? 'btn-reopen' : 'btn-close'; this.enableButton();
$(`.js-btn-issue-action.${btnClass}:visible`).trigger('click'); Flash(__('Something went wrong while closing the issue. Please try again later'));
});
} else {
this.reopenIssue()
.then(() => this.enableButton())
.catch(() => {
this.enableButton();
Flash(__('Something went wrong while reopening the issue. Please try again later'));
});
}
}, },
discard(shouldClear = true) { discard(shouldClear = true) {
// `blur` is needed to clear slash commands autocomplete cache if event fired. // `blur` is needed to clear slash commands autocomplete cache if event fired.
Loading
@@ -367,15 +384,19 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
Loading
@@ -367,15 +384,19 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
</li> </li>
</ul> </ul>
</div> </div>
<button
type="button" <loading-button
@click="handleSave(true)"
v-if="canUpdateIssue" v-if="canUpdateIssue"
:class="actionButtonClassNames" :loading="isSubmitting"
@click="handleSave(true)"
:container-class="[
actionButtonClassNames,
'btn btn-comment btn-comment-and-close js-action-button'
]"
:disabled="isSubmitting" :disabled="isSubmitting"
class="btn btn-comment btn-comment-and-close js-action-button"> :label="issueActionButtonTitle"
{{ issueActionButtonTitle }} />
</button>
<button <button
type="button" type="button"
v-if="note.length" v-if="note.length"
Loading
Loading
Loading
@@ -28,6 +28,8 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
Loading
@@ -28,6 +28,8 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
notesPath: notesDataset.notesPath, notesPath: notesDataset.notesPath,
markdownDocsPath: notesDataset.markdownDocsPath, markdownDocsPath: notesDataset.markdownDocsPath,
quickActionsDocsPath: notesDataset.quickActionsDocsPath, quickActionsDocsPath: notesDataset.quickActionsDocsPath,
closeIssuePath: notesDataset.closeIssuePath,
reopenIssuePath: notesDataset.reopenIssuePath,
}, },
}; };
}, },
Loading
Loading
Loading
@@ -32,4 +32,7 @@ export default {
Loading
@@ -32,4 +32,7 @@ export default {
toggleAward(endpoint, data) { toggleAward(endpoint, data) {
return Vue.http.post(endpoint, data, { emulateJSON: true }); return Vue.http.post(endpoint, data, { emulateJSON: true });
}, },
toggleIssueState(endpoint, data) {
return Vue.http.put(endpoint, data);
},
}; };
Loading
@@ -61,6 +61,39 @@ export const createNewNote = ({ commit }, { endpoint, data }) => service
Loading
@@ -61,6 +61,39 @@ export const createNewNote = ({ commit }, { endpoint, data }) => service
export const removePlaceholderNotes = ({ commit }) => export const removePlaceholderNotes = ({ commit }) =>
commit(types.REMOVE_PLACEHOLDER_NOTES); commit(types.REMOVE_PLACEHOLDER_NOTES);
   
export const closeIssue = ({ commit, dispatch, state }) => service
.toggleIssueState(state.notesData.closeIssuePath)
.then(res => res.json())
.then((data) => {
commit(types.CLOSE_ISSUE);
dispatch('emitStateChangedEvent', data);
});
export const reopenIssue = ({ commit, dispatch, state }) => service
.toggleIssueState(state.notesData.reopenIssuePath)
.then(res => res.json())
.then((data) => {
commit(types.REOPEN_ISSUE);
dispatch('emitStateChangedEvent', data);
});
export const emitStateChangedEvent = ({ commit, getters }, data) => {
const event = new CustomEvent('issuable_vue_app:change', { detail: {
data,
isClosed: getters.issueState === constants.CLOSED,
} });
document.dispatchEvent(event);
};
export const toggleIssueLocalState = ({ commit }, newState) => {
if (newState === constants.CLOSED) {
commit(types.CLOSE_ISSUE);
} else if (newState === constants.REOPENED) {
commit(types.REOPEN_ISSUE);
}
};
export const saveNote = ({ commit, dispatch }, noteData) => { export const saveNote = ({ commit, dispatch }, noteData) => {
const { note } = noteData.data.note; const { note } = noteData.data.note;
let placeholderText = note; let placeholderText = note;
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment