Commit f4023a69 authored by Jan Provaznik's avatar Jan Provaznik

Merge remote-tracking branch 'origin/master' into dev-master

parents 79911cb4 a0ca86e5
Dangerfile gitlab-language=ruby
db/schema.rb merge=merge_db_schema
......@@ -694,7 +694,10 @@ gitlab:setup-mysql:
# Frontend-related jobs
gitlab:assets:compile:
<<: *dedicated-no-docs-and-no-qa-pull-cache-job
image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-git-2.18-chrome-69.0-node-8.x-yarn-1.2-graphicsmagick-1.3.29-docker-18.06.1
dependencies: []
services:
- docker:stable-dind
variables:
NODE_ENV: "production"
RAILS_ENV: "production"
......@@ -703,18 +706,23 @@ gitlab:assets:compile:
WEBPACK_REPORT: "true"
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
DOCKER_DRIVER: overlay2
DOCKER_HOST: tcp://docker:2375
script:
- date
- yarn install --frozen-lockfile --production --cache-folder .yarn-cache
- date
- free -m
- bundle exec rake gitlab:assets:compile
- scripts/build_assets_image
artifacts:
name: webpack-report
expire_in: 31d
paths:
- webpack-report/
- public/assets/
tags:
- docker
karma:
<<: *dedicated-no-docs-pull-cache-job
......
# Simple container to store assets for later use
FROM scratch
ADD public/assets /assets/
CMD /bin/true
......@@ -13,7 +13,7 @@ export default () => {
if (editBlobForm.length) {
const urlRoot = editBlobForm.data('relativeUrlRoot');
const assetsPath = editBlobForm.data('assetsPrefix');
const filePath = editBlobForm.data('blobFilename')
const filePath = editBlobForm.data('blobFilename');
const currentAction = $('.js-file-title').data('currentAction');
const projectId = editBlobForm.data('project-id');
......
......@@ -42,7 +42,7 @@ export default Vue.extend({
required: true,
},
},
data () {
data() {
return {
detailIssue: boardsStore.detail,
filter: boardsStore.filter,
......@@ -55,27 +55,26 @@ export default Vue.extend({
},
isNewIssueShown() {
return this.list.type === 'backlog' || (!this.disabled && this.list.type !== 'closed');
}
},
},
watch: {
filter: {
handler() {
this.list.page = 1;
this.list.getIssues(true)
.catch(() => {
this.list.getIssues(true).catch(() => {
// TODO: handle request error
});
},
deep: true,
}
},
mounted () {
},
mounted() {
this.sortableOptions = getBoardSortableDefaultOptions({
disabled: this.disabled,
group: 'boards',
draggable: '.is-draggable',
handle: '.js-board-handle',
onEnd: (e) => {
onEnd: e => {
sortableEnd();
if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
......@@ -86,14 +85,15 @@ export default Vue.extend({
boardsStore.moveList(list, order);
});
}
}
},
});
this.sortable = Sortable.create(this.$el.parentNode, this.sortableOptions);
},
created() {
if (this.list.isExpandable && AccessorUtilities.isLocalStorageAccessSafe()) {
const isCollapsed = localStorage.getItem(`boards.${this.boardId}.${this.list.type}.expanded`) === 'false';
const isCollapsed =
localStorage.getItem(`boards.${this.boardId}.${this.list.type}.expanded`) === 'false';
this.list.isExpanded = !isCollapsed;
}
......@@ -107,7 +107,10 @@ export default Vue.extend({
this.list.isExpanded = !this.list.isExpanded;
if (AccessorUtilities.isLocalStorageAccessSafe()) {
localStorage.setItem(`boards.${this.boardId}.${this.list.type}.expanded`, this.list.isExpanded);
localStorage.setItem(
`boards.${this.boardId}.${this.list.type}.expanded`,
this.list.isExpanded,
);
}
}
},
......
......@@ -32,16 +32,16 @@ export default {
boardsStore.state.lists = _.sortBy(boardsStore.state.lists, 'position');
// Save the labels
gl.boardService.generateDefaultLists()
gl.boardService
.generateDefaultLists()
.then(res => res.data)
.then((data) => {
data.forEach((listObj) => {
.then(data => {
data.forEach(listObj => {
const list = boardsStore.findList('title', listObj.title);
list.id = listObj.id;
list.label.id = listObj.label.id;
list.getIssues()
.catch(() => {
list.getIssues().catch(() => {
// TODO: handle request error
});
});
......@@ -57,7 +57,6 @@ export default {
clearBlankState: boardsStore.removeBlankState.bind(boardsStore),
},
};
</script>
<template>
......
<script>
/* eslint-disable vue/require-default-prop */
import IssueCardInner from './issue_card_inner.vue';
import eventHub from '../eventhub';
import boardsStore from '../stores/boards_store';
/* eslint-disable vue/require-default-prop */
import IssueCardInner from './issue_card_inner.vue';
import eventHub from '../eventhub';
import boardsStore from '../stores/boards_store';
export default {
export default {
name: 'BoardsIssueCard',
components: {
IssueCardInner,
......@@ -71,7 +71,7 @@
}
},
},
};
};
</script>
<template>
......
......@@ -62,7 +62,8 @@ export default {
eventHub.$emit(`scroll-board-list-${this.list.id}`);
this.cancel();
return this.list.newIssue(issue)
return this.list
.newIssue(issue)
.then(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable();
......
......@@ -38,7 +38,7 @@ export default Vue.extend({
};
},
computed: {
showSidebar () {
showSidebar() {
return Object.keys(this.issue).length;
},
milestoneTitle() {
......@@ -51,18 +51,20 @@ export default Vue.extend({
return this.issue.labels && this.issue.labels.length;
},
labelDropdownTitle() {
return this.hasLabels ? sprintf(__('%{firstLabel} +%{labelCount} more'), {
return this.hasLabels
? sprintf(__('%{firstLabel} +%{labelCount} more'), {
firstLabel: this.issue.labels[0].title,
labelCount: this.issue.labels.length - 1
}) : __('Label');
labelCount: this.issue.labels.length - 1,
})
: __('Label');
},
selectedLabels() {
return this.hasLabels ? this.issue.labels.map(l => l.title).join(',') : '';
}
},
},
watch: {
detail: {
handler () {
handler() {
if (this.issue.id !== this.detail.issue.id) {
$('.block.assignee')
.find('input:not(.js-vue)[name="issue[assignee_ids][]"]')
......@@ -71,17 +73,19 @@ export default Vue.extend({
});
$('.js-issue-board-sidebar', this.$el).each((i, el) => {
$(el).data('glDropdown').clearMenu();
$(el)
.data('glDropdown')
.clearMenu();
});
}
this.issue = this.detail.issue;
this.list = this.detail.list;
},
deep: true
deep: true,
},
},
created () {
created() {
// Get events from glDropdown
eventHub.$on('sidebar.removeAssignee', this.removeAssignee);
eventHub.$on('sidebar.addAssignee', this.addAssignee);
......@@ -94,7 +98,7 @@ export default Vue.extend({
eventHub.$off('sidebar.removeAllAssignees', this.removeAllAssignees);
eventHub.$off('sidebar.saveAssignees', this.saveAssignees);
},
mounted () {
mounted() {
new IssuableContext(this.currentUser);
new MilestoneSelect();
new DueDateSelectors();
......@@ -102,29 +106,30 @@ export default Vue.extend({
new Sidebar();
},
methods: {
closeSidebar () {
closeSidebar() {
this.detail.issue = {};
},
assignSelf () {
assignSelf() {
// Notify gl dropdown that we are now assigning to current user
this.$refs.assigneeBlock.dispatchEvent(new Event('assignYourself'));
this.addAssignee(this.currentUser);
this.saveAssignees();
},
removeAssignee (a) {
removeAssignee(a) {
boardsStore.detail.issue.removeAssignee(a);
},
addAssignee (a) {
addAssignee(a) {
boardsStore.detail.issue.addAssignee(a);
},
removeAllAssignees () {
removeAllAssignees() {
boardsStore.detail.issue.removeAllAssignees();
},
saveAssignees () {
saveAssignees() {
this.loadingAssignees = true;
boardsStore.detail.issue.update()
boardsStore.detail.issue
.update()
.then(() => {
this.loadingAssignees = false;
})
......
<script>
import $ from 'jquery';
import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import eventHub from '../eventhub';
import tooltip from '../../vue_shared/directives/tooltip';
import boardsStore from '../stores/boards_store';
import $ from 'jquery';
import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import eventHub from '../eventhub';
import tooltip from '../../vue_shared/directives/tooltip';
import boardsStore from '../stores/boards_store';
export default {
export default {
components: {
UserAvatarLink,
Icon,
......@@ -136,7 +136,7 @@
};
},
},
};
};
</script>
<template>
<div>
......
......@@ -20,7 +20,7 @@ export default {
computed: {
contents() {
const obj = {
title: 'You haven\'t added any issues to your project yet',
title: "You haven't added any issues to your project yet",
content: `
An issue can be a bug, a todo or a feature request that needs to be
discussed in a project. Besides, issues are searchable and filterable.
......@@ -28,7 +28,7 @@ export default {
};
if (this.activeTab === 'selected') {
obj.title = 'You haven\'t selected any issues yet';
obj.title = "You haven't selected any issues yet";
obj.content = `
Go back to <strong>Open issues</strong> and select some issues
to add to your board.
......
......@@ -42,19 +42,17 @@ export default {
const req = this.buildUpdateRequest(list);
// Post the data to the backend
gl.boardService
.bulkUpdate(issueIds, req)
.catch(() => {
gl.boardService.bulkUpdate(issueIds, req).catch(() => {
Flash(__('Failed to update issues, please try again.'));
selectedIssues.forEach((issue) => {
selectedIssues.forEach(issue => {
list.removeIssue(issue);
list.issuesSize -= 1;
});
});
// Add the issues on the frontend
selectedIssues.forEach((issue) => {
selectedIssues.forEach(issue => {
list.addIssue(issue);
list.issuesSize += 1;
});
......
<script>
import ModalFilters from './filters';
import ModalTabs from './tabs.vue';
import ModalStore from '../../stores/modal_store';
import modalMixin from '../../mixins/modal_mixins';
import ModalFilters from './filters';
import ModalTabs from './tabs.vue';
import ModalStore from '../../stores/modal_store';
import modalMixin from '../../mixins/modal_mixins';
export default {
export default {
components: {
ModalTabs,
ModalFilters,
......@@ -46,7 +46,7 @@
ModalStore.toggleAll();
},
},
};
};
</script>
<template>
<div>
......
<script>
/* global ListIssue */
import { urlParamsToObject } from '~/lib/utils/common_utils';
import ModalHeader from './header.vue';
import ModalList from './list.vue';
import ModalFooter from './footer.vue';
import EmptyState from './empty_state.vue';
import ModalStore from '../../stores/modal_store';
/* global ListIssue */
import { urlParamsToObject } from '~/lib/utils/common_utils';
import ModalHeader from './header.vue';
import ModalList from './list.vue';
import ModalFooter from './footer.vue';
import EmptyState from './empty_state.vue';
import ModalStore from '../../stores/modal_store';
export default {
export default {
components: {
EmptyState,
ModalHeader,
......@@ -107,7 +107,8 @@
loadIssues(clearIssues = false) {
if (!this.showAddIssuesModal) return false;
return gl.boardService.getBacklog({
return gl.boardService
.getBacklog({
...urlParamsToObject(this.filter.path),
page: this.page,
per: this.perPage,
......@@ -137,7 +138,7 @@
});
},
},
};
};
</script>
<template>
<div
......
<script>
import Icon from '~/vue_shared/components/icon.vue';
import bp from '../../../breakpoints';
import ModalStore from '../../stores/modal_store';
import IssueCardInner from '../issue_card_inner.vue';
import Icon from '~/vue_shared/components/icon.vue';
import bp from '../../../breakpoints';
import ModalStore from '../../stores/modal_store';
import IssueCardInner from '../issue_card_inner.vue';
export default {
export default {
components: {
IssueCardInner,
Icon,
......@@ -114,7 +114,7 @@
}
},
},
};
};
</script>
<template>
<section
......
<script>
import ModalStore from '../../stores/modal_store';
import modalMixin from '../../mixins/modal_mixins';
import ModalStore from '../../stores/modal_store';
import modalMixin from '../../mixins/modal_mixins';
export default {
export default {
mixins: [modalMixin],
data() {
return ModalStore.store;
......@@ -15,7 +15,7 @@
destroyed() {
this.activeTab = 'all';
},
};
};
</script>
<template>
<div class="top-area prepend-top-10 append-bottom-10">
......
......@@ -6,7 +6,9 @@ import _ from 'underscore';
import CreateLabelDropdown from '../../create_label';
import boardsStore from '../stores/boards_store';
$(document).off('created.label').on('created.label', (e, label) => {
$(document)
.off('created.label')
.on('created.label', (e, label) => {
boardsStore.new({
title: label.title,
position: boardsStore.state.lists.length - 2,
......@@ -17,25 +19,28 @@ $(document).off('created.label').on('created.label', (e, label) => {
color: label.color,
},
});
});
});
export default function initNewListDropdown() {
$('.js-new-board-list').each(function () {
$('.js-new-board-list').each(function() {
const $this = $(this);
new CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespacePath'), $this.data('projectPath'));
new CreateLabelDropdown(
$this.closest('.dropdown').find('.dropdown-new-label'),
$this.data('namespacePath'),
$this.data('projectPath'),
);
$this.glDropdown({
data(term, callback) {
axios.get($this.attr('data-list-labels-path'))
.then(({ data }) => {
axios.get($this.attr('data-list-labels-path')).then(({ data }) => {
callback(data);
});
},
renderRow (label) {
renderRow(label) {
const active = boardsStore.findList('title', label.title);
const $li = $('<li />');
const $a = $('<a />', {
class: (active ? `is-active js-board-list-${active.id}` : ''),
class: active ? `is-active js-board-list-${active.id}` : '',
text: label.title,
href: '#',
});
......@@ -53,7 +58,7 @@ export default function initNewListDropdown() {
selectable: true,
multiSelect: true,
containerSelector: '.js-tab-container-labels .dropdown-page-one .dropdown-content',
clicked (options) {
clicked(options) {
const { e } = options;
const label = options.selectedObj;
e.preventDefault();
......
......@@ -46,7 +46,7 @@ export default {
selectable: true,
data: (term, callback) => {
this.loading = true;
return Api.groupProjects(this.groupId, term, {with_issues_enabled: true}, projects => {
return Api.groupProjects(this.groupId, term, { with_issues_enabled: true }, projects => {
this.loading = false;
callback(projects);
});
......@@ -54,7 +54,9 @@ export default {
renderRow(project) {
return `
<li>
<a href='#' class='dropdown-menu-link' data-project-id="${project.id}" data-project-name="${project.name}">
<a href='#' class='dropdown-menu-link' data-project-id="${
project.id
}" data-project-name="${project.name}">
${_.escape(project.name)}
</a>
</li>
......
<script>
import Vue from 'vue';
import Flash from '../../../flash';
import { __ } from '../../../locale';
import boardsStore from '../../stores/boards_store';
import Vue from 'vue';
import Flash from '../../../flash';
import { __ } from '../../../locale';
import boardsStore from '../../stores/boards_store';
export default Vue.extend({
export default Vue.extend({
props: {
issue: {
type: Object,
......@@ -56,9 +56,7 @@
buildPatchRequest(issue, lists) {
const listLabelIds = lists.map(list => list.label.id);
const labelIds = issue.labels
.map(label => label.id)
.filter(id => !listLabelIds.includes(id));
const labelIds = issue.labels.map(label => label.id).filter(id => !listLabelIds.includes(id));
return {
label_ids: labelIds,
......@@ -73,7 +71,7 @@
return req;
},
},
});
});
</script>
<template>
<div
......
......@@ -32,7 +32,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
const tokens = FilteredSearchContainer.container.querySelectorAll('.js-visual-token');
// Remove all the tokens as they will be replaced by the search manager
[].forEach.call(tokens, (el) => {
[].forEach.call(tokens, el => {
el.parentNode.removeChild(el);
});
......@@ -50,7 +50,10 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
canEdit(tokenName, tokenValue) {
if (this.cantEdit.includes(tokenName)) return false;
return this.cantEditWithValue.findIndex(token => token.name === tokenName &&
token.value === tokenValue) === -1;
return (
this.cantEditWithValue.findIndex(
token => token.name === tokenName && token.value === tokenValue,
) === -1
);
}
}
......@@ -32,9 +32,9 @@ export default () => {
const $boardApp = document.getElementById('board-app');
// check for browser back and trigger a hard reload to circumvent browser caching.
window.addEventListener('pageshow', (event) => {
const isNavTypeBackForward = window.performance &&
window.performance.navigation.type === NavigationType.TYPE_BACK_FORWARD;
window.addEventListener('pageshow', event => {
const isNavTypeBackForward =
window.performance && window.performance.navigation.type === NavigationType.TYPE_BACK_FORWARD;
if (event.persisted || isNavTypeBackForward) {
window.location.reload();
......
......@@ -4,7 +4,8 @@ import $ from 'jquery';
import sortableConfig from '../../sortable/sortable_config';
export function sortableStart() {
$('.has-tooltip').tooltip('hide')
$('.has-tooltip')
.tooltip('hide')
.tooltip('disable');
document.body.classList.add('is-dragging');
}
......@@ -15,7 +16,8 @@ export function sortableEnd() {
}
export function getBoardSortableDefaultOptions(obj) {
const touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
const touchEnabled =
'ontouchstart' in window || (window.DocumentTouch && document instanceof DocumentTouch);
const defaultSortOptions = Object.assign({}, sortableConfig, {
filter: '.board-delete, .btn',
......@@ -26,6 +28,8 @@ export function getBoardSortableDefaultOptions(obj) {
onEnd: sortableEnd,
});
Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
Object.keys(obj).forEach(key => {
defaultSortOptions[key] = obj[key];
});
return defaultSortOptions;
}
......@@ -9,7 +9,7 @@ import IssueProject from './project';
import boardsStore from '../stores/boards_store';
class ListIssue {
constructor (obj, defaultAvatar) {
constructor(obj, defaultAvatar) {
this.id = obj.id;
this.iid = obj.iid;
this.title = obj.title;
......@@ -39,54 +39,54 @@ class ListIssue {
this.milestone = new ListMilestone(obj.milestone);
}
obj.labels.forEach((label) => {
obj.labels.forEach(label => {
this.labels.push(new ListLabel(label));
});
this.assignees = obj.assignees.map(a => new ListAssignee(a, defaultAvatar));
}
addLabel (label) {
addLabel(label) {
if (!this.findLabel(label)) {
this.labels.push(new ListLabel(label));
}
}
findLabel (findLabel) {
findLabel(findLabel) {
return this.labels.filter(label => label.title === findLabel.title)[0];
}
removeLabel (removeLabel) {
removeLabel(removeLabel) {
if (removeLabel) {
this.labels = this.labels.filter(label => removeLabel.title !== label.title);
}
}
removeLabels (labels) {
removeLabels(labels) {
labels.forEach(this.removeLabel.bind(this));
}
addAssignee (assignee) {
addAssignee(assignee) {
if (!this.findAssignee(assignee)) {
this.assignees.push(new ListAssignee(assignee));
}
}
findAssignee (findAssignee) {
findAssignee(findAssignee) {
return this.assignees.filter(assignee => assignee.id === findAssignee.id)[0];
}
removeAssignee (removeAssignee) {
removeAssignee(removeAssignee) {
if (removeAssignee) {
this.assignees = this.assignees.filter(assignee => assignee.id !== removeAssignee.id);
}
}
removeAllAssignees () {
removeAllAssignees() {
this.assignees = [];
}
getLists () {
getLists() {
return boardsStore.state.lists.filter(list => list.findIssue(this.id));
}
......@@ -102,14 +102,14 @@ class ListIssue {
this.isLoading[key] = value;
}
update () {
update() {
const data = {
issue: {
milestone_id: this.milestone ? this.milestone.id : null,
due_date: this.dueDate,
assignee_ids: this.assignees.length > 0 ? this.assignees.map((u) => u.id) : [0],
label_ids: this.labels.map((label) => label.id)
}
assignee_ids: this.assignees.length > 0 ? this.assignees.map(u => u.id) : [0],
label_ids: this.labels.map(label => label.id),
},
};
if (!data.issue.label_ids.length) {
......
......@@ -234,11 +234,11 @@ class List {
});
}
getTypeInfo (type) {
getTypeInfo(type) {
return TYPES[type] || {};
}
onNewIssueResponse (issue, data) {
onNewIssueResponse(issue, data) {
issue.id = data.id;
issue.iid = data.iid;
issue.project = data.project;
......
......@@ -19,7 +19,9 @@ export default class BoardService {
}
static generateIssuePath(boardId, id) {
return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${id ? `/${id}` : ''}`;
return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${
id ? `/${id}` : ''
}`;
}
all() {
......@@ -54,7 +56,9 @@ export default class BoardService {
getIssuesForList(id, filter = {}) {
const data = { id };
Object.keys(filter).forEach((key) => { data[key] = filter[key]; });
Object.keys(filter).forEach(key => {
data[key] = filter[key];
});
return axios.get(mergeUrlParams(data, this.generateIssuesPath(id)));
}
......@@ -75,7 +79,9 @@ export default class BoardService {
}
getBacklog(data) {
return axios.get(mergeUrlParams(data, `${gon.relative_url_root}/-/boards/${this.boardId}/issues.json`));
return axios.get(
mergeUrlParams(data, `${gon.relative_url_root}/-/boards/${this.boardId}/issues.json`),
);
}
bulkUpdate(issueIds, extraData = {}) {
......
......@@ -20,20 +20,20 @@ const boardsStore = {
issue: {},
list: {},
},
create () {
create() {
this.state.lists = [];
this.filter.path = getUrlParamsArray().join('&');
this.detail = {
issue: {},
};
},
addList (listObj, defaultAvatar) {
addList(listObj, defaultAvatar) {
const list = new List(listObj, defaultAvatar);
this.state.lists.push(list);
return list;
},
new (listObj) {
new(listObj) {
const list = this.addList(listObj);
const backlogList = this.findList('type', 'backlog', 'backlog');
......@@ -50,44 +50,44 @@ const boardsStore = {
});
this.removeBlankState();
},
updateNewListDropdown (listId) {
updateNewListDropdown(listId) {
$(`.js-board-list-${listId}`).removeClass('is-active');
},
shouldAddBlankState () {
shouldAddBlankState() {
// Decide whether to add the blank state
return !(this.state.lists.filter(list => list.type !== 'backlog' && list.type !== 'closed')[0]);
return !this.state.lists.filter(list => list.type !== 'backlog' && list.type !== 'closed')[0];
},
addBlankState () {
addBlankState() {
if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return;
this.addList({
id: 'blank',
list_type: 'blank',
title: 'Welcome to your Issue Board!',
position: 0
position: 0,
});
this.state.lists = _.sortBy(this.state.lists, 'position');
},
removeBlankState () {
removeBlankState() {
this.removeList('blank');
Cookies.set('issue_board_welcome_hidden', 'true', {
expires: 365 * 10,
path: ''
path: '',
});
},
welcomeIsHidden () {
welcomeIsHidden() {
return Cookies.get('issue_board_welcome_hidden') === 'true';
},
removeList (id, type = 'blank') {
removeList(id, type = 'blank') {
const list = this.findList('id', id, type);
if (!list) return;
this.state.lists = this.state.lists.filter(list => list.id !== id);
},
moveList (listFrom, orderLists) {
moveList(listFrom, orderLists) {
orderLists.forEach((id, i) => {
const list = this.findList('id', parseInt(id, 10));
......@@ -95,21 +95,24 @@ const boardsStore = {
});
listFrom.update();
},
moveIssueToList (listFrom, listTo, issue, newIndex) {
moveIssueToList(listFrom, listTo, issue, newIndex) {
const issueTo = listTo.findIssue(issue.id);
const issueLists = issue.getLists();
const listLabels = issueLists.map(listIssue => listIssue.label);
if (!issueTo) {
// Check if target list assignee is already present in this issue
if ((listTo.type === 'assignee' && listFrom.type === 'assignee') &&
issue.findAssignee(listTo.assignee)) {
if (
listTo.type === 'assignee' &&
listFrom.type === 'assignee' &&
issue.findAssignee(listTo.assignee)
) {
const targetIssue = listTo.findIssue(issue.id);
targetIssue.removeAssignee(listFrom.assignee);
} else if (listTo.type === 'milestone') {
const currentMilestone = issue.milestone;
const currentLists = this.state.lists
.filter(list => (list.type === 'milestone' && list.id !== listTo.id))
.filter(list => list.type === 'milestone' && list.id !== listTo.id)
.filter(list => list.issues.some(listIssue => issue.id === listIssue.id));
issue.removeMilestone(currentMilestone);
......@@ -126,7 +129,7 @@ const boardsStore = {
}
if (listTo.type === 'closed' && listFrom.type !== 'backlog') {
issueLists.forEach((list) => {
issueLists.forEach(list => {
list.removeIssue(issue);
});
issue.removeLabels(listLabels);
......@@ -144,26 +147,28 @@ const boardsStore = {
return (
(listTo.type !== 'label' && listFrom.type === 'assignee') ||
(listTo.type !== 'assignee' && listFrom.type === 'label') ||
(listFrom.type === 'backlog')
listFrom.type === 'backlog'
);
},
moveIssueInList (list, issue, oldIndex, newIndex, idArray) {
moveIssueInList(list, issue, oldIndex, newIndex, idArray) {
const beforeId = parseInt(idArray[newIndex - 1], 10) || null;
const afterId = parseInt(idArray[newIndex + 1], 10) || null;
list.moveIssue(issue, oldIndex, newIndex, beforeId, afterId);
},
findList (key, val, type = 'label') {
const filteredList = this.state.lists.filter((list) => {
const byType = type ? (list.type === type) || (list.type === 'assignee') || (list.type === 'milestone') : true;
findList(key, val, type = 'label') {
const filteredList = this.state.lists.filter(list => {
const byType = type
? list.type === type || list.type === 'assignee' || list.type === 'milestone'
: true;
return list[key] === val && byType;
});
return filteredList[0];
},
updateFiltersUrl () {
updateFiltersUrl() {
window.history.pushState(null, null, `?${this.filter.path}`);
}
},
};
// hacks added in order to allow milestone_select to function properly
......
......@@ -40,7 +40,7 @@ class ModalStore {
toggleAll() {
const select = this.selectedCount() !== this.store.issues.length;
this.store.issues.forEach((issue) => {
this.store.issues.forEach(issue => {
const issueUpdate = issue;
if (issueUpdate.selected !== select) {
......@@ -69,13 +69,14 @@ class ModalStore {
removeSelectedIssue(issue, forcePurge = false) {
if (this.store.activeTab === 'all' || forcePurge) {
this.store.selectedIssues = this.store.selectedIssues
.filter(fIssue => fIssue.id !== issue.id);
this.store.selectedIssues = this.store.selectedIssues.filter(
fIssue => fIssue.id !== issue.id,
);
}
}
purgeUnselectedIssues() {
this.store.selectedIssues.forEach((issue) => {
this.store.selectedIssues.forEach(issue => {
if (!issue.selected) {
this.removeSelectedIssue(issue, true);
}
......@@ -87,8 +88,7 @@ class ModalStore {
}
findSelectedIssue(issue) {
return this.store.selectedIssues
.filter(filteredIssue => filteredIssue.id === issue.id)[0];
return this.store.selectedIssues.filter(filteredIssue => filteredIssue.id === issue.id)[0];
}
}
......
<script>
import _ from 'underscore';
import helmInstallIllustration from '@gitlab-org/gitlab-svgs/dist/illustrations/kubernetes-installation.svg';
import helmInstallIllustration from '@gitlab/svgs/dist/illustrations/kubernetes-installation.svg';
import elasticsearchLogo from 'images/cluster_app_logos/elasticsearch.png';
import gitlabLogo from 'images/cluster_app_logos/gitlab.png';
import helmLogo from 'images/cluster_app_logos/helm.png';
......
import Vue from 'vue';
import {
GlPagination,
GlProgressBar,
GlModal,
GlLoadingIcon,
GlModalDirective,
GlTooltipDirective,
} from '@gitlab-org/gitlab-ui';
import { GlProgressBar, GlLoadingIcon, GlTooltipDirective } from '@gitlab-org/gitlab-ui';
Vue.component('gl-pagination', GlPagination);
Vue.component('gl-progress-bar', GlProgressBar);
Vue.component('gl-ui-modal', GlModal);
Vue.component('gl-loading-icon', GlLoadingIcon);
Vue.directive('gl-modal', GlModalDirective);
Vue.directive('gl-tooltip', GlTooltipDirective);
......@@ -18,8 +18,8 @@ export default {
},
data() {
const treeListStored = localStorage.getItem(treeListStorageKey);
const renderTreeList = treeListStored !== null ?
convertPermissionToBoolean(treeListStored) : true;
const renderTreeList =
treeListStored !== null ? convertPermissionToBoolean(treeListStored) : true;
return {
search: '',
......
......@@ -15,7 +15,7 @@ import CommitComponent from '../../vue_shared/components/commit.vue';
import eventHub from '../event_hub';
/**
* Envrionment Item Component
* Environment Item Component
*
* Renders a table row for each environment.
*/
......@@ -60,7 +60,7 @@ export default {
computed: {
/**
* Verifies if `last_deployment` key exists in the current Envrionment.
* Verifies if `last_deployment` key exists in the current Environment.
* This key is required to render most of the html - this method works has
* an helper.
*
......
<script>
import Flash from '../../flash';
import { s__ } from '../../locale';
import emptyState from './empty_state.vue';
import eventHub from '../event_hub';
import environmentsMixin from '../mixins/environments_mixin';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
import StopEnvironmentModal from './stop_environment_modal.vue';
import Flash from '../../flash';
import { s__ } from '../../locale';
import emptyState from './empty_state.vue';
import eventHub from '../event_hub';
import environmentsMixin from '../mixins/environments_mixin';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
import StopEnvironmentModal from './stop_environment_modal.vue';
export default {
export default {
components: {
emptyState,
StopEnvironmentModal,
},
mixins: [
CIPaginationMixin,
environmentsMixin,
],
mixins: [CIPaginationMixin, environmentsMixin],
props: {
endpoint: {
......@@ -69,7 +66,8 @@
fetchChildEnvironments(folder, showLoader = false) {
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader);
this.service.getFolderContent(folder.folder_path)
this.service
.getFolderContent(folder.folder_path)
.then(response => this.store.setfolderContent(folder, response.data.environments))
.then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false))
.catch(() => {
......@@ -88,7 +86,7 @@
}
},
},
};
};
</script>
<template>
<div :class="cssContainerClass">
......
......@@ -34,9 +34,9 @@ export default class EnvironmentsStore {
* @returns {Array}
*/
storeEnvironments(environments = []) {
const filteredEnvironments = environments.map((env) => {
const oldEnvironmentState = this.state.environments
.find((element) => {
const filteredEnvironments = environments.map(env => {
const oldEnvironmentState =
this.state.environments.find(element => {
if (env.latest) {
return element.id === env.latest.id;
}
......@@ -119,7 +119,7 @@ export default class EnvironmentsStore {
* @return {Object}
*/
setfolderContent(folder, environments) {
const updatedEnvironments = environments.map((env) => {
const updatedEnvironments = environments.map(env => {
let updated = env;
if (env.latest) {
......@@ -148,7 +148,7 @@ export default class EnvironmentsStore {
updateEnvironmentProp(environment, prop, newValue) {
const { environments } = this.state;
const updatedEnvironments = environments.map((env) => {
const updatedEnvironments = environments.map(env => {
const updateEnv = Object.assign({}, env);
if (env.id === environment.id) {
updateEnv[prop] = newValue;
......
......@@ -39,8 +39,9 @@ export default class DropdownUser extends FilteredSearchDropdown {
}
itemClicked(e) {
super.itemClicked(e,
selected => selected.querySelector('.dropdown-light-content').innerText.trim());
super.itemClicked(e, selected =>
selected.querySelector('.dropdown-light-content').innerText.trim(),
);
}
renderContent(forceShowList = false) {
......@@ -68,7 +69,7 @@ export default class DropdownUser extends FilteredSearchDropdown {
// Removes the first character if it is a quotation so that we can search
// with multiple words
if (value[0] === '"' || value[0] === '\'') {
if (value[0] === '"' || value[0] === "'") {
value = value.slice(1);
}
......
......@@ -85,7 +85,7 @@ export default class FilteredSearchDropdown {
}
dispatchInputEvent() {
// Propogate input change to FilteredSearchDropdownManager
// Propagate input change to FilteredSearchDropdownManager
// so that it can determine which dropdowns to open
this.input.dispatchEvent(
new CustomEvent('input', {
......
......@@ -108,7 +108,7 @@ export default class FilteredSearchDropdownManager {
},
};
supportedTokens.forEach((type) => {
supportedTokens.forEach(type => {
if (availableMappings[type]) {
allowedMappings[type] = availableMappings[type];
}
......@@ -142,10 +142,7 @@ export default class FilteredSearchDropdownManager {
}
static addWordToInput(tokenName, tokenValue = '', clicked = false, options = {}) {
const {
uppercaseTokenName = false,
capitalizeTokenValue = false,
} = options;
const { uppercaseTokenName = false, capitalizeTokenValue = false } = options;
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenValue, {
uppercaseTokenName,
......@@ -164,13 +161,16 @@ export default class FilteredSearchDropdownManager {
updateDropdownOffset(key) {
// Always align dropdown with the input field
let offset = this.filteredSearchInput.getBoundingClientRect().left - this.container.querySelector('.scroll-container').getBoundingClientRect().left;
let offset =
this.filteredSearchInput.getBoundingClientRect().left -
this.container.querySelector('.scroll-container').getBoundingClientRect().left;
const maxInputWidth = 240;
const currentDropdownWidth = this.mapping[key].element.clientWidth || maxInputWidth;
// Make sure offset never exceeds the input container
const offsetMaxWidth = this.container.querySelector('.scroll-container').clientWidth - currentDropdownWidth;
const offsetMaxWidth =
this.container.querySelector('.scroll-container').clientWidth - currentDropdownWidth;
if (offsetMaxWidth < offset) {
offset = offsetMaxWidth;
}
......@@ -196,8 +196,7 @@ export default class FilteredSearchDropdownManager {
const glArguments = Object.assign({}, defaultArguments, extraArguments);
// Passing glArguments to `new glClass(<arguments>)`
mappingKey.reference =
new (Function.prototype.bind.apply(glClass, [null, glArguments]))();
mappingKey.reference = new (Function.prototype.bind.apply(glClass, [null, glArguments]))();
}
if (firstLoad) {
......@@ -224,8 +223,8 @@ export default class FilteredSearchDropdownManager {
}
const match = this.filteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase());
const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key
&& this.mapping[match.key];
const shouldOpenFilterDropdown =
match && this.currentDropdown !== match.key && this.mapping[match.key];
const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint';
if (shouldOpenFilterDropdown || shouldOpenHintDropdown) {
......@@ -236,8 +235,10 @@ export default class FilteredSearchDropdownManager {
setDropdown() {
const query = DropdownUtils.getSearchQuery(true);
const { lastToken, searchToken } =
this.tokenizer.processTokens(query, this.filteredSearchTokenKeys.getKeys());
const { lastToken, searchToken } = this.tokenizer.processTokens(
query,
this.filteredSearchTokenKeys.getKeys(),
);
if (this.currentDropdown) {
this.updateCurrentDropdownOffset();
......
......@@ -68,12 +68,12 @@ export const conditions = [
value: 'any',
},
{
url: 'milestone_title=No+Milestone',
url: 'milestone_title=None',
tokenKey: 'milestone',
value: 'none',
},
{
url: 'milestone_title=Any+Milestone',
url: 'milestone_title=Any',
tokenKey: 'milestone',
value: 'any',
},
......@@ -92,6 +92,16 @@ export const conditions = [
tokenKey: 'label',
value: 'none',
},
{
url: 'my_reaction_emoji=None',
tokenKey: 'my-reaction',
value: 'none',
},
{
url: 'my_reaction_emoji=Any',
tokenKey: 'my-reaction',
value: 'any',
},
];
const IssuableFilteredSearchTokenKeys = new FilteredSearchTokenKeys(
......
......@@ -40,7 +40,9 @@ const createFlashEl = (message, type, isFixedLayout = false) => `
class="flash-${type}"
>
<div
class="flash-text ${isFixedLayout ? 'container-fluid container-limited limit-container-width' : ''}"
class="flash-text ${
isFixedLayout ? 'container-fluid container-limited limit-container-width' : ''
}"
>
${_.escape(message)}
</div>
......@@ -78,7 +80,9 @@ const createFlash = function createFlash(
if (!flashContainer) return null;
const isFixedLayout = navigation ? navigation.parentNode.classList.contains('container-limited') : true;
const isFixedLayout = navigation
? navigation.parentNode.classList.contains('container-limited')
: true;
flashContainer.innerHTML = createFlashEl(message, type, isFixedLayout);
......
......@@ -94,7 +94,7 @@ class GfmAutoComplete {
...this.getDefaultCallbacks(),
beforeSave(commands) {
if (GfmAutoComplete.isLoading(commands)) return commands;
return $.map(commands, (c) => {
return $.map(commands, c => {
let search = c.name;
if (c.aliases.length > 0) {
search = `${search} ${c.aliases.join(' ')}`;
......@@ -167,7 +167,7 @@ class GfmAutoComplete {
callbacks: {
...this.getDefaultCallbacks(),
beforeSave(members) {
return $.map(members, (m) => {
return $.map(members, m => {
let title = '';
if (m.username == null) {
return m;
......@@ -178,7 +178,9 @@ class GfmAutoComplete {
}
const autoCompleteAvatar = m.avatar_url || m.username.charAt(0).toUpperCase();
const imgAvatar = `<img src="${m.avatar_url}" alt="${m.username}" class="avatar avatar-inline center s26"/>`;
const imgAvatar = `<img src="${m.avatar_url}" alt="${
m.username
}" class="avatar avatar-inline center s26"/>`;
const txtAvatar = `<div class="avatar center avatar-inline s26">${autoCompleteAvatar}</div>`;
return {
......@@ -211,7 +213,7 @@ class GfmAutoComplete {
callbacks: {
...this.getDefaultCallbacks(),
beforeSave(issues) {
return $.map(issues, (i) => {
return $.map(issues, i => {
if (i.title == null) {
return i;
}
......@@ -244,7 +246,7 @@ class GfmAutoComplete {
callbacks: {
...this.getDefaultCallbacks(),
beforeSave(milestones) {
return $.map(milestones, (m) => {
return $.map(milestones, m => {
if (m.title == null) {
return m;
}
......@@ -277,7 +279,7 @@ class GfmAutoComplete {
callbacks: {
...this.getDefaultCallbacks(),
beforeSave(merges) {
return $.map(merges, (m) => {
return $.map(merges, m => {
if (m.title == null) {
return m;
}
......@@ -324,13 +326,20 @@ class GfmAutoComplete {
},
matcher(flag, subtext) {
const match = GfmAutoComplete.defaultMatcher(flag, subtext, this.app.controllers);
const subtextNodes = subtext.split(/\n+/g).pop().split(GfmAutoComplete.regexSubtext);
const subtextNodes = subtext
.split(/\n+/g)
.pop()
.split(GfmAutoComplete.regexSubtext);
// Check if ~ is followed by '/label', '/relabel' or '/unlabel' commands.
command = subtextNodes.find((node) => {
if (node === LABEL_COMMAND.LABEL ||
command = subtextNodes.find(node => {
if (
node === LABEL_COMMAND.LABEL ||
node === LABEL_COMMAND.RELABEL ||
node === LABEL_COMMAND.UNLABEL) { return node; }
node === LABEL_COMMAND.UNLABEL
) {
return node;
}
return null;
});
......@@ -380,7 +389,7 @@ class GfmAutoComplete {
callbacks: {
...this.getDefaultCallbacks(),
beforeSave(snippets) {
return $.map(snippets, (m) => {
return $.map(snippets, m => {
if (m.title == null) {
return m;
}
......@@ -458,13 +467,17 @@ class GfmAutoComplete {
this.loadData($input, at, validEmojiNames);
GfmAutoComplete.glEmojiTag = glEmojiTag;
})
.catch(() => { this.isLoadingData[at] = false; });
.catch(() => {
this.isLoadingData[at] = false;
});
} else if (dataSource) {
AjaxCache.retrieve(dataSource, true)
.then((data) => {
.then(data => {
this.loadData($input, at, data);
})
.catch(() => { this.isLoadingData[at] = false; });
.catch(() => {
this.isLoadingData[at] = false;
});
} else {
this.isLoadingData[at] = false;
}
......@@ -497,15 +510,16 @@ class GfmAutoComplete {
}
const loadingState = GfmAutoComplete.defaultLoadingData[0];
return dataToInspect &&
(dataToInspect === loadingState || dataToInspect.name === loadingState);
return dataToInspect && (dataToInspect === loadingState || dataToInspect.name === loadingState);
}
static defaultMatcher(flag, subtext, controllers) {
// The below is taken from At.js source
// Tweaked to commands to start without a space only if char before is a non-word character
// https://github.com/ichord/At.js
const atSymbolsWithBar = Object.keys(controllers).join('|').replace(/[$]/, '\\$&');
const atSymbolsWithBar = Object.keys(controllers)
.join('|')
.replace(/[$]/, '\\$&');
const atSymbolsWithoutBar = Object.keys(controllers).join('');
const targetSubtext = subtext.split(GfmAutoComplete.regexSubtext).pop();
const resultantFlag = flag.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
......@@ -513,7 +527,10 @@ class GfmAutoComplete {
const accentAChar = decodeURI('%C3%80');
const accentYChar = decodeURI('%C3%BF');
const regexp = new RegExp(`^(?:\\B|[^a-zA-Z0-9_\`${atSymbolsWithoutBar}]|\\s)${resultantFlag}(?!${atSymbolsWithBar})((?:[A-Za-z${accentAChar}-${accentYChar}0-9_'.+-]|[^\\x00-\\x7a])*)$`, 'gi');
const regexp = new RegExp(
`^(?:\\B|[^a-zA-Z0-9_\`${atSymbolsWithoutBar}]|\\s)${resultantFlag}(?!${atSymbolsWithBar})((?:[A-Za-z${accentAChar}-${accentYChar}0-9_'.+-]|[^\\x00-\\x7a])*)$`,
'gi',
);
return regexp.exec(targetSubtext);
}
......@@ -552,8 +569,9 @@ GfmAutoComplete.Members = {
template: '<li>${avatarTag} ${username} <small>${title}</small></li>',
};
GfmAutoComplete.Labels = {
template:
// eslint-disable-next-line no-template-curly-in-string
template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>',
'<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>',
};
// Issues, MergeRequests and Snippets
GfmAutoComplete.Issues = {
......@@ -567,7 +585,8 @@ GfmAutoComplete.Milestones = {
template: '<li>${title}</li>',
};
GfmAutoComplete.Loading = {
template: '<li style="pointer-events: none;"><i class="fa fa-spinner fa-spin"></i> Loading...</li>',
template:
'<li style="pointer-events: none;"><i class="fa fa-spinner fa-spin"></i> Loading...</li>',
};
export default GfmAutoComplete;
......@@ -28,7 +28,7 @@ export default class GlFieldErrors {
this.form.on('submit', GlFieldErrors.catchInvalidFormSubmit);
}
/* Neccessary to prevent intercept and override invalid form submit
/* Necessary to prevent intercept and override invalid form submit
* because Safari & iOS quietly allow form submission when form is invalid
* and prevents disabling of invalid submit button by application.js */
......
import $ from 'jquery';
import { slugifyWithHyphens } from './lib/utils/text_utility';
export default class Group {
constructor() {
......@@ -7,17 +8,18 @@ export default class Group {
this.updateHandler = this.update.bind(this);
this.resetHandler = this.reset.bind(this);
if (this.groupName.val() === '') {
this.groupPath.on('keyup', this.updateHandler);
this.groupName.on('keydown', this.resetHandler);
this.groupName.on('keyup', this.updateHandler);
this.groupPath.on('keydown', this.resetHandler);
}
}
update() {
this.groupName.val(this.groupPath.val());
const slug = slugifyWithHyphens(this.groupName.val());
this.groupPath.val(slug);
}
reset() {
this.groupPath.off('keyup', this.updateHandler);
this.groupName.off('keydown', this.resetHandler);
this.groupName.off('keyup', this.updateHandler);
this.groupPath.off('keydown', this.resetHandler);
}
}
......@@ -43,7 +43,7 @@ export default {
'currentProjectId',
'errorMessage',
]),
...mapGetters(['activeFile', 'hasChanges', 'someUncommitedChanges', 'isCommitModeActive']),
...mapGetters(['activeFile', 'hasChanges', 'someUncommittedChanges', 'isCommitModeActive']),
},
mounted() {
window.onbeforeunload = e => this.onBeforeUnload(e);
......@@ -63,7 +63,7 @@ export default {
onBeforeUnload(e = {}) {
const returnValue = __('Are you sure you want to lose unsaved changes?');
if (!this.someUncommitedChanges) return undefined;
if (!this.someUncommittedChanges) return undefined;
Object.assign(e, {
returnValue,
......
......@@ -25,11 +25,11 @@ export default {
},
computed: {
...mapState(['loading', 'currentActivityView', 'changedFiles', 'stagedFiles', 'lastCommitMsg']),
...mapGetters(['currentProject', 'someUncommitedChanges']),
...mapGetters(['currentProject', 'someUncommittedChanges']),
showSuccessMessage() {
return (
this.currentActivityView === activityBarViews.edit &&
(this.lastCommitMsg && !this.someUncommitedChanges)
(this.lastCommitMsg && !this.someUncommittedChanges)
);
},
},
......
......@@ -27,10 +27,10 @@ export default {
'unusedSeal',
]),
...mapState('commit', ['commitMessage', 'submitCommitLoading']),
...mapGetters(['lastOpenedFile', 'hasChanges', 'someUncommitedChanges', 'activeFile']),
...mapGetters(['lastOpenedFile', 'hasChanges', 'someUncommittedChanges', 'activeFile']),
...mapGetters('commit', ['discardDraftButtonDisabled']),
showStageUnstageArea() {
return !!(this.someUncommitedChanges || this.lastCommitMsg || !this.unusedSeal);
return !!(this.someUncommittedChanges || this.lastCommitMsg || !this.unusedSeal);
},
activeFileKey() {
return this.activeFile ? this.activeFile.key : null;
......
......@@ -63,7 +63,7 @@ export const isEditModeActive = state => state.currentActivityView === activityB
export const isCommitModeActive = state => state.currentActivityView === activityBarViews.commit;
export const isReviewModeActive = state => state.currentActivityView === activityBarViews.review;
export const someUncommitedChanges = state =>
export const someUncommittedChanges = state =>
!!(state.changedFiles.length || state.stagedFiles.length);
export const getChangesInFolder = state => path => {
......
<script>
import _ from 'underscore';
import { mapGetters, mapState, mapActions } from 'vuex';
import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
import bp from '~/breakpoints';
import CiHeader from '~/vue_shared/components/header_ci_component.vue';
import Callout from '~/vue_shared/components/callout.vue';
import createStore from '../store';
import EmptyState from './empty_state.vue';
import EnvironmentsBlock from './environments_block.vue';
import ErasedBlock from './erased_block.vue';
import Log from './job_log.vue';
import LogTopBar from './job_log_controllers.vue';
import StuckBlock from './stuck_block.vue';
import Sidebar from './sidebar.vue';
import _ from 'underscore';
import { mapGetters, mapState, mapActions } from 'vuex';
import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
import bp from '~/breakpoints';
import CiHeader from '~/vue_shared/components/header_ci_component.vue';
import Callout from '~/vue_shared/components/callout.vue';
import createStore from '../store';
import EmptyState from './empty_state.vue';
import EnvironmentsBlock from './environments_block.vue';
import ErasedBlock from './erased_block.vue';
import Log from './job_log.vue';
import LogTopBar from './job_log_controllers.vue';
import StuckBlock from './stuck_block.vue';
import Sidebar from './sidebar.vue';
export default {
export default {
name: 'JobPageApp',
store: createStore(),
components: {
......@@ -86,7 +86,7 @@
shouldRenderContent() {
return !this.isLoading && !this.hasError;
}
},
},
watch: {
// Once the job log is loaded,
......@@ -158,7 +158,7 @@
this.throttled();
},
},
};
};
</script>
<template>
<div>
......
<script>
import { mapState, mapActions } from 'vuex';
import { mapState, mapActions } from 'vuex';
export default {
export default {
name: 'JobLog',
props: {
trace: {
......@@ -39,7 +39,7 @@
}
},
},
};
};
</script>
<template>
<pre class="js-build-trace build-trace qa-build-trace">
......
......@@ -23,4 +23,3 @@ export default () => {
},
});
};
......@@ -35,16 +35,19 @@ export const hasEnvironment = state => !_.isEmpty(state.job.deployment_status);
* Used to check if it should render the job log or the empty state
* @returns {Boolean}
*/
export const hasTrace = state => state.job.has_trace || (!_.isEmpty(state.job.status) && state.job.status.group === 'running');
export const hasTrace = state =>
state.job.has_trace || (!_.isEmpty(state.job.status) && state.job.status.group === 'running');
export const emptyStateIllustration = state =>
(state.job && state.job.status && state.job.status.illustration) || {};
export const emptyStateAction = state => (state.job && state.job.status && state.job.status.action) || {};
export const emptyStateAction = state =>
(state.job && state.job.status && state.job.status.action) || {};
export const isScrollingDown = state => isScrolledToBottom() && !state.isTraceComplete;
export const hasRunnersForProject = state => state.job.runners.available && !state.job.runners.online;
export const hasRunnersForProject = state =>
state.job.runners.available && !state.job.runners.online;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
This diff is collapsed.
/* global ace */
export default function getModeByFileExtension(path) {
const modelist = ace.require("ace/ext/modelist");
const modelist = ace.require('ace/ext/modelist');
return modelist.getModeForPath(path).mode;
};
}
......@@ -7,7 +7,7 @@ import { BYTES_IN_KIB } from './constants';
* * * Show 3 digits to the right
* * For 2 digits to the left of the decimal point and X digits to the right of it
* * * Show 2 digits to the right
*/
*/
export function formatRelevantDigits(number) {
let digitsLeft = '';
let relevantDigits = 0;
......
......@@ -7,8 +7,12 @@ export default class Members {
}
addListeners() {
$('.js-member-update-control').off('change').on('change', this.formSubmit.bind(this));
$('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess.bind(this));
$('.js-member-update-control')
.off('change')
.on('change', this.formSubmit.bind(this));
$('.js-edit-member-form')
.off('ajax:success')
.on('ajax:success', this.formSuccess.bind(this));
gl.utils.disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change');
}
......@@ -28,7 +32,7 @@ export default class Members {
toggleLabel(selected, $el) {
return $el.text();
},
clicked: (options) => {
clicked: options => {
this.formSubmit(null, options.$el);
},
});
......
......@@ -9,7 +9,10 @@ import '~/gl_dropdown';
import axios from './lib/utils/axios_utils';
import { timeFor } from './lib/utils/datetime_utility';
import ModalStore from './boards/stores/modal_store';
import boardsStore, { boardStoreIssueSet, boardStoreIssueDelete } from './boards/stores/boards_store';
import boardsStore, {
boardStoreIssueSet,
boardStoreIssueDelete,
} from './boards/stores/boards_store';
export default class MilestoneSelect {
constructor(currentProject, els, options = {}) {
......
......@@ -110,7 +110,7 @@ export default {
// Get the remaining list to use in `and x more` text.
const remainingAwardList = awardList.slice(TOOLTIP_NAME_COUNT, awardList.length);
// Add myself to the begining of the list so title will start with You.
// Add myself to the beginning of the list so title will start with You.
if (hasReactionByCurrentUser) {
namesToShow.unshift('You');
}
......
......@@ -54,7 +54,13 @@ export default {
};
},
computed: {
...mapGetters(['isNotesFetched', 'discussions', 'getNotesDataByProp', 'discussionCount', 'isLoading']),
...mapGetters([
'isNotesFetched',
'discussions',
'getNotesDataByProp',
'discussionCount',
'isLoading',
]),
noteableType() {
return this.noteableData.noteableType;
},
......
import Vue from 'vue';
import DiscussionFilter from './components/discussion_filter.vue';
export default (store) => {
export default store => {
const discussionFilterEl = document.getElementById('js-vue-discussion-filter');
if (discussionFilterEl) {
const { defaultFilter, notesFilters } = discussionFilterEl.dataset;
const defaultValue = defaultFilter ? parseInt(defaultFilter, 10) : null;
const filterValues = notesFilters ? JSON.parse(notesFilters) : {};
const filters = Object.keys(filterValues).map(entry =>
({ title: entry, value: filterValues[entry] }));
const filters = Object.keys(filterValues).map(entry => ({
title: entry,
value: filterValues[entry],
}));
return new Vue({
el: discussionFilterEl,
......
......@@ -70,7 +70,7 @@ export const collapseSystemNotes = notes => {
} else if (lastDescriptionSystemNote) {
const timeDifferenceMinutes = getTimeDifferenceMinutes(lastDescriptionSystemNote, note);
// are they less than 10 minutes appart?
// are they less than 10 minutes apart?
if (timeDifferenceMinutes > 10) {
// reset counter
descriptionChangedTimes = 1;
......
<script>
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import GlModal from '~/vue_shared/components/gl_modal.vue';
import { s__, sprintf } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility';
import eventHub from '../event_hub';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import GlModal from '~/vue_shared/components/gl_modal.vue';
import { s__, sprintf } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility';
import eventHub from '../event_hub';
export default {
export default {
components: {
GlModal,
},
......@@ -26,29 +26,41 @@
},
computed: {
title() {
return sprintf(s__('Milestones|Promote %{milestoneTitle} to group milestone?'), { milestoneTitle: this.milestoneTitle });
return sprintf(s__('Milestones|Promote %{milestoneTitle} to group milestone?'), {
milestoneTitle: this.milestoneTitle,
});
},
text() {
return sprintf(s__(`Milestones|Promoting %{milestoneTitle} will make it available for all projects inside %{groupName}.
return sprintf(
s__(`Milestones|Promoting %{milestoneTitle} will make it available for all projects inside %{groupName}.
Existing project milestones with the same title will be merged.
This action cannot be reversed.`), { milestoneTitle: this.milestoneTitle, groupName: this.groupName });
This action cannot be reversed.`),
{ milestoneTitle: this.milestoneTitle, groupName: this.groupName },
);
},
},
methods: {
onSubmit() {
eventHub.$emit('promoteMilestoneModal.requestStarted', this.url);
return axios.post(this.url, { params: { format: 'json' } })
.then((response) => {
eventHub.$emit('promoteMilestoneModal.requestFinished', { milestoneUrl: this.url, successful: true });
return axios
.post(this.url, { params: { format: 'json' } })
.then(response => {
eventHub.$emit('promoteMilestoneModal.requestFinished', {
milestoneUrl: this.url,
successful: true,
});
visitUrl(response.data.url);
})
.catch((error) => {
eventHub.$emit('promoteMilestoneModal.requestFinished', { milestoneUrl: this.url, successful: false });
.catch(error => {
eventHub.$emit('promoteMilestoneModal.requestFinished', {
milestoneUrl: this.url,
successful: false,
});
createFlash(error);
});
},
},
};
};
</script>
<template>
<gl-modal
......@@ -65,4 +77,3 @@
{{ text }}
</gl-modal>
</template>
import Vue from 'vue';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
document.addEventListener('DOMContentLoaded', () => {
const remainingTimeElements = document.querySelectorAll('.js-remaining-time');
remainingTimeElements.forEach(
el =>
new Vue({
...GlCountdown,
el,
propsData: {
endDateString: el.dateTime,
},
}),
);
});
......@@ -64,7 +64,9 @@ export default class Project {
const projectId = $(this).data('project-id');
const cookieKey = `hide_auto_devops_implicitly_enabled_banner_${projectId}`;
Cookies.set(cookieKey, 'false');
$(this).parents('.auto-devops-implicitly-enabled-banner').remove();
$(this)
.parents('.auto-devops-implicitly-enabled-banner')
.remove();
return e.preventDefault();
});
Project.projectSelectDropdown();
......
<script>
import projectFeatureSetting from './project_feature_setting.vue';
import projectFeatureToggle from '../../../../../vue_shared/components/toggle_button.vue';
import projectSettingRow from './project_setting_row.vue';
import { visibilityOptions, visibilityLevelDescriptions } from '../constants';
import { toggleHiddenClassBySelector } from '../external';
import projectFeatureSetting from './project_feature_setting.vue';
import projectFeatureToggle from '../../../../../vue_shared/components/toggle_button.vue';
import projectSettingRow from './project_setting_row.vue';
import { visibilityOptions, visibilityLevelDescriptions } from '../constants';
import { toggleHiddenClassBySelector } from '../external';
export default {
export default {
components: {
projectFeatureSetting,
projectFeatureToggle,
......@@ -91,9 +91,7 @@
computed: {
featureAccessLevelOptions() {
const options = [
[10, 'Only Project Members'],
];
const options = [[10, 'Only Project Members']];
if (this.visibilityLevel !== visibilityOptions.PRIVATE) {
options.push([20, 'Everyone With Access']);
}
......@@ -196,7 +194,7 @@
return this.allowedVisibilityOptions.includes(option);
},
},
};
};
</script>
<template>
......
<script>
import _ from 'underscore';
import { s__, sprintf } from '~/locale';
import { GlModal, GlModalDirective } from '@gitlab-org/gitlab-ui';
export default {
components: {
GlModal,
},
directives: {
'gl-modal': GlModalDirective,
},
props: {
deleteWikiUrl: {
type: String,
......@@ -54,7 +61,7 @@ export default {
>
{{ __('Delete') }}
</button>
<gl-ui-modal
<gl-modal
:title="title"
:ok-title="s__('WikiPageConfirmDelete|Delete page')"
:modal-id="modalId"
......@@ -81,6 +88,6 @@ export default {
name="authenticity_token"
/>
</form>
</gl-ui-modal>
</gl-modal>
</div>
</template>
<script>
import { s__, sprintf } from '~/locale';
import eventHub from '../event_hub';
import icon from '../../vue_shared/components/icon.vue';
import Icon from '../../vue_shared/components/icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
......@@ -10,7 +10,7 @@ export default {
tooltip,
},
components: {
icon,
Icon,
GlCountdown,
},
props: {
......
<script>
import tooltip from '../../vue_shared/directives/tooltip';
import icon from '../../vue_shared/components/icon.vue';
import Icon from '../../vue_shared/components/icon.vue';
export default {
directives: {
tooltip,
},
components: {
icon,
Icon,
},
props: {
artifacts: {
......
......@@ -88,25 +88,25 @@ export default {
class="table-section section-10 js-pipeline-status pipeline-status"
role="rowheader"
>
Status
{{ s__('Pipeline|Status') }}
</div>
<div
class="table-section section-15 js-pipeline-info pipeline-info"
role="rowheader"
>
Pipeline
{{ s__('Pipeline|Pipeline') }}
</div>
<div
class="table-section section-20 js-pipeline-commit pipeline-commit"
role="rowheader"
>
Commit
{{ s__('Pipeline|Commit') }}
</div>
<div
class="table-section section-20 js-pipeline-stages pipeline-stages"
role="rowheader"
>
Stages
{{ s__('Pipeline|Stages') }}
</div>
</div>
<pipelines-table-row-component
......
......@@ -261,7 +261,7 @@ export default {
class="table-mobile-header"
role="rowheader"
>
Status
{{ s__('Pipeline|Status') }}
</div>
<div class="table-mobile-content">
<ci-badge
......@@ -279,8 +279,9 @@ export default {
<div class="table-section section-20">
<div
class="table-mobile-header"
role="rowheader">
Commit
role="rowheader"
>
{{ s__('Pipeline|Commit') }}
</div>
<div class="table-mobile-content">
<commit-component
......@@ -298,8 +299,9 @@ export default {
<div class="table-section section-wrap section-20 stage-cell">
<div
class="table-mobile-header"
role="rowheader">
Stages
role="rowheader"
>
{{ s__('Pipeline|Stages') }}
</div>
<div class="table-mobile-content">
<template v-if="pipeline.details.stages.length > 0">
......
......@@ -60,7 +60,7 @@ export default {
class="table-mobile-header"
role="rowheader"
>
Duration
{{ s__('Pipeline|Duration') }}
</div>
<div class="table-mobile-content">
<p
......@@ -87,7 +87,8 @@ export default {
v-tooltip
:title="tooltipTitle(finishedTime)"
data-placement="top"
data-container="body">
data-container="body"
>
{{ timeFormated(finishedTime) }}
</time>
</p>
......
......@@ -4,8 +4,10 @@ import { slugifyWithHyphens } from '../lib/utils/text_utility';
let hasUserDefinedProjectPath = false;
const deriveProjectPathFromUrl = ($projectImportUrl) => {
const $currentProjectPath = $projectImportUrl.parents('.toggle-import-form').find('#project_path');
const deriveProjectPathFromUrl = $projectImportUrl => {
const $currentProjectPath = $projectImportUrl
.parents('.toggle-import-form')
.find('#project_path');
if (hasUserDefinedProjectPath) {
return;
}
......@@ -52,9 +54,11 @@ const bindEvents = () => {
return;
}
$('.how_to_import_link').on('click', (e) => {
$('.how_to_import_link').on('click', e => {
e.preventDefault();
$(e.currentTarget).next('.modal').show();
$(e.currentTarget)
.next('.modal')
.show();
});
$('.modal-header .close').on('click', () => {
......@@ -63,15 +67,21 @@ const bindEvents = () => {
$('.btn_import_gitlab_project').on('click', () => {
const importHref = $('a.btn_import_gitlab_project').attr('href');
$('.btn_import_gitlab_project')
.attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&name=${$projectName.val()}&path=${$projectPath.val()}`);
$('.btn_import_gitlab_project').attr(
'href',
`${importHref}?namespace_id=${$(
'#project_namespace_id',
).val()}&name=${$projectName.val()}&path=${$projectPath.val()}`,
);
});
if ($pushNewProjectTipTrigger) {
$pushNewProjectTipTrigger
.removeAttr('rel')
.removeAttr('target')
.on('click', (e) => { e.preventDefault(); })
.on('click', e => {
e.preventDefault();
})
.popover({
title: $pushNewProjectTipTrigger.data('title'),
placement: 'bottom',
......@@ -79,13 +89,15 @@ const bindEvents = () => {
content: $('.push-new-project-tip-template').html(),
})
.on('shown.bs.popover', () => {
$(document).on('click.popover touchstart.popover', (event) => {
$(document).on('click.popover touchstart.popover', event => {
if ($(event.target).closest('.popover').length === 0) {
$pushNewProjectTipTrigger.trigger('click');
}
});
const target = $(`#${$pushNewProjectTipTrigger.attr('aria-describedby')}`).find('.js-select-on-focus');
const target = $(`#${$pushNewProjectTipTrigger.attr('aria-describedby')}`).find(
'.js-select-on-focus',
);
addSelectOnFocusBehaviour(target);
target.focus();
......@@ -117,13 +129,15 @@ const bindEvents = () => {
const selectedTemplate = templates[value];
$selectedTemplateText.text(selectedTemplate.text);
$(selectedTemplate.icon).clone().addClass('d-block').appendTo($selectedIcon);
$(selectedTemplate.icon)
.clone()
.addClass('d-block')
.appendTo($selectedIcon);
const $activeTabProjectName = $('.tab-pane.active #project_name');
const $activeTabProjectPath = $('.tab-pane.active #project_path');
$activeTabProjectName.focus();
$activeTabProjectName
.keyup(() => {
$activeTabProjectName.keyup(() => {
onProjectNameChange($activeTabProjectName, $activeTabProjectPath);
hasUserDefinedProjectPath = $activeTabProjectPath.val().trim().length > 0;
});
......
......@@ -21,7 +21,7 @@ Sidebar.initialize = function(currentUser) {
}
};
Sidebar.prototype.removeListeners = function () {
Sidebar.prototype.removeListeners = function() {
this.sidebar.off('click', '.sidebar-collapsed-icon');
this.sidebar.off('hidden.gl.dropdown');
$('.dropdown').off('loading.gl.dropdown');
......@@ -38,10 +38,12 @@ Sidebar.prototype.addEventListeners = function() {
$('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
$document.on('click', '.js-sidebar-toggle', this.sidebarToggleClicked);
return $(document).off('click', '.js-issuable-todo').on('click', '.js-issuable-todo', this.toggleTodo);
return $(document)
.off('click', '.js-issuable-todo')
.on('click', '.js-issuable-todo', this.toggleTodo);
};
Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
Sidebar.prototype.sidebarToggleClicked = function(e, triggered) {
var $allGutterToggleIcons, $this, isExpanded, tooltipLabel;
e.preventDefault();
$this = $(this);
......@@ -51,18 +53,26 @@ Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
if (isExpanded) {
$allGutterToggleIcons.removeClass('fa-angle-double-right').addClass('fa-angle-double-left');
$('aside.right-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
$('.layout-page').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
$('aside.right-sidebar')
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed');
$('.layout-page')
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed');
} else {
$allGutterToggleIcons.removeClass('fa-angle-double-left').addClass('fa-angle-double-right');
$('aside.right-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
$('.layout-page').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
$('aside.right-sidebar')
.removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded');
$('.layout-page')
.removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded');
}
$this.attr('data-original-title', tooltipLabel);
if (!triggered) {
Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'));
Cookies.set('collapsed_gutter', $('.right-sidebar').hasClass('right-sidebar-collapsed'));
}
};
......@@ -71,21 +81,27 @@ Sidebar.prototype.toggleTodo = function(e) {
$this = $(e.currentTarget);
ajaxType = $this.attr('data-delete-path') ? 'delete' : 'post';
if ($this.attr('data-delete-path')) {
url = "" + ($this.attr('data-delete-path'));
url = '' + $this.attr('data-delete-path');
} else {
url = "" + ($this.data('url'));
url = '' + $this.data('url');
}
$this.tooltip('hide');
$('.js-issuable-todo').disable().addClass('is-loading');
$('.js-issuable-todo')
.disable()
.addClass('is-loading');
axios[ajaxType](url, {
issuable_id: $this.data('issuableId'),
issuable_type: $this.data('issuableType'),
}).then(({ data }) => {
})
.then(({ data }) => {
this.todoUpdateDone(data);
}).catch(() => flash(`There was an error ${ajaxType === 'post' ? 'adding a' : 'deleting the'} todo.`));
})
.catch(() =>
flash(`There was an error ${ajaxType === 'post' ? 'adding a' : 'deleting the'} todo.`),
);
};
Sidebar.prototype.todoUpdateDone = function(data) {
......@@ -99,7 +115,8 @@ Sidebar.prototype.todoUpdateDone = function(data) {
const $el = $(el);
const $elText = $el.find('.js-issuable-todo-inner');
$el.removeClass('is-loading')
$el
.removeClass('is-loading')
.enable()
.attr('aria-label', $el.data(`${attrPrefix}Text`))
.attr('data-delete-path', deletePath)
......@@ -119,7 +136,9 @@ Sidebar.prototype.todoUpdateDone = function(data) {
Sidebar.prototype.sidebarDropdownLoading = function(e) {
var $loading, $sidebarCollapsedIcon, i, img;
$sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon');
$sidebarCollapsedIcon = $(this)
.closest('.block')
.find('.sidebar-collapsed-icon');
img = $sidebarCollapsedIcon.find('img');
i = $sidebarCollapsedIcon.find('i');
$loading = $('<i class="fa fa-spinner fa-spin"></i>');
......@@ -134,7 +153,9 @@ Sidebar.prototype.sidebarDropdownLoading = function(e) {
Sidebar.prototype.sidebarDropdownLoaded = function(e) {
var $sidebarCollapsedIcon, i, img;
$sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon');
$sidebarCollapsedIcon = $(this)
.closest('.block')
.find('.sidebar-collapsed-icon');
img = $sidebarCollapsedIcon.find('img');
$sidebarCollapsedIcon.find('i.fa-spin').remove();
i = $sidebarCollapsedIcon.find('i');
......@@ -220,7 +241,7 @@ Sidebar.prototype.isOpen = function() {
};
Sidebar.prototype.getBlock = function(name) {
return this.sidebar.find(".block." + name);
return this.sidebar.find('.block.' + name);
};
export default Sidebar;
......@@ -226,7 +226,7 @@ export class SearchAutocomplete {
icon,
text: term,
template: s__('SearchAutocomplete|in all GitLab'),
url: `/search?search=${term}`,
url: `${gon.relative_url_root}/search?search=${term}`,
});
if (template) {
......@@ -234,7 +234,9 @@ export class SearchAutocomplete {
icon,
text: term,
template,
url: `/search?search=${term}&project_id=${this.projectInputEl.val()}&group_id=${this.groupInputEl.val()}`,
url: `${
gon.relative_url_root
}/search?search=${term}&project_id=${this.projectInputEl.val()}&group_id=${this.groupInputEl.val()}`,
});
}
}
......
......@@ -5,6 +5,7 @@ import Icon from '~/vue_shared/components/icon.vue';
import GfmAutoComplete from '~/gfm_auto_complete';
import { __, s__ } from '~/locale';
import Api from '~/api';
import { GlModal } from '@gitlab-org/gitlab-ui';
import eventHub from './event_hub';
import EmojiMenuInModal from './emoji_menu_in_modal';
......@@ -13,6 +14,7 @@ const emojiMenuClass = 'js-modal-status-emoji-menu';
export default {
components: {
Icon,
GlModal,
},
props: {
currentEmoji: {
......@@ -152,7 +154,7 @@ export default {
</script>
<template>
<gl-ui-modal
<gl-modal
:title="s__('SetStatusModal|Set a status')"
:modal-id="modalId"
:ok-title="s__('SetStatusModal|Set status')"
......@@ -237,5 +239,5 @@ export default {
</div>
</div>
</div>
</gl-ui-modal>
</gl-modal>
</template>
......@@ -74,8 +74,8 @@ export default {
}
if (!this.users.length) {
const emptyTooltipLabel = this.issuableType === 'issue' ?
__('Assignee(s)') : __('Assignee');
const emptyTooltipLabel =
this.issuableType === 'issue' ? __('Assignee(s)') : __('Assignee');
names.push(emptyTooltipLabel);
}
......@@ -248,4 +248,3 @@ export default {
</div>
</div>
</template>
<script>
import { __ } from '~/locale';
import icon from '~/vue_shared/components/icon.vue';
import toggleButton from '~/vue_shared/components/toggle_button.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import eventHub from '../../event_hub';
import { __ } from '~/locale';
import icon from '~/vue_shared/components/icon.vue';
import toggleButton from '~/vue_shared/components/toggle_button.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import eventHub from '../../event_hub';
const ICON_ON = 'notifications';
const ICON_OFF = 'notifications-off';
const LABEL_ON = __('Notifications on');
const LABEL_OFF = __('Notifications off');
const ICON_ON = 'notifications';
const ICON_OFF = 'notifications-off';
const LABEL_ON = __('Notifications on');
const LABEL_OFF = __('Notifications off');
export default {
export default {
directives: {
tooltip,
},
......@@ -68,7 +68,7 @@
this.$emit('toggleSidebar');
},
},
};
};
</script>
<template>
......
......@@ -39,9 +39,10 @@ export default class SidebarMediator {
}
fetch() {
return this.service.get()
return this.service
.get()
.then(response => response.json())
.then((data) => {
.then(data => {
this.processFetchedData(data);
})
.catch(() => new Flash('Error occurred when fetching sidebar data'));
......@@ -56,30 +57,33 @@ export default class SidebarMediator {
toggleSubscription() {
this.store.setFetchingState('subscriptions', true);
return this.service.toggleSubscription()
return this.service
.toggleSubscription()
.then(() => {
this.store.setSubscribedState(!this.store.subscribed);
this.store.setFetchingState('subscriptions', false);
})
.catch((err) => {
.catch(err => {
this.store.setFetchingState('subscriptions', false);
throw err;
});
}
fetchAutocompleteProjects(searchTerm) {
return this.service.getProjectsAutocomplete(searchTerm)
return this.service
.getProjectsAutocomplete(searchTerm)
.then(response => response.json())
.then((data) => {
.then(data => {
this.store.setAutocompleteProjects(data);
return this.store.autocompleteProjects;
});
}
moveIssue() {
return this.service.moveIssue(this.store.moveToProjectId)
return this.service
.moveIssue(this.store.moveToProjectId)
.then(response => response.json())
.then((data) => {
.then(data => {
if (window.location.pathname !== data.web_url) {
visitUrl(data.web_url);
}
......
<script>
/* eslint-disable vue/require-default-prop */
import { sprintf, __ } from '~/locale';
import PipelineStage from '~/pipelines/components/stage.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
......@@ -36,6 +37,10 @@ export default {
type: String,
required: false,
},
troubleshootingDocsPath: {
type: String,
required: true,
},
},
computed: {
hasPipeline() {
......@@ -57,6 +62,17 @@ export default {
hasCommitInfo() {
return this.pipeline.commit && Object.keys(this.pipeline.commit).length > 0;
},
errorText() {
return sprintf(
__(
'Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}',
),
{
linkStart: `<a href="${this.troubleshootingDocsPath}">`,
linkEnd: '</a>',
},
);
},
},
};
</script>
......@@ -77,8 +93,10 @@ export default {
name="status_failed_borderless"
/>
</div>
<div class="media-body">
Could not connect to the CI server. Please check your settings and try again
<div
class="media-body"
v-html="errorText"
>
</div>
</template>
<template v-else-if="hasPipeline">
......
......@@ -71,7 +71,12 @@ export default {
return defaultClass;
},
iconClass() {
if (this.status === 'failed' || !this.commitMessage.length || !this.mr.isMergeAllowed || this.mr.preventMerge) {
if (
this.status === 'failed' ||
!this.commitMessage.length ||
!this.mr.isMergeAllowed ||
this.mr.preventMerge
) {
return 'warning';
}
return 'success';
......@@ -90,10 +95,12 @@ export default {
},
isMergeButtonDisabled() {
const { commitMessage } = this;
return Boolean(!commitMessage.length
|| !this.shouldShowMergeControls()
|| this.isMakingRequest
|| this.mr.preventMerge);
return Boolean(
!commitMessage.length ||
!this.shouldShowMergeControls() ||
this.isMakingRequest ||
this.mr.preventMerge,
);
},
isRemoveSourceBranchButtonDisabled() {
return this.isMergeButtonDisabled;
......@@ -140,9 +147,10 @@ export default {
};
this.isMakingRequest = true;
this.service.merge(options)
this.service
.merge(options)
.then(res => res.data)
.then((data) => {
.then(data => {
const hasError = data.status === 'failed' || data.status === 'hook_validation_error';
if (data.status === 'merge_when_pipeline_succeeds') {
......@@ -167,9 +175,10 @@ export default {
});
},
handleMergePolling(continuePolling, stopPolling) {
this.service.poll()
this.service
.poll()
.then(res => res.data)
.then((data) => {
.then(data => {
if (data.state === 'merged') {
// If state is merged we should update the widget and stop the polling
eventHub.$emit('MRWidgetUpdateRequested');
......@@ -205,9 +214,10 @@ export default {
});
},
handleRemoveBranchPolling(continuePolling, stopPolling) {
this.service.poll()
this.service
.poll()
.then(res => res.data)
.then((data) => {
.then(data => {
// If source branch exists then we should continue polling
// because removing a source branch is a background task and takes time
if (data.source_branch_exists) {
......
......@@ -116,7 +116,7 @@ export default {
// init polling
this.initPostMergeDeploymentsPolling();
}
}
},
},
created() {
this.initPolling();
......@@ -213,7 +213,7 @@ export default {
})
.catch(() => this.throwDeploymentsError());
},
fetchPostMergeDeployments(){
fetchPostMergeDeployments() {
return this.fetchDeployments('merge_commit')
.then(({ data }) => {
if (data.length) {
......@@ -223,7 +223,11 @@ export default {
.catch(() => this.throwDeploymentsError());
},
throwDeploymentsError() {
createFlash(__('Something went wrong while fetching the environments for this merge request. Please try again.'));
createFlash(
__(
'Something went wrong while fetching the environments for this merge request. Please try again.',
),
);
},
fetchActionsContent() {
this.service
......@@ -301,6 +305,7 @@ export default {
:has-ci="mr.hasCI"
:source-branch="mr.sourceBranch"
:source-branch-link="mr.sourceBranchLink"
:troubleshooting-docs-path="mr.troubleshootingDocsPath"
/>
<deployment
v-for="deployment in mr.deployments"
......@@ -355,6 +360,7 @@ export default {
:has-ci="mr.hasCI"
:source-branch="mr.targetBranch"
:source-branch-link="mr.targetBranch"
:troubleshooting-docs-path="mr.troubleshootingDocsPath"
/>
<deployment
v-for="postMergeDeployment in mr.postMergeDeployments"
......
......@@ -24,8 +24,8 @@ export default class MRWidgetService {
fetchDeployments(targetParam) {
return axios.get(this.endpoints.ciEnvironmentsStatusPath, {
params: {
environment_target: targetParam
}
environment_target: targetParam,
},
});
}
......
......@@ -18,6 +18,7 @@ export default class MergeRequestStore {
this.squash = data.squash;
this.squashBeforeMergeHelpPath =
this.squashBeforeMergeHelpPath || data.squash_before_merge_help_path;
this.troubleshootingDocsPath = this.troubleshootingDocsPath || data.troubleshooting_docs_path;
this.enableSquashBeforeMerge = this.enableSquashBeforeMerge || true;
this.iid = data.iid;
......
......@@ -13,7 +13,7 @@ export default {
},
props: {
/**
* Indicates the existance of a tag.
* Indicates the existence of a tag.
* Used to render the correct icon, if true will render `fa-tag` icon,
* if false will render a svg sprite fork icon
*/
......
......@@ -56,12 +56,14 @@ export default {
filteredResults() {
if (this.filter !== '') {
return this.items.filter(
item => item[this.filterKey] && item[this.filterKey].toLowerCase().includes(this.filter.toLowerCase()),
item =>
item[this.filterKey] &&
item[this.filterKey].toLowerCase().includes(this.filter.toLowerCase()),
);
}
return this.items.slice(0, this.visibleItems);
}
},
},
mounted() {
/**
......
......@@ -8,7 +8,7 @@ let iconValidator = () => true;
*/
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line global-require
const data = require('@gitlab-org/gitlab-svgs/dist/icons.json');
const data = require('@gitlab/svgs/dist/icons.json');
const { icons } = data;
iconValidator = value => {
if (icons.includes(value)) {
......
<script>
import $ from 'jquery';
import Tooltip from '../../directives/tooltip';
import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import ToolbarButton from './toolbar_button.vue';
import Icon from '../icon.vue';
export default {
directives: {
Tooltip,
},
components: {
ToolbarButton,
Icon,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
previewMarkdown: {
type: Boolean,
......@@ -147,7 +147,7 @@ export default {
icon="table"
/>
<button
v-tooltip
v-gl-tooltip
aria-label="Go full screen"
class="toolbar-btn toolbar-fullscreen-btn js-zen-enter"
data-container="body"
......
<script>
import tooltip from '../../directives/tooltip';
import icon from '../icon.vue';
import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import Icon from '../icon.vue';
export default {
components: {
icon,
Icon,
},
directives: {
tooltip,
GlTooltip: GlTooltipDirective,
},
props: {
buttonTitle: {
......@@ -43,7 +43,7 @@ export default {
<template>
<button
v-tooltip
v-gl-tooltip
:data-md-tag="tag"
:data-md-select="tagSelect"
:data-md-block="tagBlock"
......
<script>
import { GlPagination } from '@gitlab-org/gitlab-ui';
import { s__ } from '../../locale';
export default {
components: {
GlPagination,
},
props: {
change: {
type: Function,
......
<script>
import tooltip from '~/vue_shared/directives/tooltip';
import tooltip from '~/vue_shared/directives/tooltip';
export default {
export default {
name: 'CollapsedCalendarIcon',
directives: {
tooltip,
......@@ -33,7 +33,7 @@
this.$emit('click');
},
},
};
};
</script>
<template>
......
<script>
import { __ } from '~/locale';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { dateInWords, timeFor } from '~/lib/utils/datetime_utility';
import collapsedCalendarIcon from './collapsed_calendar_icon.vue';
import { __ } from '~/locale';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { dateInWords, timeFor } from '~/lib/utils/datetime_utility';
import collapsedCalendarIcon from './collapsed_calendar_icon.vue';
export default {
export default {
name: 'SidebarCollapsedGroupedDatePicker',
components: {
collapsedCalendarIcon,
},
mixins: [
timeagoMixin,
],
mixins: [timeagoMixin],
props: {
collapsed: {
type: Boolean,
......@@ -67,10 +65,7 @@
const defaultText = dateType === 'min' ? __('Start date') : __('Due date');
const date = this[`${dateType}Date`];
const timeAgo = dateType === 'min' ? this.timeFormated(date) : timeFor(date);
const dateText = date ? [
this.dateText(dateType),
`(${timeAgo})`,
].join(' ') : '';
const dateText = date ? [this.dateText(dateType), `(${timeAgo})`].join(' ') : '';
if (date) {
return [defaultText, dateText].join('<br />');
......@@ -78,7 +73,7 @@
return __('Start and due date');
},
},
};
};
</script>
<template>
......
......@@ -14,7 +14,10 @@ export default {
},
computed: {
labelsList() {
const labelsString = this.labels.slice(0, 5).map(label => label.title).join(', ');
const labelsString = this.labels
.slice(0, 5)
.map(label => label.title)
.join(', ');
if (this.labels.length > 5) {
return sprintf(s__('LabelSelect|%{labelsString}, and %{remainingLabelCount} more'), {
......
......@@ -158,7 +158,7 @@
color: $gl-text-color;
outline: 0;
// make sure the text color is not overriden
// make sure the text color is not overridden
&.text-danger {
color: $brand-danger;
}
......@@ -184,7 +184,7 @@
text-align: left;
width: 100%;
// make sure the text color is not overriden
// make sure the text color is not overridden
&.text-danger {
color: $brand-danger;
}
......
// For tabbed navigation links, scrolling tabs, etc. For all top/main navigation,
// please check nav.scss
.nav-links {
.nav-links:not(.quick-links) {
display: flex;
padding: 0;
margin: 0;
......@@ -106,7 +106,7 @@
display: inline-block;
float: right;
text-align: right;
padding: 11px 0;
padding: $gl-padding-8 0;
margin-bottom: 0;
> .btn,
......
......@@ -33,21 +33,21 @@ class Admin::AppearancesController < Admin::ApplicationController
@appearance.save
redirect_to admin_appearances_path, notice: 'Logo was succesfully removed.'
redirect_to admin_appearances_path, notice: 'Logo was successfully removed.'
end
def header_logos
@appearance.remove_header_logo!
@appearance.save
redirect_to admin_appearances_path, notice: 'Header logo was succesfully removed.'
redirect_to admin_appearances_path, notice: 'Header logo was successfully removed.'
end
def favicon
@appearance.remove_favicon!
@appearance.save
redirect_to admin_appearances_path, notice: 'Favicon was succesfully removed.'
redirect_to admin_appearances_path, notice: 'Favicon was successfully removed.'
end
private
......
......@@ -61,7 +61,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
format.html do
usage_data_json = JSON.pretty_generate(Gitlab::UsageData.data)
render html: Gitlab::Highlight.highlight('payload.json', usage_data_json)
render html: Gitlab::Highlight.highlight('payload.json', usage_data_json, language: 'json')
end
format.json { render json: Gitlab::UsageData.to_json }
end
......
......@@ -23,7 +23,7 @@ class Import::GiteaController < Import::GithubController
:"#{provider}_host_url"
end
# Overriden methods
# Overridden methods
def provider
:gitea
end
......
......@@ -103,7 +103,7 @@ class Import::GithubController < Import::BaseController
{ github_access_token: session[access_token_key] }
end
# The following methods are overriden in subclasses
# The following methods are overridden in subclasses
def provider
:github
end
......
......@@ -3,7 +3,7 @@
class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
layout 'profile'
# Overriden from Doorkeeper::AuthorizationsController to
# Overridden from Doorkeeper::AuthorizationsController to
# include the call to session.delete
def new
if pre_auth.authorizable?
......
......@@ -92,7 +92,7 @@ class Projects::BlobController < Projects::ApplicationController
apply_diff_view_cookie!
@blob.load_all_data!
@lines = Gitlab::Highlight.highlight(@blob.path, @blob.data, repository: @repository).lines
@lines = @blob.present.highlight.lines
@form = UnfoldForm.new(params)
......
......@@ -276,7 +276,7 @@ class ProjectsController < Projects::ApplicationController
# rubocop: enable CodeReuse/ActiveRecord
# Render project landing depending of which features are available
# So if page is not availble in the list it renders the next page
# So if page is not available in the list it renders the next page
#
# pages list order: repository readme, wiki home, issues list, customize workflow
def render_landing_page
......
......@@ -72,7 +72,6 @@ module Autocomplete
author_id.present? && current_user
end
# rubocop: disable CodeReuse/ActiveRecord
def find_users
if project
project.authorized_users.union_with_user(author_id)
......@@ -84,6 +83,5 @@ module Autocomplete
User.none
end
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment