Commit 15483879 authored by Nick Thomas's avatar Nick Thomas

Merge remote-tracking branch 'ce/master' into nt/ce-to-ee-thursday

parents ccbd665a f96e1bf1
......@@ -264,7 +264,6 @@ rake karma:
cache:
paths:
- vendor/ruby
- node_modules
stage: test
<<: *use-db
<<: *dedicated-runner
......@@ -363,9 +362,6 @@ coverage:
lint:javascript:
<<: *dedicated-runner
cache:
paths:
- node_modules/
stage: test
before_script: []
script:
......@@ -373,9 +369,6 @@ lint:javascript:
lint:javascript:report:
<<: *dedicated-runner
cache:
paths:
- node_modules/
stage: post-test
before_script: []
script:
......
import CANCELED_SVG from 'icons/_icon_status_canceled_borderless.svg';
import CREATED_SVG from 'icons/_icon_status_created_borderless.svg';
import FAILED_SVG from 'icons/_icon_status_failed_borderless.svg';
import MANUAL_SVG from 'icons/_icon_status_manual_borderless.svg';
import PENDING_SVG from 'icons/_icon_status_pending_borderless.svg';
import RUNNING_SVG from 'icons/_icon_status_running_borderless.svg';
import SKIPPED_SVG from 'icons/_icon_status_skipped_borderless.svg';
import SUCCESS_SVG from 'icons/_icon_status_success_borderless.svg';
import WARNING_SVG from 'icons/_icon_status_warning_borderless.svg';
const StatusIconEntityMap = {
icon_status_canceled: CANCELED_SVG,
icon_status_created: CREATED_SVG,
icon_status_failed: FAILED_SVG,
icon_status_manual: MANUAL_SVG,
icon_status_pending: PENDING_SVG,
icon_status_running: RUNNING_SVG,
icon_status_skipped: SKIPPED_SVG,
icon_status_success: SUCCESS_SVG,
icon_status_warning: WARNING_SVG,
};
export {
CANCELED_SVG,
CREATED_SVG,
FAILED_SVG,
MANUAL_SVG,
PENDING_SVG,
RUNNING_SVG,
SKIPPED_SVG,
SUCCESS_SVG,
WARNING_SVG,
StatusIconEntityMap as default,
};
......@@ -19,7 +19,11 @@ const ResolveBtn = Vue.extend({
data: function () {
return {
discussions: CommentsStore.state,
<<<<<<< HEAD
loading: false,
=======
loading: false
>>>>>>> ce/master
};
},
watch: {
......
......@@ -156,13 +156,13 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'projects:milestones:new':
case 'projects:milestones:edit':
case 'projects:milestones:update':
case 'groups:milestones:new':
case 'groups:milestones:edit':
case 'groups:milestones:update':
new ZenMode();
new gl.DueDateSelectors();
new gl.GLForm($('.milestone-form'));
break;
case 'groups:milestones:new':
new ZenMode();
break;
case 'projects:compare:show':
new gl.Diff();
break;
......@@ -389,9 +389,14 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'admin':
new Admin();
switch (path[1]) {
<<<<<<< HEAD
case 'application_settings':
case 'cohorts':
new gl.ApplicationSettings();
=======
case 'cohorts':
new gl.UsagePing();
>>>>>>> ce/master
break;
case 'groups':
new UsersSelect();
......
......@@ -2,10 +2,12 @@ const DATA_TRIGGER = 'data-dropdown-trigger';
const DATA_DROPDOWN = 'data-dropdown';
const SELECTED_CLASS = 'droplab-item-selected';
const ACTIVE_CLASS = 'droplab-item-active';
const IGNORE_CLASS = 'droplab-item-ignore';
export {
DATA_TRIGGER,
DATA_DROPDOWN,
SELECTED_CLASS,
ACTIVE_CLASS,
IGNORE_CLASS,
};
/* eslint-disable */
import utils from './utils';
import { SELECTED_CLASS } from './constants';
import { SELECTED_CLASS, IGNORE_CLASS } from './constants';
var DropDown = function(list) {
this.currentIndex = 0;
......@@ -36,6 +36,7 @@ Object.assign(DropDown.prototype, {
clickEvent: function(e) {
if (e.target.tagName === 'UL') return;
if (e.target.classList.contains(IGNORE_CLASS)) return;
var selected = utils.closest(e.target, 'LI');
if (!selected) return;
......
......@@ -38,6 +38,9 @@ window.DropzoneInput = (function() {
"opacity": 0,
"display": "none"
});
if (!project_uploads_path) return;
dropzone = form_dropzone.dropzone({
url: project_uploads_path,
dictDefaultMessage: "",
......@@ -133,8 +136,11 @@ window.DropzoneInput = (function() {
const textarea = child.get(0);
caretStart = textarea.selectionStart;
caretEnd = textarea.selectionEnd;
<<<<<<< HEAD
caretStart = textarea.selectionStart;
caretEnd = textarea.selectionEnd;
=======
>>>>>>> ce/master
textEnd = $(child).val().length;
beforeSelection = $(child).val().substring(0, caretStart);
afterSelection = $(child).val().substring(caretEnd, textEnd);
......
<script>
/**
* Renders the external url link in environments table.
*/
......@@ -5,7 +6,7 @@ export default {
props: {
externalUrl: {
type: String,
default: '',
required: true,
},
},
......@@ -14,17 +15,19 @@ export default {
return 'Open';
},
},
template: `
<a
class="btn external-url has-tooltip"
data-container="body"
:href="externalUrl"
target="_blank"
rel="noopener noreferrer nofollow"
:title="title"
:aria-label="title">
<i class="fa fa-external-link" aria-hidden="true"></i>
</a>
`,
};
</script>
<template>
<a
class="btn external-url has-tooltip"
data-container="body"
target="_blank"
rel="noopener noreferrer nofollow"
:title="title"
:aria-label="title"
:href="externalUrl">
<i
class="fa fa-external-link"
aria-hidden="true" />
</a>
</template>
......@@ -7,10 +7,10 @@
import Timeago from 'timeago.js';
import '../../lib/utils/text_utility';
import ActionsComponent from './environment_actions';
import ExternalUrlComponent from './environment_external_url';
import StopComponent from './environment_stop';
import ExternalUrlComponent from './environment_external_url.vue';
import StopComponent from './environment_stop.vue';
import RollbackComponent from './environment_rollback';
import TerminalButtonComponent from './environment_terminal_button';
import TerminalButtonComponent from './environment_terminal_button.vue';
import MonitoringButtonComponent from './environment_monitoring';
import CommitComponent from '../../vue_shared/components/commit';
import eventHub from '../event_hub';
......
......@@ -21,7 +21,6 @@ export default {
class="btn monitoring-url has-tooltip"
data-container="body"
:href="monitoringUrl"
target="_blank"
rel="noopener noreferrer nofollow"
:title="title"
:aria-label="title">
......
<script>
/* global Flash */
/* eslint-disable no-new, no-alert */
/**
......@@ -51,17 +52,23 @@ export default {
}
},
},
template: `
<button type="button"
class="btn stop-env-link has-tooltip"
data-container="body"
@click="onClick"
:disabled="isLoading"
:title="title"
:aria-label="title">
<i class="fa fa-stop stop-env-icon" aria-hidden="true"></i>
<i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</button>
`,
};
</script>
<template>
<button
type="button"
class="btn stop-env-link has-tooltip"
data-container="body"
@click="onClick"
:disabled="isLoading"
:title="title"
:aria-label="title">
<i
class="fa fa-stop stop-env-icon"
aria-hidden="true" />
<i
v-if="isLoading"
class="fa fa-spinner fa-spin"
aria-hidden="true" />
</button>
</template>
<script>
/**
* Renders a terminal button to open a web terminal.
* Used in environments table.
......@@ -24,14 +25,15 @@ export default {
return 'Terminal';
},
},
template: `
<a class="btn terminal-button has-tooltip"
data-container="body"
:title="title"
:aria-label="title"
:href="terminalPath">
${terminalIconSvg}
</a>
`,
};
</script>
<template>
<a
class="btn terminal-button has-tooltip"
data-container="body"
:title="title"
:aria-label="title"
:href="terminalPath"
v-html="terminalIconSvg">
</a>
</template>
......@@ -2,82 +2,80 @@ import Filter from '~/droplab/plugins/filter';
require('./filtered_search_dropdown');
(() => {
class DropdownHint extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter);
this.config = {
Filter: {
template: 'hint',
filterFunction: gl.DropdownUtils.filterHint.bind(null, input),
},
};
}
itemClicked(e) {
const { selected } = e.detail;
class DropdownHint extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter);
this.config = {
Filter: {
template: 'hint',
filterFunction: gl.DropdownUtils.filterHint.bind(null, input),
},
};
}
if (selected.tagName === 'LI') {
if (selected.hasAttribute('data-value')) {
this.dismissDropdown();
} else if (selected.getAttribute('data-action') === 'submit') {
this.dismissDropdown();
this.dispatchFormSubmitEvent();
} else {
const token = selected.querySelector('.js-filter-hint').innerText.trim();
const tag = selected.querySelector('.js-filter-tag').innerText.trim();
itemClicked(e) {
const { selected } = e.detail;
if (tag.length) {
// Get previous input values in the input field and convert them into visual tokens
const previousInputValues = this.input.value.split(' ');
const searchTerms = [];
if (selected.tagName === 'LI') {
if (selected.hasAttribute('data-value')) {
this.dismissDropdown();
} else if (selected.getAttribute('data-action') === 'submit') {
this.dismissDropdown();
this.dispatchFormSubmitEvent();
} else {
const token = selected.querySelector('.js-filter-hint').innerText.trim();
const tag = selected.querySelector('.js-filter-tag').innerText.trim();
previousInputValues.forEach((value, index) => {
searchTerms.push(value);
if (tag.length) {
// Get previous input values in the input field and convert them into visual tokens
const previousInputValues = this.input.value.split(' ');
const searchTerms = [];
if (index === previousInputValues.length - 1
&& token.indexOf(value.toLowerCase()) !== -1) {
searchTerms.pop();
}
});
previousInputValues.forEach((value, index) => {
searchTerms.push(value);
if (searchTerms.length > 0) {
gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms.join(' '));
if (index === previousInputValues.length - 1
&& token.indexOf(value.toLowerCase()) !== -1) {
searchTerms.pop();
}
});
gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''), '', false, this.container);
if (searchTerms.length > 0) {
gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms.join(' '));
}
this.dismissDropdown();
this.dispatchInputEvent();
gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''), '', false, this.container);
}
this.dismissDropdown();
this.dispatchInputEvent();
}
}
}
renderContent() {
const dropdownData = [];
renderContent() {
const dropdownData = [];
[].forEach.call(this.input.closest('.filtered-search-box-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
const { icon, hint, tag, type } = dropdownMenu.dataset;
if (icon && hint && tag) {
dropdownData.push(
Object.assign({
icon: `fa-${icon}`,
hint,
tag: `&lt;${tag}&gt;`,
}, type && { type }),
);
}
});
[].forEach.call(this.input.closest('.filtered-search-box-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
const { icon, hint, tag, type } = dropdownMenu.dataset;
if (icon && hint && tag) {
dropdownData.push(
Object.assign({
icon: `fa-${icon}`,
hint,
tag: `&lt;${tag}&gt;`,
}, type && { type }),
);
}
});
this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config);
this.droplab.setData(this.hookId, dropdownData);
}
this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config);
this.droplab.setData(this.hookId, dropdownData);
}
init() {
this.droplab.addHook(this.input, this.dropdown, [Filter], this.config).init();
}
init() {
this.droplab.addHook(this.input, this.dropdown, [Filter], this.config).init();
}
}
window.gl = window.gl || {};
gl.DropdownHint = DropdownHint;
})();
window.gl = window.gl || {};
gl.DropdownHint = DropdownHint;
......@@ -5,48 +5,46 @@ import Filter from '~/droplab/plugins/filter';
require('./filtered_search_dropdown');
(() => {
class DropdownNonUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter, endpoint, symbol) {
super(droplab, dropdown, input, filter);
this.symbol = symbol;
this.config = {
Ajax: {
endpoint,
method: 'setData',
loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
/* eslint-enable no-new */
},
class DropdownNonUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter, endpoint, symbol) {
super(droplab, dropdown, input, filter);
this.symbol = symbol;
this.config = {
Ajax: {
endpoint,
method: 'setData',
loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
/* eslint-enable no-new */
},
Filter: {
filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input),
template: 'title',
},
};
}
},
Filter: {
filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input),
template: 'title',
},
};
}
itemClicked(e) {
super.itemClicked(e, (selected) => {
const title = selected.querySelector('.js-data-value').innerText.trim();
return `${this.symbol}${gl.DropdownUtils.getEscapedText(title)}`;
});
}
itemClicked(e) {
super.itemClicked(e, (selected) => {
const title = selected.querySelector('.js-data-value').innerText.trim();
return `${this.symbol}${gl.DropdownUtils.getEscapedText(title)}`;
});
}
renderContent(forceShowList = false) {
this.droplab
.changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
super.renderContent(forceShowList);
}
renderContent(forceShowList = false) {
this.droplab
.changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
super.renderContent(forceShowList);
}
init() {
this.droplab
.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
}
init() {
this.droplab
.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
}
}
window.gl = window.gl || {};
gl.DropdownNonUser = DropdownNonUser;
})();
window.gl = window.gl || {};
gl.DropdownNonUser = DropdownNonUser;
......@@ -4,69 +4,67 @@ import AjaxFilter from '~/droplab/plugins/ajax_filter';
require('./filtered_search_dropdown');
(() => {
class DropdownUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter);
this.config = {
AjaxFilter: {
endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
searchKey: 'search',
params: {
per_page: 20,
active: true,
project_id: this.getProjectId(),
current_user: true,
},
searchValueFunction: this.getSearchInput.bind(this),
loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
/* eslint-enable no-new */
},
class DropdownUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter);
this.config = {
AjaxFilter: {
endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
searchKey: 'search',
params: {
per_page: 20,
active: true,
project_id: this.getProjectId(),
current_user: true,
},
};
}
itemClicked(e) {
super.itemClicked(e,
selected => selected.querySelector('.dropdown-light-content').innerText.trim());
}
renderContent(forceShowList = false) {
this.droplab.changeHookList(this.hookId, this.dropdown, [AjaxFilter], this.config);
super.renderContent(forceShowList);
}
searchValueFunction: this.getSearchInput.bind(this),
loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
/* eslint-enable no-new */
},
},
};
}
getProjectId() {
return this.input.getAttribute('data-project-id');
}
itemClicked(e) {
super.itemClicked(e,
selected => selected.querySelector('.dropdown-light-content').innerText.trim());
}
getSearchInput() {
const query = gl.DropdownUtils.getSearchInput(this.input);
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
renderContent(forceShowList = false) {
this.droplab.changeHookList(this.hookId, this.dropdown, [AjaxFilter], this.config);
super.renderContent(forceShowList);
}
let value = lastToken || '';
getProjectId() {
return this.input.getAttribute('data-project-id');
}
if (value[0] === '@') {
value = value.slice(1);
}
getSearchInput() {
const query = gl.DropdownUtils.getSearchInput(this.input);
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
// Removes the first character if it is a quotation so that we can search
// with multiple words
if (value[0] === '"' || value[0] === '\'') {
value = value.slice(1);
}
let value = lastToken || '';
return value;
if (value[0] === '@') {
value = value.slice(1);
}
init() {
this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init();
// Removes the first character if it is a quotation so that we can search
// with multiple words
if (value[0] === '"' || value[0] === '\'') {
value = value.slice(1);
}
return value;
}
init() {
this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init();
}
}
window.gl = window.gl || {};
gl.DropdownUser = DropdownUser;
})();
window.gl = window.gl || {};
gl.DropdownUser = DropdownUser;
(() => {
const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger';
class FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
this.droplab = droplab;
this.hookId = input && input.id;
this.input = input;
this.filter = filter;
this.dropdown = dropdown;
this.loadingTemplate = `<div class="filter-dropdown-loading">
<i class="fa fa-spinner fa-spin"></i>
</div>`;
this.bindEvents();
}
bindEvents() {
this.itemClickedWrapper = this.itemClicked.bind(this);
this.dropdown.addEventListener('click.dl', this.itemClickedWrapper);
}
const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger';
class FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
this.droplab = droplab;
this.hookId = input && input.id;
this.input = input;
this.filter = filter;
this.dropdown = dropdown;
this.loadingTemplate = `<div class="filter-dropdown-loading">
<i class="fa fa-spinner fa-spin"></i>
</div>`;
this.bindEvents();
}
unbindEvents() {
this.dropdown.removeEventListener('click.dl', this.itemClickedWrapper);
}
bindEvents() {
this.itemClickedWrapper = this.itemClicked.bind(this);
this.dropdown.addEventListener('click.dl', this.itemClickedWrapper);
}
getCurrentHook() {
return this.droplab.hooks.filter(h => h.id === this.hookId)[0] || null;
}
unbindEvents() {
this.dropdown.removeEventListener('click.dl', this.itemClickedWrapper);
}
itemClicked(e, getValueFunction) {
const { selected } = e.detail;
getCurrentHook() {
return this.droplab.hooks.filter(h => h.id === this.hookId)[0] || null;
}
if (selected.tagName === 'LI' && selected.innerHTML) {
const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(this.filter, selected);
itemClicked(e, getValueFunction) {
const { selected } = e.detail;
if (!dataValueSet) {
const value = getValueFunction(selected);
gl.FilteredSearchDropdownManager.addWordToInput(this.filter, value, true);
}
if (selected.tagName === 'LI' && selected.innerHTML) {
const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(this.filter, selected);
this.resetFilters();
this.dismissDropdown();
this.dispatchInputEvent();
if (!dataValueSet) {
const value = getValueFunction(selected);
gl.FilteredSearchDropdownManager.addWordToInput(this.filter, value, true);
}
}
setAsDropdown() {
this.input.setAttribute(DATA_DROPDOWN_TRIGGER, `#${this.dropdown.id}`);
this.resetFilters();
this.dismissDropdown();
this.dispatchInputEvent();
}
}
setOffset(offset = 0) {
if (window.innerWidth > 480) {
this.dropdown.style.left = `${offset}px`;
} else {
this.dropdown.style.left = '0px';
}
setAsDropdown() {
this.input.setAttribute(DATA_DROPDOWN_TRIGGER, `#${this.dropdown.id}`);
}
setOffset(offset = 0) {
if (window.innerWidth > 480) {
this.dropdown.style.left = `${offset}px`;
} else {
this.dropdown.style.left = '0px';
}
}
renderContent(forceShowList = false) {
const currentHook = this.getCurrentHook();
if (forceShowList && currentHook && currentHook.list.hidden) {
currentHook.list.show();
}
renderContent(forceShowList = false) {
const currentHook = this.getCurrentHook();
if (forceShowList && currentHook && currentHook.list.hidden) {
currentHook.list.show();
}
}
render(forceRenderContent = false, forceShowList = false) {
this.setAsDropdown();
render(forceRenderContent = false, forceShowList = false) {
this.setAsDropdown();
const currentHook = this.getCurrentHook();
const firstTimeInitialized = currentHook === null;
const currentHook = this.getCurrentHook();
const firstTimeInitialized = currentHook === null;
if (firstTimeInitialized || forceRenderContent) {
this.renderContent(forceShowList);
} else if (currentHook.list.list.id !== this.dropdown.id) {
this.renderContent(forceShowList);
}
if (firstTimeInitialized || forceRenderContent) {
this.renderContent(forceShowList);
} else if (currentHook.list.list.id !== this.dropdown.id) {
this.renderContent(forceShowList);
}
}
dismissDropdown() {
// Focusing on the input will dismiss dropdown
// (default droplab functionality)
this.input.focus();
}
dismissDropdown() {
// Focusing on the input will dismiss dropdown
// (default droplab functionality)
this.input.focus();
}
dispatchInputEvent() {
// Propogate input change to FilteredSearchDropdownManager
// so that it can determine which dropdowns to open
this.input.dispatchEvent(new CustomEvent('input', {
bubbles: true,
cancelable: true,
}));
}
dispatchInputEvent() {
// Propogate input change to FilteredSearchDropdownManager
// so that it can determine which dropdowns to open
this.input.dispatchEvent(new CustomEvent('input', {
bubbles: true,
cancelable: true,
}));
}
dispatchFormSubmitEvent() {
// dispatchEvent() is necessary as form.submit() does not
// trigger event handlers
this.input.form.dispatchEvent(new Event('submit'));
}
dispatchFormSubmitEvent() {
// dispatchEvent() is necessary as form.submit() does not
// trigger event handlers
this.input.form.dispatchEvent(new Event('submit'));
}
hideDropdown() {
const currentHook = this.getCurrentHook();
if (currentHook) {
currentHook.list.hide();
}
hideDropdown() {
const currentHook = this.getCurrentHook();
if (currentHook) {
currentHook.list.hide();
}
}
<<<<<<< HEAD
resetFilters() {
const hook = this.getCurrentHook();
......@@ -119,9 +119,22 @@
});
hook.list.render(results);
}
=======
resetFilters() {
const hook = this.getCurrentHook();
if (hook) {
const data = hook.list.data || [];
const results = data.map((o) => {
const updated = o;
updated.droplab_hidden = false;
return updated;
});
hook.list.render(results);
>>>>>>> ce/master
}
}
}
window.gl = window.gl || {};
gl.FilteredSearchDropdown = FilteredSearchDropdown;
})();
window.gl = window.gl || {};
gl.FilteredSearchDropdown = FilteredSearchDropdown;
(() => {
const tokenKeys = [{
key: 'author',
type: 'string',
param: 'username',
symbol: '@',
}, {
key: 'assignee',
type: 'string',
param: 'username',
symbol: '@',
}, {
key: 'milestone',
type: 'string',
param: 'title',
symbol: '%',
}, {
key: 'label',
type: 'array',
param: 'name[]',
symbol: '~',
}];
const tokenKeys = [{
key: 'author',
type: 'string',
param: 'username',
symbol: '@',
}, {
key: 'assignee',
type: 'string',
param: 'username',
symbol: '@',
}, {
key: 'milestone',
type: 'string',
param: 'title',
symbol: '%',
}, {
key: 'label',
type: 'array',
param: 'name[]',
symbol: '~',
}];
const alternativeTokenKeys = [{
key: 'label',
type: 'string',
param: 'name',
symbol: '~',
}];
const alternativeTokenKeys = [{
key: 'label',
type: 'string',
param: 'name',
symbol: '~',
}];
const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys);
const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys);
const conditions = [{
url: 'assignee_id=0',
tokenKey: 'assignee',
value: 'none',
}, {
url: 'milestone_title=No+Milestone',
tokenKey: 'milestone',
value: 'none',
}, {
url: 'milestone_title=%23upcoming',
tokenKey: 'milestone',
value: 'upcoming',
}, {
url: 'milestone_title=%23started',
tokenKey: 'milestone',
value: 'started',
}, {
url: 'label_name[]=No+Label',
tokenKey: 'label',
value: 'none',
}];
const conditions = [{
url: 'assignee_id=0',
tokenKey: 'assignee',
value: 'none',
}, {
url: 'milestone_title=No+Milestone',
tokenKey: 'milestone',
value: 'none',
}, {
url: 'milestone_title=%23upcoming',
tokenKey: 'milestone',
value: 'upcoming',
}, {
url: 'milestone_title=%23started',
tokenKey: 'milestone',
value: 'started',
}, {
url: 'label_name[]=No+Label',
tokenKey: 'label',
value: 'none',
}];
class FilteredSearchTokenKeys {
static get() {
return tokenKeys;
}
class FilteredSearchTokenKeys {
static get() {
return tokenKeys;
}
static getAlternatives() {
return alternativeTokenKeys;
}
static getAlternatives() {
return alternativeTokenKeys;
}
static getConditions() {
return conditions;
}
static getConditions() {
return conditions;
}
static searchByKey(key) {
return tokenKeys.find(tokenKey => tokenKey.key === key) || null;
}
static searchByKey(key) {
return tokenKeys.find(tokenKey => tokenKey.key === key) || null;
}
static searchBySymbol(symbol) {
return tokenKeys.find(tokenKey => tokenKey.symbol === symbol) || null;
}
static searchBySymbol(symbol) {
return tokenKeys.find(tokenKey => tokenKey.symbol === symbol) || null;
}
static searchByKeyParam(keyParam) {
return tokenKeysWithAlternative.find((tokenKey) => {
let tokenKeyParam = tokenKey.key;
static searchByKeyParam(keyParam) {
return tokenKeysWithAlternative.find((tokenKey) => {
let tokenKeyParam = tokenKey.key;
if (tokenKey.param) {
tokenKeyParam += `_${tokenKey.param}`;
}
if (tokenKey.param) {
tokenKeyParam += `_${tokenKey.param}`;
}
return keyParam === tokenKeyParam;
}) || null;
}
return keyParam === tokenKeyParam;
}) || null;
}
static searchByConditionUrl(url) {
return conditions.find(condition => condition.url === url) || null;
}
static searchByConditionUrl(url) {
return conditions.find(condition => condition.url === url) || null;
}
static searchByConditionKeyValue(key, value) {
return conditions
.find(condition => condition.tokenKey === key && condition.value === value) || null;
}
static searchByConditionKeyValue(key, value) {
return conditions
.find(condition => condition.tokenKey === key && condition.value === value) || null;
}
}
window.gl = window.gl || {};
gl.FilteredSearchTokenKeys = FilteredSearchTokenKeys;
})();
window.gl = window.gl || {};
gl.FilteredSearchTokenKeys = FilteredSearchTokenKeys;
require('./filtered_search_token_keys');
(() => {
class FilteredSearchTokenizer {
static processTokens(input) {
const allowedKeys = gl.FilteredSearchTokenKeys.get().map(i => i.key);
// Regex extracts `(token):(symbol)(value)`
// Values that start with a double quote must end in a double quote (same for single)
const tokenRegex = new RegExp(`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, 'g');
const tokens = [];
const tokenIndexes = []; // stores key+value for simple search
let lastToken = null;
const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => {
let tokenValue = v1 || v2 || v3;
let tokenSymbol = symbol;
let tokenIndex = '';
if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') {
tokenSymbol = tokenValue;
tokenValue = '';
}
tokenIndex = `${key}:${tokenValue}`;
// Prevent adding duplicates
if (tokenIndexes.indexOf(tokenIndex) === -1) {
tokenIndexes.push(tokenIndex);
tokens.push({
key,
value: tokenValue || '',
symbol: tokenSymbol || '',
});
}
return '';
}).replace(/\s{2,}/g, ' ').trim() || '';
if (tokens.length > 0) {
const last = tokens[tokens.length - 1];
const lastString = `${last.key}:${last.symbol}${last.value}`;
lastToken = input.lastIndexOf(lastString) ===
input.length - lastString.length ? last : searchToken;
} else {
lastToken = searchToken;
class FilteredSearchTokenizer {
static processTokens(input) {
const allowedKeys = gl.FilteredSearchTokenKeys.get().map(i => i.key);
// Regex extracts `(token):(symbol)(value)`
// Values that start with a double quote must end in a double quote (same for single)
const tokenRegex = new RegExp(`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, 'g');
const tokens = [];
const tokenIndexes = []; // stores key+value for simple search
let lastToken = null;
const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => {
let tokenValue = v1 || v2 || v3;
let tokenSymbol = symbol;
let tokenIndex = '';
if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') {
tokenSymbol = tokenValue;
tokenValue = '';
}
return {
tokens,
lastToken,
searchToken,
};
tokenIndex = `${key}:${tokenValue}`;
// Prevent adding duplicates
if (tokenIndexes.indexOf(tokenIndex) === -1) {
tokenIndexes.push(tokenIndex);
tokens.push({
key,
value: tokenValue || '',
symbol: tokenSymbol || '',
});
}
return '';
}).replace(/\s{2,}/g, ' ').trim() || '';
if (tokens.length > 0) {
const last = tokens[tokens.length - 1];
const lastString = `${last.key}:${last.symbol}${last.value}`;
lastToken = input.lastIndexOf(lastString) ===
input.length - lastString.length ? last : searchToken;
} else {
lastToken = searchToken;
}
return {
tokens,
lastToken,
searchToken,
};
}
}
window.gl = window.gl || {};
gl.FilteredSearchTokenizer = FilteredSearchTokenizer;
})();
window.gl = window.gl || {};
gl.FilteredSearchTokenizer = FilteredSearchTokenizer;
......@@ -368,9 +368,9 @@
});
};
w.gl.utils.setFavicon = (iconName) => {
if (faviconEl && iconName) {
faviconEl.setAttribute('href', `/assets/${iconName}.ico`);
w.gl.utils.setFavicon = (faviconPath) => {
if (faviconEl && faviconPath) {
faviconEl.setAttribute('href', faviconPath);
}
};
......@@ -385,8 +385,8 @@
url: pageUrl,
dataType: 'json',
success: function(data) {
if (data && data.icon) {
gl.utils.setFavicon(`ci_favicons/${data.icon}`);
if (data && data.favicon) {
gl.utils.setFavicon(data.favicon);
} else {
gl.utils.resetFavicon();
}
......
......@@ -165,6 +165,7 @@ import './syntax_highlight';
import './task_list';
import './todos';
import './tree';
import './usage_ping';
import './user';
import './user_tabs';
import './username_validator';
......@@ -218,6 +219,14 @@ $(function () {
}
});
if (bootstrapBreakpoint === 'xs') {
const $rightSidebar = $('aside.right-sidebar, .page-with-sidebar');
$rightSidebar
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed');
}
// prevent default action for disabled buttons
$('.btn').click(function(e) {
if ($(this).hasClass('disabled')) {
......
......@@ -308,8 +308,10 @@ require('./task_list');
if (this.isNewNote(note)) {
this.note_ids.push(note.id);
$notesList = $('ul.main-notes-list');
$notesList.append(note.html).syntaxHighlight();
$notesList = window.$('ul.main-notes-list');
Notes.animateAppendNote(note.html, $notesList);
// Update datetime format on the recent note
gl.utils.localTimeAgo($notesList.find("#note_" + note.id + " .js-timeago"), false);
this.collapseLongCommitList();
......@@ -348,7 +350,7 @@ require('./task_list');
lineType = this.isParallelView() ? form.find('#line_type').val() : 'old';
diffAvatarContainer = row.prevAll('.line_holder').first().find('.js-avatar-container.' + lineType + '_line');
// is this the first note of discussion?
discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']");
discussionContainer = window.$(`.notes[data-discussion-id="${note.discussion_id}"]`);
if (!discussionContainer.length) {
discussionContainer = form.closest('.discussion').find('.notes');
}
......@@ -370,14 +372,13 @@ require('./task_list');
row.find(contentContainerClass + ' .content').append($notes.closest('.content').children());
}
}
// Init discussion on 'Discussion' page if it is merge request page
if ($('body').attr('data-page').indexOf('projects:merge_request') === 0 || !note.diff_discussion_html) {
$('ul.main-notes-list').append($(note.discussion_html).renderGFM());
if (window.$('body').attr('data-page').indexOf('projects:merge_request') === 0 || !note.diff_discussion_html) {
Notes.animateAppendNote(note.discussion_html, window.$('ul.main-notes-list'));
}
} else {
// append new note to all matching discussions
discussionContainer.append($(note.html).renderGFM());
Notes.animateAppendNote(note.html, discussionContainer);
}
if (typeof gl.diffNotesCompileComponents !== 'undefined' && note.discussion_resolvable) {
......@@ -1063,6 +1064,13 @@ require('./task_list');
return $form;
};
Notes.animateAppendNote = function(noteHTML, $notesList) {
const $note = window.$(noteHTML);
$note.addClass('fade-in').renderGFM();
$notesList.append($note);
};
return Notes;
})();
}).call(window);
/* global Flash */
import canceledSvg from 'icons/_icon_status_canceled_borderless.svg';
import createdSvg from 'icons/_icon_status_created_borderless.svg';
import failedSvg from 'icons/_icon_status_failed_borderless.svg';
import manualSvg from 'icons/_icon_status_manual_borderless.svg';
import pendingSvg from 'icons/_icon_status_pending_borderless.svg';
import runningSvg from 'icons/_icon_status_running_borderless.svg';
import skippedSvg from 'icons/_icon_status_skipped_borderless.svg';
import successSvg from 'icons/_icon_status_success_borderless.svg';
import warningSvg from 'icons/_icon_status_warning_borderless.svg';
import StatusIconEntityMap from '../../ci_status_icons';
export default {
data() {
const svgsDictionary = {
icon_status_canceled: canceledSvg,
icon_status_created: createdSvg,
icon_status_failed: failedSvg,
icon_status_manual: manualSvg,
icon_status_pending: pendingSvg,
icon_status_running: runningSvg,
icon_status_skipped: skippedSvg,
icon_status_success: successSvg,
icon_status_warning: warningSvg,
};
return {
builds: '',
spinner: '<span class="fa fa-spinner fa-spin"></span>',
svg: svgsDictionary[this.stage.status.icon],
};
},
......@@ -89,6 +68,9 @@ export default {
triggerButtonClass() {
return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`;
},
svgHTML() {
return StatusIconEntityMap[this.stage.status.icon];
},
},
template: `
<div>
......@@ -100,7 +82,7 @@ export default {
data-toggle="dropdown"
type="button"
:aria-label="stage.title">
<span v-html="svg" aria-hidden="true"></span>
<span v-html="svgHTML" aria-hidden="true"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i>
</button>
<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
......
function UsagePing() {
const usageDataUrl = $('.usage-data').data('endpoint');
$.ajax({
type: 'GET',
url: usageDataUrl,
dataType: 'html',
success(html) {
$('.usage-data').html(html);
},
});
}
window.gl = window.gl || {};
window.gl.UsagePing = UsagePing;
......@@ -145,3 +145,17 @@ a {
.dropdown-menu-nav a {
transition: none;
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.fade-in {
animation: fadeIn $fade-in-duration 1;
}
......@@ -40,6 +40,10 @@
line-height: 24px;
}
.bold {
font-weight: 600;
}
.tab-content {
overflow: visible;
}
......
......@@ -564,3 +564,7 @@
color: $gl-text-color-secondary;
}
}
.droplab-item-ignore {
pointer-events: none;
}
......@@ -331,6 +331,14 @@ header {
.dropdown-menu-nav {
min-width: 140px;
margin-top: -5px;
.current-user {
padding: 5px 18px;
.user-name {
display: block;
}
}
}
}
......
......@@ -460,6 +460,11 @@ $label-inverse-bg: #333;
$label-remove-border: rgba(0, 0, 0, .1);
$label-border-radius: 100px;
/*
* Animation
*/
$fade-in-duration: 200ms;
/*
* Lint
*/
......
......@@ -210,10 +210,6 @@
}
}
.bold {
font-weight: 600;
}
.light {
font-weight: normal;
}
......
......@@ -289,8 +289,12 @@ table.u2f-registrations {
margin: 0 auto;
.bordered-box {
border: 1px solid $border-color;
border: 1px solid $blue-300;
border-radius: $border-radius-default;
background-color: $blue-25;
position: relative;
display: flex;
justify-content: center;
}
.landing {
......@@ -298,28 +302,59 @@ table.u2f-registrations {
margin-bottom: $gl-padding;
.close {
margin-right: 20px;
}
position: absolute;
right: 20px;
opacity: 1;
.dismiss-icon {
float: right;
cursor: pointer;
color: $blue-300;
}
.dismiss-icon {
float: right;
cursor: pointer;
color: $cycle-analytics-dismiss-icon-color;
&:hover {
background-color: transparent;
border: 0;
.dismiss-icon {
color: $blue-400;
}
}
}
.svg-container {
text-align: center;
margin-right: 30px;
display: inline-block;
svg {
width: 136px;
height: 136px;
height: 110px;
vertical-align: top;
}
}
.user-callout-copy {
display: inline-block;
vertical-align: top;
}
}
@media(max-width: $screen-xs-max) {
.inner-content {
padding-left: 30px;
text-align: center;
.bordered-box {
display: block;
}
.landing {
.svg-container,
.user-callout-copy {
margin: 0;
display: block;
svg {
height: 75px;
}
}
}
}
}
......@@ -604,6 +604,10 @@ pre.light-well {
.avatar-container {
align-self: flex-start;
> a {
width: 100%;
}
}
.project-details {
......
......@@ -112,4 +112,8 @@ class Projects::GitHttpController < Projects::GitHttpClientController
def access_klass
@access_klass ||= wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
end
def log_user_activity
Users::ActivityService.new(user, 'pull').execute
end
end
......@@ -64,6 +64,14 @@ module SortingHelper
}
end
def branches_sort_options_hash
{
sort_value_name => sort_title_name,
sort_value_recently_updated => sort_title_recently_updated,
sort_value_oldest_updated => sort_title_oldest_updated
}
end
def sort_title_priority
'Priority'
end
......
......@@ -259,6 +259,7 @@ class ApplicationSetting < ActiveRecord::Base
user_default_external: false,
polling_interval_multiplier: 1,
usage_ping_enabled: true
<<<<<<< HEAD
}
end
......@@ -269,6 +270,8 @@ class ApplicationSetting < ActiveRecord::Base
elasticsearch_aws_region: ENV['ELASTIC_REGION'] || 'us-east-1',
minimum_mirror_sync_time: Gitlab::Mirror::FIFTEEN,
repository_size_limit: 0
=======
>>>>>>> ce/master
}
end
......
......@@ -23,7 +23,7 @@ module Issuable
included do
cache_markdown_field :title, pipeline: :single_line
cache_markdown_field :description
cache_markdown_field :description, issuable_state_filter_enabled: true
belongs_to :author, class_name: "User"
belongs_to :assignee, class_name: "User"
......
......@@ -7,7 +7,11 @@ class Identity < ActiveRecord::Base
validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider }
validates :user_id, uniqueness: { scope: :provider }
<<<<<<< HEAD
scope :with_provider, ->(provider) { where(provider: provider) }
=======
scope :with_extern_uid, ->(provider, extern_uid) { where(extern_uid: extern_uid, provider: provider) }
>>>>>>> ce/master
def ldap?
provider.starts_with?('ldap')
......
......@@ -17,7 +17,7 @@ class Note < ActiveRecord::Base
ignore_column :original_discussion_id
cache_markdown_field :note, pipeline: :note
cache_markdown_field :note, pipeline: :note, issuable_state_filter_enabled: true
# Attribute containing rendered and redacted Markdown as generated by
# Banzai::ObjectRenderer.
......
......@@ -22,7 +22,7 @@ class ChatNotificationService < Service
end
def can_test?
valid?
super && valid?
end
def self.supported_events
......
class StatusEntity < Grape::Entity
include RequestAwareEntity
expose :icon, :favicon, :text, :label, :group
expose :icon, :text, :label, :group
expose :has_details?, as: :has_details
expose :details_path
expose :favicon do |status|
ActionController::Base.helpers.image_path(File.join('ci_favicons', "#{status.favicon}.ico"))
end
end
......@@ -37,7 +37,10 @@ module Commits
def validate!
validate_permissions!
<<<<<<< HEAD
validate_repository_size!
=======
>>>>>>> ce/master
validate_on_branch!
validate_branch_existance!
......@@ -52,12 +55,15 @@ module Commits
end
end
<<<<<<< HEAD
def validate_repository_size!
if project.above_size_limit?
raise_error(Gitlab::RepositorySizeError.new(project).commit_error)
end
end
=======
>>>>>>> ce/master
def validate_on_branch!
if !@start_project.empty_repo? && !@start_project.repository.branch_exists?(@start_branch)
raise_error('You can only create or edit files when you are on a branch')
......
......@@ -8,9 +8,20 @@ class DeleteMergedBranchesService < BaseService
branches = project.repository.branch_names
branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) }
# Prevent deletion of branches relevant to open merge requests
branches -= merge_request_branch_names
branches.each do |branch|
DeleteBranchService.new(project, current_user).execute(branch)
end
end
private
def merge_request_branch_names
# reorder(nil) is necessary for SELECT DISTINCT because default scope adds an ORDER BY
source_names = project.origin_merge_requests.opened.reorder(nil).uniq.pluck(:source_branch)
target_names = project.merge_requests.opened.reorder(nil).uniq.pluck(:target_branch)
(source_names + target_names).uniq
end
end
......@@ -9,6 +9,7 @@ module Search
end
def execute
<<<<<<< HEAD
if current_application_settings.elasticsearch_search?
Gitlab::Elastic::SearchResults.new(current_user, params[:search], elastic_projects, elastic_global)
else
......@@ -33,6 +34,13 @@ module Search
def elastic_global
true
=======
Gitlab::SearchResults.new(current_user, projects, params[:search])
>>>>>>> ce/master
end
def projects
@projects ||= ProjectsFinder.new(current_user: current_user).execute
end
def scope
......
......@@ -14,6 +14,7 @@ module Search
@projects = super.inside_path(group.full_path)
end
<<<<<<< HEAD
def elastic_projects
@elastic_projects ||= projects.pluck(:id)
......@@ -22,5 +23,7 @@ module Search
def elastic_global
false
end
=======
>>>>>>> ce/master
end
end
......@@ -14,7 +14,11 @@ module Users
private
def record_activity
<<<<<<< HEAD
Gitlab::UserActivities.record(@author.id) unless Gitlab::Geo.secondary?
=======
Gitlab::UserActivities.record(@author.id)
>>>>>>> ce/master
Rails.logger.debug("Recorded activity: #{@activity} for User ID: #{@author.id} (username: #{@author.username}")
end
......
......@@ -515,11 +515,20 @@
= f.check_box :usage_ping_enabled
Usage ping enabled
= link_to icon('question-circle'), help_page_path("user/admin_area/settings/usage_statistics", anchor: "usage-data")
<<<<<<< HEAD
.container
.help-block
Every week GitLab will report license usage back to GitLab, Inc.
Disable this option if you do not want this to occur. This is the JSON payload that will be sent:
%pre.usage-data.js-syntax-highlight.code.highlight{ "data-endpoint" => usage_data_admin_application_settings_path(format: :html) }
=======
.help-block
Every week GitLab will report license usage back to GitLab, Inc.
Disable this option if you do not want this to occur. To see the
JSON payload that will be sent, visit the
= succeed '.' do
= link_to "Cohorts page", admin_cohorts_path(anchor: 'usage-ping')
>>>>>>> ce/master
%fieldset
%legend Email
......
<<<<<<< HEAD
%h2 Usage ping
=======
%h2#usage-ping Usage ping
>>>>>>> ce/master
.bs-callout.clearfix
%p
......
......@@ -79,6 +79,11 @@
= icon('caret-down')
.dropdown-menu-nav.dropdown-menu-align-right
%ul
%li.current-user
.user-name.bold
= current_user.name
@#{current_user.username}
%li.divider
%li
= link_to "Profile", current_user, class: 'profile-link', aria: { label: "Profile" }, data: { user: current_user.username }
%li
......
......@@ -11,13 +11,17 @@
Project
- if project_nav_tab? :files
<<<<<<< HEAD
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases graphs network path_locks)) do
=======
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do
>>>>>>> ce/master
= link_to project_files_path(@project), title: 'Repository', class: 'shortcuts-tree' do
%span
Repository
- if project_nav_tab? :container_registry
= nav_link(controller: %w(container_registry)) do
= nav_link(controller: %w[projects/registry/repositories]) do
= link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
%span
Registry
......
......@@ -15,16 +15,14 @@
.dropdown.inline>
%button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
%span.light
= projects_sort_options_hash[@sort]
= branches_sort_options_hash[@sort]
= icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-align-right
%li
= link_to filter_branches_path(sort: sort_value_name) do
= sort_title_name
= link_to filter_branches_path(sort: sort_value_recently_updated) do
= sort_title_recently_updated
= link_to filter_branches_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
%li.dropdown-header
Sort by
- branches_sort_options_hash.each do |value, title|
%li
= link_to title, filter_branches_path(sort: value), class: ("is-active" if @sort == value)
- if can? current_user, :push_code, @project
= link_to namespace_project_merged_branches_path(@project.namespace, @project), class: 'btn btn-inverted btn-remove has-tooltip', title: "Delete all branches that are merged into '#{@project.repository.root_ref}'", method: :delete, data: { confirm: "Deleting the merged branches cannot be undone. Are you sure?", container: 'body' } do
......
......@@ -13,9 +13,6 @@
Environment:
= link_to @environment.name, environment_path(@environment)
.col-sm-6
.nav-controls
= render 'projects/deployments/actions', deployment: @environment.last_deployment
.prometheus-state
.js-getting-started.hidden
.row
......
......@@ -46,7 +46,7 @@
-# This tab is always loaded via AJAX
- if @pipelines.any?
#pipelines.pipelines.tab-pane
= render 'projects/merge_requests/show/pipelines', endpoint: url_for(params.merge(format: :json))
= render 'projects/merge_requests/show/pipelines', endpoint: url_for(params.merge(format: :json)), disable_initialization: true
.mr-loading-status
= spinner
......
- endpoint_path = local_assigns[:endpoint] || pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :json)
- disable_initialization = local_assigns.fetch(:disable_initialization, false)
= render 'projects/commit/pipelines_list', endpoint: endpoint_path
= render 'projects/commit/pipelines_list', endpoint: endpoint_path, disable_initialization: disable_initialization
......@@ -27,8 +27,11 @@
= render 'projects/merge_requests/widget/open/conflicts'
- elsif @merge_request.work_in_progress?
= render 'projects/merge_requests/widget/open/wip'
<<<<<<< HEAD
- elsif @merge_request.should_be_rebased?
= render 'projects/merge_requests/widget/open/rebase'
=======
>>>>>>> ce/master
- elsif @merge_request.merge_when_pipeline_succeeds? && @merge_request.merge_error.present?
= render 'projects/merge_requests/widget/open/error'
- elsif @merge_request.merge_when_pipeline_succeeds?
......
......@@ -16,7 +16,7 @@
%p
Add a general comment to this #{noteable_name}.
%li.divider
%li.divider.droplab-item-ignore
%li#discussion{ data: { value: 'DiscussionNote', 'submit-text' => 'Start discussion', 'close-text' => "Start discussion & close #{noteable_name}", 'reopen-text' => "Start discussion & reopen #{noteable_name}" } }
%a{ href: '#' }
......
......@@ -3,12 +3,11 @@
%button.btn.btn-default.close.js-close-callout{ type: 'button',
'aria-label' => 'Dismiss customize experience box' }
= icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true')
.row
.col-sm-3.col-xs-12.svg-container
= custom_icon('icon_customization')
.col-sm-8.col-xs-12.inner-content
%h4
Customize your experience
%p
Change syntax themes, default project pages, and more in preferences.
= link_to 'Check it out', profile_preferences_path, class: 'btn btn-default js-close-callout'
.svg-container
= custom_icon('icon_customization')
.user-callout-copy
%h4
Customize your experience
%p
Change syntax themes, default project pages, and more in preferences.
= link_to 'Check it out', profile_preferences_path, class: 'btn btn-primary js-close-callout'
......@@ -27,7 +27,8 @@
= visibility_level_icon(group.visibility_level, fw: false)
.avatar-container.s40
= image_tag group_icon(group), class: "avatar s40 hidden-xs"
= link_to group do
= image_tag group_icon(group), class: "avatar s40 hidden-xs"
.title
= link_to group_name, group, class: 'group-name'
......
......@@ -12,10 +12,11 @@
= cache(cache_key) do
- if avatar
.avatar-container.s40
- if use_creator_avatar
= image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
- else
= project_icon(project, alt: '', class: 'avatar project-avatar s40')
= link_to project_path(project), class: dom_class(project) do
- if use_creator_avatar
= image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
- else
= project_icon(project, alt: '', class: 'avatar project-avatar s40')
.project-details
%h3.prepend-top-0.append-bottom-0
= link_to project_path(project), class: dom_class(project) do
......
......@@ -3,8 +3,11 @@ class ScheduleUpdateUserActivityWorker
include CronjobQueue
def perform(batch_size = 500)
<<<<<<< HEAD
return if Gitlab::Geo.secondary?
=======
>>>>>>> ce/master
Gitlab::UserActivities.new.each_slice(batch_size) do |batch|
UpdateUserActivityWorker.perform_async(Hash[batch])
end
......
......@@ -2,6 +2,8 @@ class SystemHookWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
sidekiq_options retry: 4
def perform(hook_id, data, hook_name)
SystemHook.find(hook_id).execute(data, hook_name)
end
......
......@@ -3,8 +3,11 @@ class UpdateUserActivityWorker
include DedicatedSidekiqQueue
def perform(pairs)
<<<<<<< HEAD
return if Gitlab::Geo.secondary?
=======
>>>>>>> ce/master
pairs = cast_data(pairs)
ids = pairs.keys
conditions = 'WHEN id = ? THEN ? ' * ids.length
......
---
title: 29595 Update callout design
merge_request:
author:
---
title: Remove pipeline controls for last deployment from Environment monitoring page
merge_request: 10769
author:
---
title: Added quick-update (fade-in) animation to newly rendered notes
merge_request: 10623
author:
---
title: Added profile name to user dropdown
merge_request:
author:
---
title: Disable test settings on chat notification services when repository is empty
merge_request: 10759
author:
---
title: Display custom hook error messages when automatic merge is enabled
merge_request:
author:
---
title: Removed target blank from the metrics action inside the environments list
merge_request: 10726
author:
---
title: Removed orphaned notification settings without a namespace
merge_request:
author:
---
title: Fixed group milestone date dropdowns not opening
merge_request:
author:
---
title: Add retry to system hook worker
merge_request: 10801
author:
---
title: Fix PlantUML integration in GFM
merge_request: 10651
author:
---
title: Implement search by extern_uid in Users API
merge_request: 10509
author: Robin Bobbitt
---
title: Set the issuable sidebar to remain closed for mobile devices
merge_request:
author:
---
title: Add unique index for notes_id to system note metadata table
merge_request:
author:
---
title: Don't delete a branch involved in an open merge request in "Delete all merged
branches" service
merge_request:
author:
---
title: Add usage ping to CE
merge_request:
author:
......@@ -246,8 +246,8 @@ Settings.gitlab['email_from'] ||= ENV['GITLAB_EMAIL_FROM'] || "gitlab@#{Settings
Settings.gitlab['email_display_name'] ||= ENV['GITLAB_EMAIL_DISPLAY_NAME'] || 'GitLab'
Settings.gitlab['email_reply_to'] ||= ENV['GITLAB_EMAIL_REPLY_TO'] || "noreply@#{Settings.gitlab.host}"
Settings.gitlab['email_subject_suffix'] ||= ENV['GITLAB_EMAIL_SUBJECT_SUFFIX'] || ""
Settings.gitlab['base_url'] ||= Settings.send(:build_base_gitlab_url)
Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url)
Settings.gitlab['base_url'] ||= Settings.__send__(:build_base_gitlab_url)
Settings.gitlab['url'] ||= Settings.__send__(:build_gitlab_url)
Settings.gitlab['user'] ||= 'git'
Settings.gitlab['user_home'] ||= begin
Etc.getpwnam(Settings.gitlab['user']).dir
......@@ -257,7 +257,7 @@ end
Settings.gitlab['time_zone'] ||= nil
Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].nil?
Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['restricted_visibility_levels'] = Settings.__send__(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing))(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil?
Settings.gitlab['default_projects_features'] ||= {}
......@@ -270,7 +270,7 @@ Settings.gitlab.default_projects_features['wiki'] = true if Settin
Settings.gitlab.default_projects_features['snippets'] = true if Settings.gitlab.default_projects_features['snippets'].nil?
Settings.gitlab.default_projects_features['builds'] = true if Settings.gitlab.default_projects_features['builds'].nil?
Settings.gitlab.default_projects_features['container_registry'] = true if Settings.gitlab.default_projects_features['container_registry'].nil?
Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
Settings.gitlab.default_projects_features['visibility_level'] = Settings.__send__(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
Settings.gitlab['domain_whitelist'] ||= []
Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab google_code fogbugz git gitlab_project gitea]
Settings.gitlab['trusted_proxies'] ||= []
......@@ -291,7 +291,7 @@ Settings.gitlab_ci['shared_runners_enabled'] = true if Settings.gitlab_ci['share
Settings.gitlab_ci['all_broken_builds'] = true if Settings.gitlab_ci['all_broken_builds'].nil?
Settings.gitlab_ci['add_pusher'] = false if Settings.gitlab_ci['add_pusher'].nil?
Settings.gitlab_ci['builds_path'] = Settings.absolute(Settings.gitlab_ci['builds_path'] || "builds/")
Settings.gitlab_ci['url'] ||= Settings.send(:build_gitlab_ci_url)
Settings.gitlab_ci['url'] ||= Settings.__send__(:build_gitlab_ci_url)
#
# Reply by email
......@@ -330,7 +330,7 @@ Settings.pages['https'] = false if Settings.pages['https'].nil?
Settings.pages['host'] ||= "example.com"
Settings.pages['port'] ||= Settings.pages.https ? 443 : 80
Settings.pages['protocol'] ||= Settings.pages.https ? "https" : "http"
Settings.pages['url'] ||= Settings.send(:build_pages_url)
Settings.pages['url'] ||= Settings.__send__(:build_pages_url)
Settings.pages['external_http'] ||= false unless Settings.pages['external_http'].present?
Settings.pages['external_https'] ||= false unless Settings.pages['external_https'].present?
......@@ -430,6 +430,14 @@ Settings.cron_jobs['remove_unreferenced_lfs_objects_worker']['job_class'] = 'Rem
Settings.cron_jobs['stuck_import_jobs_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['stuck_import_jobs_worker']['cron'] ||= '15 * * * *'
Settings.cron_jobs['stuck_import_jobs_worker']['job_class'] = 'StuckImportJobsWorker'
Settings.cron_jobs['gitlab_usage_ping_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['gitlab_usage_ping_worker']['cron'] ||= Settings.__send__(:cron_random_weekly_time)
Settings.cron_jobs['gitlab_usage_ping_worker']['job_class'] = 'GitlabUsagePingWorker'
# Every day at 00:30
Settings.cron_jobs['schedule_update_user_activity_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['schedule_update_user_activity_worker']['cron'] ||= '30 0 * * *'
Settings.cron_jobs['schedule_update_user_activity_worker']['job_class'] = 'ScheduleUpdateUserActivityWorker'
Settings.cron_jobs['clear_shared_runners_minutes_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['clear_shared_runners_minutes_worker']['cron'] ||= '0 0 1 * *'
......@@ -453,7 +461,7 @@ Settings.gitlab_shell['ssh_host'] ||= Settings.gitlab.ssh_host
Settings.gitlab_shell['ssh_port'] ||= 22
Settings.gitlab_shell['ssh_user'] ||= Settings.gitlab.user
Settings.gitlab_shell['owner_group'] ||= Settings.gitlab.user
Settings.gitlab_shell['ssh_path_prefix'] ||= Settings.send(:build_gitlab_shell_ssh_path_prefix)
Settings.gitlab_shell['ssh_path_prefix'] ||= Settings.__send__(:build_gitlab_shell_ssh_path_prefix)
#
# Repositories
......
......@@ -100,11 +100,15 @@ namespace :admin do
resource :application_settings, only: [:show, :update] do
resources :services, only: [:index, :edit, :update]
<<<<<<< HEAD
## EE-specific
get :usage_data
## EE-specific
=======
get :usage_data
>>>>>>> ce/master
put :reset_runners_token
put :reset_health_check_token
put :clear_repository_check_states
......
......@@ -54,6 +54,7 @@
- [pages, 1]
- [system_hook_push, 1]
- [update_user_activity, 1]
<<<<<<< HEAD
# EE specific queues
- [geo, 1]
- [project_mirror, 1]
......@@ -64,3 +65,5 @@
- [elastic_indexer, 1]
- [elastic_commit_indexer, 1]
- [export_csv, 1]
=======
>>>>>>> ce/master
......@@ -21,7 +21,10 @@ var config = {
entry: {
blob: './blob_edit/blob_bundle.js',
boards: './boards/boards_bundle.js',
<<<<<<< HEAD
burndown_chart: './burndown_chart/index.js',
=======
>>>>>>> ce/master
common: './commons/index.js',
common_vue: ['vue', './vue_shared/common_vue.js'],
common_d3: ['d3'],
......@@ -129,8 +132,11 @@ var config = {
'notebook_viewer',
'pdf_viewer',
'pipelines',
<<<<<<< HEAD
'mr_widget_ee',
'issue_show'
=======
>>>>>>> ce/master
],
minChunks: function(module, count) {
return module.resource && (/vue_shared/).test(module.resource);
......
class AddUsagePingToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
<<<<<<< HEAD
=======
DOWNTIME = false
>>>>>>> ce/master
def change
add_column :application_settings, :usage_ping_enabled, :boolean, default: true, null: false
end
......
class CreateUserActivities < ActiveRecord::Migration
<<<<<<< HEAD
# Set this constant to true if this migration requires downtime.
DOWNTIME = true
......@@ -23,5 +24,11 @@ class CreateUserActivities < ActiveRecord::Migration
t.belongs_to :user, index: { unique: true }, foreign_key: { on_delete: :cascade }
t.datetime :last_activity_at, null: false
end
=======
DOWNTIME = false
# This migration is a no-op. It just exists to match EE.
def change
>>>>>>> ce/master
end
end
class DeleteOrphanNotificationSettings < ActiveRecord::Migration
DOWNTIME = false
def up
execute("DELETE FROM notification_settings WHERE EXISTS (SELECT true FROM (#{orphan_notification_settings}) AS ns WHERE ns.id = notification_settings.id)")
end
def down
# This is a no-op method to make the migration reversible.
# If someone is trying to rollback for other reasons, we should not throw an Exception.
# raise ActiveRecord::IrreversibleMigration
end
def orphan_notification_settings
<<-SQL
SELECT notification_settings.id
FROM notification_settings
LEFT OUTER JOIN namespaces
ON namespaces.id = notification_settings.source_id
WHERE notification_settings.source_type = 'Namespace'
AND namespaces.id IS NULL
SQL
end
end
class AddIndexToSystemNoteMetadata < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
disable_ddl_transaction!
def up
# MySQL automatically creates an index on a foreign-key constraint; PostgreSQL does not
add_concurrent_index :system_note_metadata, :note_id, unique: true if Gitlab::Database.postgresql?
end
def down
remove_concurrent_index :system_note_metadata, :note_id, unique: true if Gitlab::Database.postgresql?
end
end
<<<<<<< HEAD
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
=======
>>>>>>> ce/master
class DropUserActivitiesTable < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
<<<<<<< HEAD
# When using the methods "add_concurrent_index" or "add_column_with_default"
# you must disable the use of transactions as these methods can not run in an
# existing transaction. When using "add_concurrent_index" make sure that this
......@@ -30,5 +34,9 @@ class DropUserActivitiesTable < ActiveRecord::Migration
add_index "user_activities", ["user_id"], name: "index_user_activities_on_user_id", unique: true, using: :btree
end
=======
# This migration is a no-op. It just exists to match EE.
def change
>>>>>>> ce/master
end
end
......@@ -11,7 +11,11 @@
#
# It's strongly recommended that you check this file into your version control system.
<<<<<<< HEAD
ActiveRecord::Schema.define(version: 20170419065104) do
=======
ActiveRecord::Schema.define(version: 20170419001229) do
>>>>>>> ce/master
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -131,6 +135,7 @@ ActiveRecord::Schema.define(version: 20170419065104) do
t.integer "geo_status_timeout", default: 10
t.string "uuid"
t.decimal "polling_interval_multiplier", default: 1.0, null: false
<<<<<<< HEAD
t.boolean "elasticsearch_experimental_indexer"
end
......@@ -139,6 +144,10 @@ ActiveRecord::Schema.define(version: 20170419065104) do
t.integer "user_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
=======
t.boolean "usage_ping_enabled", default: true, null: false
t.string "uuid"
>>>>>>> ce/master
end
create_table "approver_groups", force: :cascade do |t|
......@@ -1343,6 +1352,8 @@ ActiveRecord::Schema.define(version: 20170419065104) do
t.datetime "updated_at", null: false
end
add_index "system_note_metadata", ["note_id"], name: "index_system_note_metadata_on_note_id", unique: true, using: :btree
create_table "taggings", force: :cascade do |t|
t.integer "tag_id"
t.integer "taggable_id"
......
......@@ -80,6 +80,7 @@ All technical content published by GitLab lives in the documentation, including:
- [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs.
- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
- [Update](update/README.md) Update guides to upgrade your installation.
- [User cohorts](user/admin_area/user_cohorts.md) View user activity over time.
- [Web terminals](administration/integration/terminal.md) Provide terminal access to environments from within GitLab.
- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
- [Downgrade back to CE](downgrade_ee_to_ce/README.md) Follow this guide if you need to downgrade from EE to CE.
......
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