Commit 9a9d7ad9 authored by Rémy Coutable's avatar Rémy Coutable

Merge remote-tracking branch 'ce/master' into rc/ce-to-ee-friday

Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parents 0e519d55 46aadc5c
......@@ -8,7 +8,6 @@
"plugins": [
["istanbul", {
"exclude": [
"app/assets/javascripts/droplab/**/*",
"spec/javascripts/**/*"
]
}],
......
......@@ -333,7 +333,7 @@ migration paths:
- master@gitlab/gitlabhq
- master@gitlab/gitlab-ee
script:
- git fetch origin v8.5.9
- git fetch origin v8.14.10
- git checkout -f FETCH_HEAD
- cp config/resque.yml.example config/resque.yml
- sed -i 's/localhost/redis/g' config/resque.yml
......
......@@ -154,6 +154,9 @@ gem 'sidekiq-cron', '~> 0.4.4'
gem 'redis-namespace', '~> 1.5.2'
gem 'sidekiq-limit_fetch', '~> 3.4'
# Cron Parser
gem 'rufus-scheduler', '~> 3.1.10'
# HTTP requests
gem 'httparty', '~> 0.13.3'
......
......@@ -1032,6 +1032,7 @@ DEPENDENCIES
rubocop-rspec (~> 1.12.0)
ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.16.2)
rufus-scheduler (~> 3.1.10)
rugged (~> 0.25.1.1)
sanitize (~> 2.0)
sass-rails (~> 5.0.6)
......
......@@ -33,7 +33,7 @@ core team members will mention this person.
### Merge request coaching
Several people from the [GitLab team][team] are helping community members to get
their contributions accepted by meeting our [Definition of done](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#definition-of-done).
their contributions accepted by meeting our [Definition of done][done].
What you can expect from them is described at https://about.gitlab.com/jobs/merge-request-coach/.
......@@ -64,6 +64,49 @@ Merge requests may still be merged into master during this period,
but they will go into the _next_ release, unless they are manually cherry-picked into the stable branch.
By freezing the stable branches 2 weeks prior to a release, we reduce the risk of a last minute merge request potentially breaking things.
### Between the 1st and the 7th
These types of merge requests need special consideration:
* **Large features**: a large feature is one that is highlighted in the kick-off
and the release blogpost; typically this will have its own channel in Slack
and a dedicated team with front-end, back-end, and UX.
* **Small features**: any other feature request.
**Large features** must be with a maintainer **by the 1st**. It's OK if they
aren't completely done, but this allows the maintainer enough time to make the
decision about whether this can make it in before the freeze. If the maintainer
doesn't think it will make it, they should inform the developers working on it
and the Product Manager responsible for the feature.
**Small features** must be with a reviewer (not necessarily maintainer) **by the
3rd**.
Most merge requests from the community do not have a specific release
target. However, if one does and falls into either of the above categories, it's
the reviewer's responsibility to manage the above communication and assignment
on behalf of the community member.
### On the 7th
Merge requests should still be complete, following the
[definition of done][done]. The single exception is documentation, and this can
only be left until after the freeze if:
* There is a follow-up issue to add documentation.
* It is assigned to the person writing documentation for this feature, and they
are aware of it.
* It is in the correct milestone, with the ~Deliverable label.
All Community Edition merge requests from GitLab team members merged on the
freeze date (the 7th) should have a corresponding Enterprise Edition merge
request, even if there are no conflicts. This is to reduce the size of the
subsequent EE merge, as we often merge a lot to CE on the release date. For more
information, see
[limit conflicts with EE when developing on CE][limit_ee_conflicts].
### Between the 7th and the 22nd
Once the stable branch is frozen, only fixes for regressions (bugs introduced in that same release)
and security issues will be cherry-picked into the stable branch.
Any merge requests cherry-picked into the stable branch for a previous release will also be picked into the latest stable branch.
......@@ -158,3 +201,5 @@ still an issue I encourage you to open it on the [GitLab.com issue tracker](http
[contribution acceptance criteria]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria
["Implement design & UI elements" guidelines]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#implement-design-ui-elements
[Thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review
[done]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#definition-of-done
[limit_ee_conflicts]: https://docs.gitlab.com/ce/development/limit_ee_conflicts.html
......@@ -51,7 +51,7 @@ function renderCategory(name, emojiList, opts = {}) {
<h5 class="emoji-menu-title">
${name}
</h5>
<ul class="clearfix emoji-menu-list ${opts.menuListClass}">
<ul class="clearfix emoji-menu-list ${opts.menuListClass || ''}">
${emojiList.map(emojiName => `
<li class="emoji-menu-list-item">
<button class="emoji-menu-btn text-center js-emoji-btn" type="button">
......@@ -263,7 +263,8 @@ AwardsHandler.prototype.addAward = function addAward(
this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality);
return typeof callback === 'function' ? callback() : undefined;
});
return $('.emoji-menu').removeClass('is-visible');
$('.emoji-menu').removeClass('is-visible');
$('.js-add-award.is-active').removeClass('is-active');
};
AwardsHandler.prototype.addAwardToEmojiBar = function addAwardToEmojiBar(
......
......@@ -20,6 +20,7 @@ import eventHub from '../eventhub';
list: {
type: Object,
required: false,
default: () => ({}),
},
rootPath: {
type: String,
......@@ -31,6 +32,26 @@ import eventHub from '../eventhub';
default: false,
},
},
computed: {
cardUrl() {
return `${this.issueLinkBase}/${this.issue.id}`;
},
assigneeUrl() {
return `${this.rootPath}${this.issue.assignee.username}`;
},
assigneeUrlTitle() {
return `Assigned to ${this.issue.assignee.name}`;
},
avatarUrlTitle() {
return `Avatar for ${this.issue.assignee.name}`;
},
issueId() {
return `#${this.issue.id}`;
},
showLabelFooter() {
return this.issue.labels.find(l => this.showLabel(l)) !== undefined;
},
},
methods: {
showLabel(label) {
if (!this.list) return true;
......@@ -67,35 +88,41 @@ import eventHub from '../eventhub';
},
template: `
<div>
<h4 class="card-title">
<i
class="fa fa-eye-slash confidential-icon"
v-if="issue.confidential"></i>
<a
:href="issueLinkBase + '/' + issue.id"
:title="issue.title">
{{ issue.title }}
</a>
</h4>
<div class="card-footer">
<span
class="card-number"
v-if="issue.id">
#{{ issue.id }}
</span>
<div class="card-header">
<h4 class="card-title">
<i
class="fa fa-eye-slash confidential-icon"
v-if="issue.confidential"
aria-hidden="true"
/>
<a
class="js-no-trigger"
:href="cardUrl"
:title="issue.title">{{ issue.title }}</a>
<span
class="card-number"
v-if="issue.id"
>
{{ issueId }}
</span>
</h4>
<a
class="card-assignee has-tooltip js-no-trigger"
:href="rootPath + issue.assignee.username"
:title="'Assigned to ' + issue.assignee.name"
:href="assigneeUrl"
:title="assigneeUrlTitle"
v-if="issue.assignee"
data-container="body">
data-container="body"
>
<img
class="avatar avatar-inline s20 js-no-trigger"
:src="issue.assignee.avatar"
width="20"
height="20"
:alt="'Avatar for ' + issue.assignee.name" />
:alt="avatarUrlTitle"
/>
</a>
</div>
<div class="card-footer" v-if="showLabelFooter">
<button
class="label color-label has-tooltip js-no-trigger"
v-for="label in issue.labels"
......
This diff is collapsed.
......@@ -8,10 +8,8 @@ Vue.use(VueResource);
/**
* Commits View > Pipelines Tab > Pipelines Table.
* Merge Request View > Pipelines Tab > Pipelines Table.
*
* Renders Pipelines table in pipelines tab in the commits show view.
* Renders Pipelines table in pipelines tab in the merge request show view.
*/
$(() => {
......@@ -20,13 +18,14 @@ $(() => {
gl.commits.pipelines = gl.commits.pipelines || {};
if (gl.commits.PipelinesTableBundle) {
document.querySelector('#commit-pipeline-table-view').removeChild(this.pipelinesTableBundle.$el);
gl.commits.PipelinesTableBundle.$destroy(true);
}
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
gl.commits.pipelines.PipelinesTableBundle = new CommitPipelinesTable();
if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) {
gl.commits.pipelines.PipelinesTableBundle.$mount(pipelineTableViewEl);
gl.commits.pipelines.PipelinesTableBundle = new CommitPipelinesTable().$mount();
document.querySelector('#commit-pipeline-table-view').appendChild(gl.commits.pipelines.PipelinesTableBundle.$el);
}
});
import Vue from 'vue';
import Visibility from 'visibilityjs';
import PipelinesTableComponent from '../../vue_shared/components/pipelines_table';
import PipelinesService from '../../vue_pipelines_index/services/pipelines_service';
import PipelineStore from '../../vue_pipelines_index/stores/pipelines_store';
......@@ -7,6 +8,7 @@ import EmptyState from '../../vue_pipelines_index/components/empty_state';
import ErrorState from '../../vue_pipelines_index/components/error_state';
import '../../lib/utils/common_utils';
import '../../vue_shared/vue_resource_interceptor';
import Poll from '../../lib/utils/poll';
/**
*
......@@ -20,6 +22,7 @@ import '../../vue_shared/vue_resource_interceptor';
*/
export default Vue.component('pipelines-table', {
components: {
'pipelines-table-component': PipelinesTableComponent,
'error-state': ErrorState,
......@@ -42,6 +45,7 @@ export default Vue.component('pipelines-table', {
state: store.state,
isLoading: false,
hasError: false,
isMakingRequest: false,
};
},
......@@ -64,17 +68,41 @@ export default Vue.component('pipelines-table', {
*
*/
beforeMount() {
this.endpoint = this.$el.dataset.endpoint;
this.helpPagePath = this.$el.dataset.helpPagePath;
const element = document.querySelector('#commit-pipeline-table-view');
this.endpoint = element.dataset.endpoint;
this.helpPagePath = element.dataset.helpPagePath;
this.service = new PipelinesService(this.endpoint);
this.fetchPipelines();
this.poll = new Poll({
resource: this.service,
method: 'getPipelines',
successCallback: this.successCallback,
errorCallback: this.errorCallback,
notificationCallback: this.setIsMakingRequest,
});
if (!Visibility.hidden()) {
this.isLoading = true;
this.poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
this.poll.restart();
} else {
this.poll.stop();
}
});
eventHub.$on('refreshPipelines', this.fetchPipelines);
},
beforeUpdate() {
if (this.state.pipelines.length && this.$children) {
if (this.state.pipelines.length &&
this.$children &&
!this.isMakingRequest &&
!this.isLoading) {
this.store.startTimeAgoLoops.call(this, Vue);
}
},
......@@ -83,21 +111,35 @@ export default Vue.component('pipelines-table', {
eventHub.$off('refreshPipelines');
},
destroyed() {
this.poll.stop();
},
methods: {
fetchPipelines() {
this.isLoading = true;
return this.service.getPipelines()
.then(response => response.json())
.then((json) => {
// depending of the endpoint the response can either bring a `pipelines` key or not.
const pipelines = json.pipelines || json;
this.store.storePipelines(pipelines);
this.isLoading = false;
})
.catch(() => {
this.hasError = true;
this.isLoading = false;
});
.then(response => this.successCallback(response))
.catch(() => this.errorCallback());
},
successCallback(resp) {
const response = resp.json();
// depending of the endpoint the response can either bring a `pipelines` key or not.
const pipelines = response.pipelines || response;
this.store.storePipelines(pipelines);
this.isLoading = false;
},
errorCallback() {
this.hasError = true;
this.isLoading = false;
},
setIsMakingRequest(isMakingRequest) {
this.isMakingRequest = isMakingRequest;
},
},
......
......@@ -38,9 +38,35 @@ showTooltip = function(target, title) {
};
$(function() {
var clipboard;
clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
const clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
clipboard.on('success', genericSuccess);
return clipboard.on('error', genericError);
clipboard.on('error', genericError);
// This a workaround around ClipboardJS limitations to allow the context-specific copy/pasting of plain text or GFM.
// The Ruby `clipboard_button` helper sneaks a JSON hash with `text` and `gfm` keys into the `data-clipboard-text`
// attribute that ClipboardJS reads from.
// When ClipboardJS creates a new `textarea` (directly inside `body`, with a `readonly` attribute`), sets its value
// to the value of this data attribute, focusses on it, and finally programmatically issues the 'Copy' command,
// this code intercepts the copy command/event at the last minute to deconstruct this JSON hash and set the
// `text/plain` and `text/x-gfm` copy data types to the intended values.
$(document).on('copy', 'body > textarea[readonly]', function(e) {
const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return;
const text = e.target.value;
let json;
try {
json = JSON.parse(text);
} catch (ex) {
return;
}
if (!json.text || !json.gfm) return;
e.preventDefault();
clipboardData.setData('text/plain', json.text);
clipboardData.setData('text/x-gfm', json.gfm);
});
});
......@@ -13,10 +13,6 @@ class Diff {
$diffFile.each((index, file) => new gl.ImageFile(file));
if (this.diffViewType() === 'parallel') {
$('.content-wrapper .container-fluid').removeClass('container-limited');
}
if (!isBound) {
$(document)
.on('click', '.js-unfold', this.handleClickUnfold.bind(this))
......
......@@ -24,7 +24,6 @@
/* global Search */
/* global Admin */
/* global NamespaceSelects */
/* global ShortcutsDashboardNavigation */
/* global Project */
/* global ProjectAvatar */
/* global CompareAutocomplete */
......@@ -48,9 +47,12 @@ import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import UserCallout from './user_callout';
import { ProtectedTagCreate, ProtectedTagEditList } from './protected_tags';
<<<<<<< HEAD
import GeoNodes from './geo_nodes';
import ServiceDeskRoot from './projects/settings_service_desk/service_desk_root';
=======
>>>>>>> ce/master
const ShortcutsBlob = require('./shortcuts_blob');
......@@ -354,7 +356,10 @@ const ShortcutsBlob = require('./shortcuts_blob');
// Initialize Protected Branch Settings
new gl.ProtectedBranchCreate();
new gl.ProtectedBranchEditList();
<<<<<<< HEAD
new UsersSelect();
=======
>>>>>>> ce/master
// Initialize Protected Tag Settings
new ProtectedTagCreate();
new ProtectedTagEditList();
......@@ -410,7 +415,6 @@ const ShortcutsBlob = require('./shortcuts_blob');
break;
case 'dashboard':
case 'root':
shortcut_handler = new ShortcutsDashboardNavigation();
new UserCallout();
break;
case 'groups':
......
const DATA_TRIGGER = 'data-dropdown-trigger';
const DATA_DROPDOWN = 'data-dropdown';
const SELECTED_CLASS = 'droplab-item-selected';
const ACTIVE_CLASS = 'droplab-item-active';
export {
DATA_TRIGGER,
DATA_DROPDOWN,
SELECTED_CLASS,
ACTIVE_CLASS,
};
/* eslint-disable */
import utils from './utils';
import { SELECTED_CLASS } from './constants';
var DropDown = function(list) {
this.currentIndex = 0;
this.hidden = true;
this.list = typeof list === 'string' ? document.querySelector(list) : list;
this.items = [];
this.eventWrapper = {};
this.getItems();
this.initTemplateString();
this.addEvents();
this.initialState = list.innerHTML;
};
Object.assign(DropDown.prototype, {
getItems: function() {
this.items = [].slice.call(this.list.querySelectorAll('li'));
return this.items;
},
initTemplateString: function() {
var items = this.items || this.getItems();
var templateString = '';
if (items.length > 0) templateString = items[items.length - 1].outerHTML;
this.templateString = templateString;
return this.templateString;
},
clickEvent: function(e) {
var selected = utils.closest(e.target, 'LI');
if (!selected) return;
this.addSelectedClass(selected);
e.preventDefault();
this.hide();
var listEvent = new CustomEvent('click.dl', {
detail: {
list: this,
selected: selected,
data: e.target.dataset,
},
});
this.list.dispatchEvent(listEvent);
},
addSelectedClass: function (selected) {
this.removeSelectedClasses();
selected.classList.add(SELECTED_CLASS);
},
removeSelectedClasses: function () {
const items = this.items || this.getItems();
items.forEach(item => item.classList.remove(SELECTED_CLASS));
},
addEvents: function() {
this.eventWrapper.clickEvent = this.clickEvent.bind(this)
this.list.addEventListener('click', this.eventWrapper.clickEvent);
},
toggle: function() {
this.hidden ? this.show() : this.hide();
},
setData: function(data) {
this.data = data;
this.render(data);
},
addData: function(data) {
this.data = (this.data || []).concat(data);
this.render(this.data);
},
render: function(data) {
const children = data ? data.map(this.renderChildren.bind(this)) : [];
const renderableList = this.list.querySelector('ul[data-dynamic]') || this.list;
renderableList.innerHTML = children.join('');
},
renderChildren: function(data) {
var html = utils.t(this.templateString, data);
var template = document.createElement('div');
template.innerHTML = html;
this.setImagesSrc(template);
template.firstChild.style.display = data.droplab_hidden ? 'none' : 'block';
return template.firstChild.outerHTML;
},
setImagesSrc: function(template) {
const images = [].slice.call(template.querySelectorAll('img[data-src]'));
images.forEach((image) => {
image.src = image.getAttribute('data-src');
image.removeAttribute('data-src');
});
},
show: function() {
if (!this.hidden) return;
this.list.style.display = 'block';
this.currentIndex = 0;
this.hidden = false;
},
hide: function() {
if (this.hidden) return;
this.list.style.display = 'none';
this.currentIndex = 0;
this.hidden = true;
},
toggle: function () {
this.hidden ? this.show() : this.hide();
},
destroy: function() {
this.hide();
this.list.removeEventListener('click', this.eventWrapper.clickEvent);
}
});
export default DropDown;
/* eslint-disable */
import HookButton from './hook_button';
import HookInput from './hook_input';
import utils from './utils';
import Keyboard from './keyboard';
import { DATA_TRIGGER } from './constants';
var DropLab = function() {
this.ready = false;
this.hooks = [];
this.queuedData = [];
this.config = {};
this.eventWrapper = {};
};
Object.assign(DropLab.prototype, {
loadStatic: function(){
var dropdownTriggers = [].slice.apply(document.querySelectorAll(`[${DATA_TRIGGER}]`));
this.addHooks(dropdownTriggers);
},
addData: function () {
var args = [].slice.apply(arguments);
this.applyArgs(args, '_addData');
},
setData: function() {
var args = [].slice.apply(arguments);
this.applyArgs(args, '_setData');
},
destroy: function() {
this.hooks.forEach(hook => hook.destroy());
this.hooks = [];
this.removeEvents();
},
applyArgs: function(args, methodName) {
if (this.ready) return this[methodName].apply(this, args);
this.queuedData = this.queuedData || [];
this.queuedData.push(args);
},
_addData: function(trigger, data) {
this._processData(trigger, data, 'addData');
},
_setData: function(trigger, data) {
this._processData(trigger, data, 'setData');
},
_processData: function(trigger, data, methodName) {
this.hooks.forEach((hook) => {
if (Array.isArray(trigger)) hook.list[methodName](trigger);
if (hook.trigger.id === trigger) hook.list[methodName](data);
});
},
addEvents: function() {
this.eventWrapper.documentClicked = this.documentClicked.bind(this)
document.addEventListener('click', this.eventWrapper.documentClicked);
},
documentClicked: function(e) {
let thisTag = e.target;
if (thisTag.tagName !== 'UL') thisTag = utils.closest(thisTag, 'UL');
if (utils.isDropDownParts(thisTag, this.hooks) || utils.isDropDownParts(e.target, this.hooks)) return;
this.hooks.forEach(hook => hook.list.hide());
},
removeEvents: function(){
document.removeEventListener('click', this.eventWrapper.documentClicked);
},
changeHookList: function(trigger, list, plugins, config) {
const availableTrigger = typeof trigger === 'string' ? document.getElementById(trigger) : trigger;
this.hooks.forEach((hook, i) => {
hook.list.list.dataset.dropdownActive = false;
if (hook.trigger !== availableTrigger) return;
hook.destroy();
this.hooks.splice(i, 1);
this.addHook(availableTrigger, list, plugins, config);
});
},
addHook: function(hook, list, plugins, config) {
const availableHook = typeof hook === 'string' ? document.querySelector(hook) : hook;
let availableList;
if (typeof list === 'string') {
availableList = document.querySelector(list);
} else if (list instanceof Element) {
availableList = list;
} else {
availableList = document.querySelector(hook.dataset[utils.toCamelCase(DATA_TRIGGER)]);
}
availableList.dataset.dropdownActive = true;
const HookObject = availableHook.tagName === 'INPUT' ? HookInput : HookButton;
this.hooks.push(new HookObject(availableHook, availableList, plugins, config));
return this;
},
addHooks: function(hooks, plugins, config) {
hooks.forEach(hook => this.addHook(hook, null, plugins, config));
return this;
},
setConfig: function(obj){
this.config = obj;
},
fireReady: function() {
const readyEvent = new CustomEvent('ready.dl', {
detail: {
dropdown: this,
},
});
document.dispatchEvent(readyEvent);
this.ready = true;
},
init: function (hook, list, plugins, config) {
hook ? this.addHook(hook, list, plugins, config) : this.loadStatic();
this.addEvents();
Keyboard();
this.fireReady();
this.queuedData.forEach(data => this.addData(data));
this.queuedData = [];
return this;
},
});
export default DropLab;
This diff is collapsed.
/* eslint-disable */
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.droplab||(g.droplab = {}));g=(g.ajax||(g.ajax = {}));g=(g.datasource||(g.datasource = {}));g.js = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global droplab */
require('../window')(function(w){
function droplabAjaxException(message) {
this.message = message;
}
w.droplabAjax = {
_loadUrlData: function _loadUrlData(url) {
var self = this;
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
self.cache[url] = data;
return resolve(data);
} else {
return reject([xhr.responseText, xhr.status]);
}
}
};
xhr.send();
});
},
_loadData: function _loadData(data, config, self) {
if (config.loadingTemplate) {
var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) {
dataLoadingTemplate.outerHTML = self.listTemplate;
}
}
if (!self.destroyed) {
self.hook.list[config.method].call(self.hook.list, data);
}
},
init: function init(hook) {
var self = this;
self.destroyed = false;
self.cache = self.cache || {};
var config = hook.config.droplabAjax;
this.hook = hook;
if (!config || !config.endpoint || !config.method) {
return;
}
if (config.method !== 'setData' && config.method !== 'addData') {
return;
}
if (config.loadingTemplate) {
var dynamicList = hook.list.list.querySelector('[data-dynamic]');
var loadingTemplate = document.createElement('div');
loadingTemplate.innerHTML = config.loadingTemplate;
loadingTemplate.setAttribute('data-loading-template', '');
this.listTemplate = dynamicList.outerHTML;
dynamicList.outerHTML = loadingTemplate.outerHTML;
}
if (self.cache[config.endpoint]) {
self._loadData(self.cache[config.endpoint], config, self);
} else {
this._loadUrlData(config.endpoint)
.then(function(d) {
self._loadData(d, config, self);
}, function(xhrError) {
// TODO: properly handle errors due to XHR cancellation
return;
}).catch(function(e) {
throw new droplabAjaxException(e.message || e);
});
}
},
destroy: function() {
var dynamicList = this.hook.list.list.querySelector('[data-dynamic]');
this.destroyed = true;
if (this.listTemplate && dynamicList) {
dynamicList.outerHTML = this.listTemplate;
}
}
};
});
},{"../window":2}],2:[function(require,module,exports){
module.exports = function(callback) {
return (function() {
callback(this);
}).call(null);
};
},{}]},{},[1])(1)
});
/* eslint-disable */
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.droplab||(g.droplab = {}));g=(g.ajax||(g.ajax = {}));g=(g.datasource||(g.datasource = {}));g.js = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global droplab */
require('../window')(function(w){
w.droplabAjaxFilter = {
init: function(hook) {
this.destroyed = false;
this.hook = hook;
this.notLoading();
this.debounceTriggerWrapper = this.debounceTrigger.bind(this);
this.hook.trigger.addEventListener('keydown.dl', this.debounceTriggerWrapper);
this.hook.trigger.addEventListener('focus', this.debounceTriggerWrapper);
this.trigger(true);
},
notLoading: function notLoading() {
this.loading = false;
},
debounceTrigger: function debounceTrigger(e) {
var NON_CHARACTER_KEYS = [16, 17, 18, 20, 37, 38, 39, 40, 91, 93];
var invalidKeyPressed = NON_CHARACTER_KEYS.indexOf(e.detail.which || e.detail.keyCode) > -1;
var focusEvent = e.type === 'focus';
if (invalidKeyPressed || this.loading) {
return;
}
if (this.timeout) {
clearTimeout(this.timeout);
}
this.timeout = setTimeout(this.trigger.bind(this, focusEvent), 200);
},
trigger: function trigger(getEntireList) {
var config = this.hook.config.droplabAjaxFilter;
var searchValue = this.trigger.value;
if (!config || !config.endpoint || !config.searchKey) {
return;
}
if (config.searchValueFunction) {
searchValue = config.searchValueFunction();
}
if (config.loadingTemplate && this.hook.list.data === undefined ||
this.hook.list.data.length === 0) {
var dynamicList = this.hook.list.list.querySelector('[data-dynamic]');
var loadingTemplate = document.createElement('div');
loadingTemplate.innerHTML = config.loadingTemplate;
loadingTemplate.setAttribute('data-loading-template', true);
this.listTemplate = dynamicList.outerHTML;
dynamicList.outerHTML = loadingTemplate.outerHTML;
}
if (getEntireList) {
searchValue = '';
}
if (config.searchKey === searchValue) {
return this.list.show();
}
this.loading = true;
var params = config.params || {};
params[config.searchKey] = searchValue;
var self = this;
self.cache = self.cache || {};
var url = config.endpoint + this.buildParams(params);
var urlCachedData = self.cache[url];
if (urlCachedData) {
self._loadData(urlCachedData, config, self);
} else {
this._loadUrlData(url)
.then(function(data) {
self._loadData(data, config, self);
}, function(xhrError) {
// TODO: properly handle errors due to XHR cancellation
return;
});
}
},
_loadUrlData: function _loadUrlData(url) {
var self = this;
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
self.cache[url] = data;
return resolve(data);
} else {
return reject([xhr.responseText, xhr.status]);
}
}
};
xhr.send();
});
},
_loadData: function _loadData(data, config, self) {
if (config.loadingTemplate && self.hook.list.data === undefined ||
self.hook.list.data.length === 0) {
const dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) {
dataLoadingTemplate.outerHTML = self.listTemplate;
}
}
if (!self.destroyed) {
var hookListChildren = self.hook.list.list.children;
var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
if (onlyDynamicList && data.length === 0) {
self.hook.list.hide();
}
self.hook.list.setData.call(self.hook.list, data);
}
self.notLoading();
self.hook.list.currentIndex = 0;
},
buildParams: function(params) {
if (!params) return '';
var paramsArray = Object.keys(params).map(function(param) {
return param + '=' + (params[param] || '');
});
return '?' + paramsArray.join('&');
},
destroy: function destroy() {
if (this.timeout) {
clearTimeout(this.timeout);
}
this.destroyed = true;
this.hook.trigger.removeEventListener('keydown.dl', this.debounceTriggerWrapper);
this.hook.trigger.removeEventListener('focus', this.debounceTriggerWrapper);
}
};
});
},{"../window":2}],2:[function(require,module,exports){
module.exports = function(callback) {
return (function() {
callback(this);
}).call(null);
};
},{}]},{},[1])(1)
});
/* eslint-disable */
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.droplab||(g.droplab = {}));g=(g.filter||(g.filter = {}));g.js = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global droplab */
require('../window')(function(w){
w.droplabFilter = {
keydownWrapper: function(e){
var hiddenCount = 0;
var dataHiddenCount = 0;
var list = e.detail.hook.list;
var data = list.data;
var value = e.detail.hook.trigger.value.toLowerCase();
var config = e.detail.hook.config.droplabFilter;
var matches = [];
var filterFunction;
// will only work on dynamically set data
if(!data){
return;
}
if (config && config.filterFunction && typeof config.filterFunction === 'function') {
filterFunction = config.filterFunction;
} else {
filterFunction = function(o){
// cheap string search
o.droplab_hidden = o[config.template].toLowerCase().indexOf(value) === -1;
return o;
};
}
dataHiddenCount = data.filter(function(o) {
return !o.droplab_hidden;
}).length;
matches = data.map(function(o) {
return filterFunction(o, value);
});
hiddenCount = matches.filter(function(o) {
return !o.droplab_hidden;
}).length;
if (dataHiddenCount !== hiddenCount) {
list.render(matches);
list.currentIndex = 0;
}
},
init: function init(hookInput) {
var config = hookInput.config.droplabFilter;
if (!config || (!config.template && !config.filterFunction)) {
return;
}
this.hookInput = hookInput;
this.hookInput.trigger.addEventListener('keyup.dl', this.keydownWrapper);
this.hookInput.trigger.addEventListener('mousedown.dl', this.keydownWrapper);
},
destroy: function destroy(){
this.hookInput.trigger.removeEventListener('keyup.dl', this.keydownWrapper);
this.hookInput.trigger.removeEventListener('mousedown.dl', this.keydownWrapper);
}
};
});
},{"../window":2}],2:[function(require,module,exports){
module.exports = function(callback) {
return (function() {
callback(this);
}).call(null);
};
},{}]},{},[1])(1)
});
/* eslint-disable */
import DropDown from './drop_down';
var Hook = function(trigger, list, plugins, config){
this.trigger = trigger;
this.list = new DropDown(list);
this.type = 'Hook';
this.event = 'click';
this.plugins = plugins || [];
this.config = config || {};
this.id = trigger.id;
};
Object.assign(Hook.prototype, {
addEvents: function(){},
constructor: Hook,
});
export default Hook;
/* eslint-disable */
import Hook from './hook';
var HookButton = function(trigger, list, plugins, config) {
Hook.call(this, trigger, list, plugins, config);
this.type = 'button';
this.event = 'click';
this.eventWrapper = {};
this.addEvents();
this.addPlugins();
};
HookButton.prototype = Object.create(Hook.prototype);
Object.assign(HookButton.prototype, {
addPlugins: function() {
this.plugins.forEach(plugin => plugin.init(this));
},
clicked: function(e){
var buttonEvent = new CustomEvent('click.dl', {
detail: {
hook: this,
},
bubbles: true,
cancelable: true
});
e.target.dispatchEvent(buttonEvent);
this.list.toggle();
},
addEvents: function(){
this.eventWrapper.clicked = this.clicked.bind(this);
this.trigger.addEventListener('click', this.eventWrapper.clicked);
},
removeEvents: function(){
this.trigger.removeEventListener('click', this.eventWrapper.clicked);
},
restoreInitialState: function() {
this.list.list.innerHTML = this.list.initialState;
},
removePlugins: function() {
this.plugins.forEach(plugin => plugin.destroy());
},
destroy: function() {
this.restoreInitialState();
this.removeEvents();
this.removePlugins();
},
constructor: HookButton,
});
export default HookButton;
/* eslint-disable */
import Hook from './hook';
var HookInput = function(trigger, list, plugins, config) {
Hook.call(this, trigger, list, plugins, config);
this.type = 'input';
this.event = 'input';
this.eventWrapper = {};
this.addEvents();
this.addPlugins();
};
Object.assign(HookInput.prototype, {
addPlugins: function() {
this.plugins.forEach(plugin => plugin.init(this));
},
addEvents: function(){
this.eventWrapper.mousedown = this.mousedown.bind(this);
this.eventWrapper.input = this.input.bind(this);
this.eventWrapper.keyup = this.keyup.bind(this);
this.eventWrapper.keydown = this.keydown.bind(this);
this.trigger.addEventListener('mousedown', this.eventWrapper.mousedown);
this.trigger.addEventListener('input', this.eventWrapper.input);
this.trigger.addEventListener('keyup', this.eventWrapper.keyup);
this.trigger.addEventListener('keydown', this.eventWrapper.keydown);
},
removeEvents: function() {
this.hasRemovedEvents = true;
this.trigger.removeEventListener('mousedown', this.eventWrapper.mousedown);
this.trigger.removeEventListener('input', this.eventWrapper.input);
this.trigger.removeEventListener('keyup', this.eventWrapper.keyup);
this.trigger.removeEventListener('keydown', this.eventWrapper.keydown);
},
input: function(e) {
if(this.hasRemovedEvents) return;
this.list.show();
const inputEvent = new CustomEvent('input.dl', {
detail: {
hook: this,
text: e.target.value,
},
bubbles: true,
cancelable: true
});
e.target.dispatchEvent(inputEvent);
},
mousedown: function(e) {
if (this.hasRemovedEvents) return;
const mouseEvent = new CustomEvent('mousedown.dl', {
detail: {
hook: this,
text: e.target.value,
},
bubbles: true,
cancelable: true,
});
e.target.dispatchEvent(mouseEvent);
},
keyup: function(e) {
if (this.hasRemovedEvents) return;
this.keyEvent(e, 'keyup.dl');
},
keydown: function(e) {
if (this.hasRemovedEvents) return;
this.keyEvent(e, 'keydown.dl');
},
keyEvent: function(e, eventName) {
this.list.show();
const keyEvent = new CustomEvent(eventName, {
detail: {
hook: this,
text: e.target.value,
which: e.which,
key: e.key,
},
bubbles: true,
cancelable: true,
});
e.target.dispatchEvent(keyEvent);
},
restoreInitialState: function() {
this.list.list.innerHTML = this.list.initialState;
},
removePlugins: function() {
this.plugins.forEach(plugin => plugin.destroy());
},
destroy: function() {
this.restoreInitialState();
this.removeEvents();
this.removePlugins();
this.list.destroy();
}
});
export default HookInput;
/* eslint-disable */
import { ACTIVE_CLASS } from './constants';
const Keyboard = function () {
var currentKey;
var currentFocus;
var isUpArrow = false;
var isDownArrow = false;
var removeHighlight = function removeHighlight(list) {
var itemElements = Array.prototype.slice.call(list.list.querySelectorAll('li:not(.divider)'), 0);
var listItems = [];
for(var i = 0; i < itemElements.length; i++) {
var listItem = itemElements[i];
listItem.classList.remove(ACTIVE_CLASS);
if (listItem.style.display !== 'none') {
listItems.push(listItem);
}
}
return listItems;
};
var setMenuForArrows = function setMenuForArrows(list) {
var listItems = removeHighlight(list);
if(list.currentIndex>0){
if(!listItems[list.currentIndex-1]){
list.currentIndex = list.currentIndex-1;
}
if (listItems[list.currentIndex-1]) {
var el = listItems[list.currentIndex-1];
var filterDropdownEl = el.closest('.filter-dropdown');
el.classList.add(ACTIVE_CLASS);
if (filterDropdownEl) {
var filterDropdownBottom = filterDropdownEl.offsetHeight;
var elOffsetTop = el.offsetTop - 30;
if (elOffsetTop > filterDropdownBottom) {
filterDropdownEl.scrollTop = elOffsetTop - filterDropdownBottom;
}
}
}
}
};
var mousedown = function mousedown(e) {
var list = e.detail.hook.list;
removeHighlight(list);
list.show();
list.currentIndex = 0;
isUpArrow = false;
isDownArrow = false;
};
var selectItem = function selectItem(list) {
var listItems = removeHighlight(list);
var currentItem = listItems[list.currentIndex-1];
var listEvent = new CustomEvent('click.dl', {
detail: {
list: list,
selected: currentItem,
data: currentItem.dataset,
},
});
list.list.dispatchEvent(listEvent);
list.hide();
}
var keydown = function keydown(e){
var typedOn = e.target;
var list = e.detail.hook.list;
var currentIndex = list.currentIndex;
isUpArrow = false;
isDownArrow = false;
if(e.detail.which){
currentKey = e.detail.which;
if(currentKey === 13){
selectItem(e.detail.hook.list);
return;
}
if(currentKey === 38) {
isUpArrow = true;
}
if(currentKey === 40) {
isDownArrow = true;
}
} else if(e.detail.key) {
currentKey = e.detail.key;
if(currentKey === 'Enter'){
selectItem(e.detail.hook.list);
return;
}
if(currentKey === 'ArrowUp') {
isUpArrow = true;
}
if(currentKey === 'ArrowDown') {
isDownArrow = true;
}
}
if(isUpArrow){ currentIndex--; }
if(isDownArrow){ currentIndex++; }
if(currentIndex < 0){ currentIndex = 0; }
list.currentIndex = currentIndex;
setMenuForArrows(e.detail.hook.list);
};
document.addEventListener('mousedown.dl', mousedown);
document.addEventListener('keydown.dl', keydown);
};
export default Keyboard;
/* eslint-disable */
const Ajax = {
_loadUrlData: function _loadUrlData(url) {
var self = this;
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
self.cache[url] = data;
return resolve(data);
} else {
return reject([xhr.responseText, xhr.status]);
}
}
};
xhr.send();
});
},
_loadData: function _loadData(data, config, self) {
if (config.loadingTemplate) {
var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) dataLoadingTemplate.outerHTML = self.listTemplate;
}
if (!self.destroyed) self.hook.list[config.method].call(self.hook.list, data);
},
init: function init(hook) {
var self = this;
self.destroyed = false;
self.cache = self.cache || {};
var config = hook.config.Ajax;
this.hook = hook;
if (!config || !config.endpoint || !config.method) {
return;
}
if (config.method !== 'setData' && config.method !== 'addData') {
return;
}
if (config.loadingTemplate) {
var dynamicList = hook.list.list.querySelector('[data-dynamic]');
var loadingTemplate = document.createElement('div');
loadingTemplate.innerHTML = config.loadingTemplate;
loadingTemplate.setAttribute('data-loading-template', '');
this.listTemplate = dynamicList.outerHTML;
dynamicList.outerHTML = loadingTemplate.outerHTML;
}
if (self.cache[config.endpoint]) {
self._loadData(self.cache[config.endpoint], config, self);
} else {
this._loadUrlData(config.endpoint)
.then(function(d) {
self._loadData(d, config, self);
}, config.onError).catch(config.onError);
}
},
destroy: function() {
this.destroyed = true;
}
};
export default Ajax;
/* eslint-disable */
const AjaxFilter = {
init: function(hook) {
this.destroyed = false;
this.hook = hook;
this.notLoading();
this.eventWrapper = {};
this.eventWrapper.debounceTrigger = this.debounceTrigger.bind(this);
this.hook.trigger.addEventListener('keydown.dl', this.eventWrapper.debounceTrigger);
this.hook.trigger.addEventListener('focus', this.eventWrapper.debounceTrigger);
this.trigger(true);
},
notLoading: function notLoading() {
this.loading = false;
},
debounceTrigger: function debounceTrigger(e) {
var NON_CHARACTER_KEYS = [16, 17, 18, 20, 37, 38, 39, 40, 91, 93];
var invalidKeyPressed = NON_CHARACTER_KEYS.indexOf(e.detail.which || e.detail.keyCode) > -1;
var focusEvent = e.type === 'focus';
if (invalidKeyPressed || this.loading) {
return;
}
if (this.timeout) {
clearTimeout(this.timeout);
}
this.timeout = setTimeout(this.trigger.bind(this, focusEvent), 200);
},
trigger: function trigger(getEntireList) {
var config = this.hook.config.AjaxFilter;
var searchValue = this.trigger.value;
if (!config || !config.endpoint || !config.searchKey) {
return;
}
if (config.searchValueFunction) {
searchValue = config.searchValueFunction();
}
if (config.loadingTemplate && this.hook.list.data === undefined ||
this.hook.list.data.length === 0) {
var dynamicList = this.hook.list.list.querySelector('[data-dynamic]');
var loadingTemplate = document.createElement('div');
loadingTemplate.innerHTML = config.loadingTemplate;
loadingTemplate.setAttribute('data-loading-template', true);
this.listTemplate = dynamicList.outerHTML;
dynamicList.outerHTML = loadingTemplate.outerHTML;
}
if (getEntireList) {
searchValue = '';
}
if (config.searchKey === searchValue) {
return this.list.show();
}
this.loading = true;
var params = config.params || {};
params[config.searchKey] = searchValue;
var self = this;
self.cache = self.cache || {};
var url = config.endpoint + this.buildParams(params);
var urlCachedData = self.cache[url];
if (urlCachedData) {
self._loadData(urlCachedData, config, self);
} else {
this._loadUrlData(url)
.then(function(data) {
self._loadData(data, config, self);
}, config.onError).catch(config.onError);
}
},
_loadUrlData: function _loadUrlData(url) {
var self = this;
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
self.cache[url] = data;
return resolve(data);
} else {
return reject([xhr.responseText, xhr.status]);
}
}
};
xhr.send();
});
},
_loadData: function _loadData(data, config, self) {
const list = self.hook.list;
if (config.loadingTemplate && list.data === undefined ||
list.data.length === 0) {
const dataLoadingTemplate = list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) {
dataLoadingTemplate.outerHTML = self.listTemplate;
}
}
if (!self.destroyed) {
var hookListChildren = list.list.children;
var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
if (onlyDynamicList && data.length === 0) {
list.hide();
}
list.setData.call(list, data);
}
self.notLoading();
list.currentIndex = 0;
},
buildParams: function(params) {
if (!params) return '';
var paramsArray = Object.keys(params).map(function(param) {
return param + '=' + (params[param] || '');
});
return '?' + paramsArray.join('&');
},
destroy: function destroy() {
if (this.timeout)clearTimeout(this.timeout);
this.destroyed = true;
this.hook.trigger.removeEventListener('keydown.dl', this.eventWrapper.debounceTrigger);
this.hook.trigger.removeEventListener('focus', this.eventWrapper.debounceTrigger);
}
};
export default AjaxFilter;
/* eslint-disable */
const Filter = {
keydown: function(e){
if (this.destroyed) return;
var hiddenCount = 0;
var dataHiddenCount = 0;
var list = e.detail.hook.list;
var data = list.data;
var value = e.detail.hook.trigger.value.toLowerCase();
var config = e.detail.hook.config.Filter;
var matches = [];
var filterFunction;
// will only work on dynamically set data
if(!data){
return;
}
if (config && config.filterFunction && typeof config.filterFunction === 'function') {
filterFunction = config.filterFunction;
} else {
filterFunction = function(o){
// cheap string search
o.droplab_hidden = o[config.template].toLowerCase().indexOf(value) === -1;
return o;
};
}
dataHiddenCount = data.filter(function(o) {
return !o.droplab_hidden;
}).length;
matches = data.map(function(o) {
return filterFunction(o, value);
});
hiddenCount = matches.filter(function(o) {
return !o.droplab_hidden;
}).length;
if (dataHiddenCount !== hiddenCount) {
list.setData(matches);
list.currentIndex = 0;
}
},
debounceKeydown: function debounceKeydown(e) {
if ([
13, // enter
16, // shift
17, // ctrl
18, // alt
20, // caps lock
37, // left arrow
38, // up arrow
39, // right arrow
40, // down arrow
91, // left window
92, // right window
93, // select
].indexOf(e.detail.which || e.detail.keyCode) > -1) return;
if (this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(this.keydown.bind(this, e), 200);
},
init: function init(hook) {
var config = hook.config.Filter;
if (!config || !config.template) return;
this.hook = hook;
this.destroyed = false;
this.eventWrapper = {};
this.eventWrapper.debounceKeydown = this.debounceKeydown.bind(this);
this.hook.trigger.addEventListener('keydown.dl', this.eventWrapper.debounceKeydown);
this.hook.trigger.addEventListener('mousedown.dl', this.eventWrapper.debounceKeydown);
this.debounceKeydown({ detail: { hook: this.hook } });
},
destroy: function destroy() {
if (this.timeout) clearTimeout(this.timeout);
this.destroyed = true;
this.hook.trigger.removeEventListener('keydown.dl', this.eventWrapper.debounceKeydown);
this.hook.trigger.removeEventListener('mousedown.dl', this.eventWrapper.debounceKeydown);
}
};
export default Filter;
/* eslint-disable */
const InputSetter = {
init(hook) {
this.hook = hook;
this.destroyed = false;
this.config = hook.config.InputSetter || (this.hook.config.InputSetter = {});
this.eventWrapper = {};
this.addEvents();
},
addEvents() {
this.eventWrapper.setInputs = this.setInputs.bind(this);
this.hook.list.list.addEventListener('click.dl', this.eventWrapper.setInputs);
},
removeEvents() {
this.hook.list.list.removeEventListener('click.dl', this.eventWrapper.setInputs);
},
setInputs(e) {
if (this.destroyed) return;
const selectedItem = e.detail.selected;
if (!Array.isArray(this.config)) this.config = [this.config];
this.config.forEach(config => this.setInput(config, selectedItem));
},
setInput(config, selectedItem) {
const input = config.input || this.hook.trigger;
const newValue = selectedItem.getAttribute(config.valueAttribute);
const inputAttribute = config.inputAttribute;
if (!newValue) return;
if (input.hasAttribute(inputAttribute)) return input.setAttribute(inputAttribute, newValue);
if (input.tagName === 'INPUT') return input.value = newValue;
return input.textContent = newValue;
},
destroy() {
this.destroyed = true;
this.removeEvents();
},
};
export default InputSetter;
/* eslint-disable */
import { DATA_TRIGGER, DATA_DROPDOWN } from './constants';
const utils = {
toCamelCase(attr) {
return this.camelize(attr.split('-').slice(1).join(' '));
},
t(s, d) {
for (const p in d) {
if (Object.prototype.hasOwnProperty.call(d, p)) {
s = s.replace(new RegExp(`{{${p}}}`, 'g'), d[p]);
}
}
return s;
},
camelize(str) {
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => {
return index === 0 ? letter.toLowerCase() : letter.toUpperCase();
}).replace(/\s+/g, '');
},
closest(thisTag, stopTag) {
while (thisTag && thisTag.tagName !== stopTag && thisTag.tagName !== 'HTML') {
thisTag = thisTag.parentNode;
}
return thisTag;
},
isDropDownParts(target) {
if (!target || target.tagName === 'HTML') return false;
return target.hasAttribute(DATA_TRIGGER) || target.hasAttribute(DATA_DROPDOWN);
},
};
export default utils;
......@@ -32,12 +32,6 @@ export default Vue.component('environment-folder-view', {
cssContainerClass: environmentsData.cssClass,
canCreateDeployment: environmentsData.canCreateDeployment,
canReadEnvironment: environmentsData.canReadEnvironment,
// svgs
commitIconSvg: environmentsData.commitIconSvg,
playIconSvg: environmentsData.playIconSvg,
terminalIconSvg: environmentsData.terminalIconSvg,
// Pagination Properties,
paginationInformation: {},
pageNumber: 1,
......@@ -175,11 +169,14 @@ export default Vue.component('environment-folder-view', {
:environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed"
<<<<<<< HEAD
:play-icon-svg="playIconSvg"
:terminal-icon-svg="terminalIconSvg"
:commit-icon-svg="commitIconSvg"
:toggleDeployBoard="toggleDeployBoard"
:store="store"
=======
>>>>>>> ce/master
:service="service"/>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
......
require('./filtered_search_dropdown');
import Filter from '~/droplab/plugins/filter';
/* global droplabFilter */
require('./filtered_search_dropdown');
(() => {
class DropdownHint extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter);
this.config = {
droplabFilter: {
Filter: {
template: 'hint',
filterFunction: gl.DropdownUtils.filterHint.bind(null, input),
},
......@@ -69,12 +69,12 @@ require('./filtered_search_dropdown');
}
});
this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config);
this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config);
this.droplab.setData(this.hookId, dropdownData);
}
init() {
this.droplab.addHook(this.input, this.dropdown, [droplabFilter], this.config).init();
this.droplab.addHook(this.input, this.dropdown, [Filter], this.config).init();
}
}
......
require('./filtered_search_dropdown');
/* global Flash */
import Ajax from '~/droplab/plugins/ajax';
import Filter from '~/droplab/plugins/filter';
/* global droplabAjax */
/* global droplabFilter */
require('./filtered_search_dropdown');
(() => {
class DropdownNonUser extends gl.FilteredSearchDropdown {
......@@ -9,13 +11,19 @@ require('./filtered_search_dropdown');
super(droplab, dropdown, input, filter);
this.symbol = symbol;
this.config = {
droplabAjax: {
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 */
},
},
droplabFilter: {
Filter: {
filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input),
template: 'title',
},
};
}
......@@ -29,13 +37,13 @@ require('./filtered_search_dropdown');
renderContent(forceShowList = false) {
this.droplab
.changeHookList(this.hookId, this.dropdown, [droplabAjax, droplabFilter], this.config);
.changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
super.renderContent(forceShowList);
}
init() {
this.droplab
.addHook(this.input, this.dropdown, [droplabAjax, droplabFilter], this.config).init();
.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
}
}
......
require('./filtered_search_dropdown');
/* global Flash */
import AjaxFilter from '~/droplab/plugins/ajax_filter';
/* global droplabAjaxFilter */
require('./filtered_search_dropdown');
(() => {
class DropdownUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter);
this.config = {
droplabAjaxFilter: {
AjaxFilter: {
endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
searchKey: 'search',
params: {
......@@ -18,6 +20,11 @@ require('./filtered_search_dropdown');
},
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 */
},
},
};
}
......@@ -28,7 +35,7 @@ require('./filtered_search_dropdown');
}
renderContent(forceShowList = false) {
this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjaxFilter], this.config);
this.droplab.changeHookList(this.hookId, this.dropdown, [AjaxFilter], this.config);
super.renderContent(forceShowList);
}
......@@ -56,7 +63,7 @@ require('./filtered_search_dropdown');
}
init() {
this.droplab.addHook(this.input, this.dropdown, [droplabAjaxFilter], this.config).init();
this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init();
}
}
......
......@@ -4,7 +4,7 @@
class FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
this.droplab = droplab;
this.hookId = input && input.getAttribute('data-id');
this.hookId = input && input.id;
this.input = input;
this.filter = filter;
this.dropdown = dropdown;
......
/* global DropLab */
import DropLab from '~/droplab/drop_lab';
import FilteredSearchContainer from './container';
(() => {
......
......@@ -16,10 +16,13 @@ import eventHub from './event_hub';
this.tokensContainer = this.container.querySelector('.tokens-container');
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
<<<<<<< HEAD
if (page === 'issues' || page === 'boards') {
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeysWithWeights;
}
=======
>>>>>>> ce/master
this.recentSearchesStore = new RecentSearchesStore();
let recentSearchesKey = 'issue-recent-searches';
if (page === 'merge_requests') {
......@@ -160,7 +163,7 @@ import eventHub from './event_hub';
if (e.keyCode === 13) {
const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
const dropdownEl = dropdown.element;
const activeElements = dropdownEl.querySelectorAll('.dropdown-active');
const activeElements = dropdownEl.querySelectorAll('.droplab-item-active');
e.preventDefault();
......
......@@ -65,7 +65,6 @@ export default class Poll {
this.makeRequest();
}, pollInterval);
}
this.options.successCallback(response);
}
......@@ -76,8 +75,14 @@ export default class Poll {
notificationCallback(true);
return resource[method](data)
.then(response => this.checkConditions(response))
.catch(error => errorCallback(error));
.then((response) => {
this.checkConditions(response);
notificationCallback(false);
})
.catch((error) => {
notificationCallback(false);
errorCallback(error);
});
}
/**
......
......@@ -75,12 +75,6 @@ import './u2f/error';
import './u2f/register';
import './u2f/util';
// droplab
import './droplab/droplab';
import './droplab/droplab_ajax';
import './droplab/droplab_ajax_filter';
import './droplab/droplab_filter';
// everything else
import './abuse_reports';
import './activities';
......
......@@ -90,6 +90,7 @@ import './flash';
.on('click', this.clickTab);
}
// Used in tests
unbindEvents() {
$(document)
.off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
......@@ -99,9 +100,11 @@ import './flash';
.off('click', this.clickTab);
}
destroy() {
this.unbindEvents();
destroyPipelinesView() {
if (this.commitPipelinesTable) {
document.querySelector('#commit-pipeline-table-view')
.removeChild(this.commitPipelinesTable.$el);
this.commitPipelinesTable.$destroy();
}
}
......@@ -128,6 +131,7 @@ import './flash';
this.loadCommits($target.attr('href'));
this.expandView();
this.resetViewContainer();
this.destroyPipelinesView();
} else if (this.isDiffAction(action)) {
this.loadDiff($target.attr('href'));
if (Breakpoints.get().getBreakpointSize() !== 'lg') {
......@@ -136,12 +140,14 @@ import './flash';
if (this.diffViewType() === 'parallel') {
this.expandViewContainer();
}
this.destroyPipelinesView();
} else if (action === 'pipelines') {
this.resetViewContainer();
this.loadPipelines();
this.mountPipelinesView();
} else {
this.expandView();
this.resetViewContainer();
this.destroyPipelinesView();
}
if (this.setUrl) {
this.setCurrentAction(action);
......@@ -227,16 +233,12 @@ import './flash';
});
}
loadPipelines() {
if (this.pipelinesLoaded) {
return;
}
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
// Could already be mounted from the `pipelines_bundle`
if (pipelineTableViewEl) {
this.commitPipelinesTable = new CommitPipelinesTable().$mount(pipelineTableViewEl);
}
this.pipelinesLoaded = true;
mountPipelinesView() {
this.commitPipelinesTable = new CommitPipelinesTable().$mount();
// $mount(el) replaces the el with the new rendered component. We need it in order to mount
// it everytime this tab is clicked - https://vuejs.org/v2/api/#vm-mount
document.querySelector('#commit-pipeline-table-view')
.appendChild(this.commitPipelinesTable.$el);
}
loadDiff(source) {
......
......@@ -6,7 +6,10 @@ import statusCodes from '~/lib/utils/http_status';
import { formatRelevantDigits } from '~/lib/utils/number_utils';
import '../flash';
const prometheusContainer = '.prometheus-container';
const prometheusParentGraphContainer = '.prometheus-graphs';
const prometheusGraphsContainer = '.prometheus-graph';
const prometheusStatesContainer = '.prometheus-state';
const metricsEndpoint = 'metrics.json';
const timeFormat = d3.time.format('%H:%M');
const dayFormat = d3.time.format('%b %e, %a');
......@@ -14,19 +17,30 @@ const bisectDate = d3.bisector(d => d.time).left;
const extraAddedWidthParent = 100;
class PrometheusGraph {
constructor() {
this.margin = { top: 80, right: 180, bottom: 80, left: 100 };
this.marginLabelContainer = { top: 40, right: 0, bottom: 40, left: 0 };
const parentContainerWidth = $(prometheusGraphsContainer).parent().width() +
extraAddedWidthParent;
this.originalWidth = parentContainerWidth;
this.originalHeight = 330;
this.width = parentContainerWidth - this.margin.left - this.margin.right;
this.height = this.originalHeight - this.margin.top - this.margin.bottom;
this.backOffRequestCounter = 0;
this.configureGraph();
this.init();
const $prometheusContainer = $(prometheusContainer);
const hasMetrics = $prometheusContainer.data('has-metrics');
this.docLink = $prometheusContainer.data('doc-link');
this.integrationLink = $prometheusContainer.data('prometheus-integration');
$(document).ajaxError(() => {});
if (hasMetrics) {
this.margin = { top: 80, right: 180, bottom: 80, left: 100 };
this.marginLabelContainer = { top: 40, right: 0, bottom: 40, left: 0 };
const parentContainerWidth = $(prometheusGraphsContainer).parent().width() +
extraAddedWidthParent;
this.originalWidth = parentContainerWidth;
this.originalHeight = 330;
this.width = parentContainerWidth - this.margin.left - this.margin.right;
this.height = this.originalHeight - this.margin.top - this.margin.bottom;
this.backOffRequestCounter = 0;
this.configureGraph();
this.init();
} else {
this.state = '.js-getting-started';
this.updateState();
}
}
createGraph() {
......@@ -40,8 +54,19 @@ class PrometheusGraph {
init() {
this.getData().then((metricsResponse) => {
if (Object.keys(metricsResponse).length === 0) {
new Flash('Empty metrics', 'alert');
let enoughData = true;
Object.keys(metricsResponse.metrics).forEach((key) => {
let currentKey;
if (key === 'cpu_values' || key === 'memory_values') {
currentKey = metricsResponse.metrics[key];
if (Object.keys(currentKey).length === 0) {
enoughData = false;
}
}
});
if (!enoughData) {
this.state = '.js-loading';
this.updateState();
} else {
this.transformData(metricsResponse);
this.createGraph();
......@@ -345,14 +370,17 @@ class PrometheusGraph {
}
return resp.metrics;
})
.catch(() => new Flash('An error occurred while fetching metrics.', 'alert'));
.catch(() => {
this.state = '.js-unable-to-connect';
this.updateState();
});
}
transformData(metricsResponse) {
Object.keys(metricsResponse.metrics).forEach((key) => {
if (key === 'cpu_values' || key === 'memory_values') {
const metricValues = (metricsResponse.metrics[key])[0];
if (typeof metricValues !== 'undefined') {
if (metricValues !== undefined) {
this.graphSpecificProperties[key].data = metricValues.values.map(metric => ({
time: new Date(metric[0] * 1000),
value: metric[1],
......@@ -361,6 +389,13 @@ class PrometheusGraph {
}
});
}
updateState() {
const $statesContainer = $(prometheusStatesContainer);
$(prometheusParentGraphContainer).hide();
$(`${this.state}`, $statesContainer).removeClass('hidden');
$(prometheusStatesContainer).show();
}
}
export default PrometheusGraph;
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */
/* global Mousetrap */
/* global findFileURL */
import findAndFollowLink from './shortcuts_dashboard_navigation';
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
......@@ -14,11 +15,33 @@
}
Mousetrap.bind('?', this.onToggleHelp);
Mousetrap.bind('s', Shortcuts.focusSearch);
Mousetrap.bind('f', (function(_this) {
return function(e) {
return _this.focusFilter(e);
};
})(this));
Mousetrap.bind('f', (e => this.focusFilter(e)));
const $globalDropdownMenu = $('.global-dropdown-menu');
const $globalDropdownToggle = $('.global-dropdown-toggle');
$('.global-dropdown').on('hide.bs.dropdown', () => {
$globalDropdownMenu.removeClass('shortcuts');
});
Mousetrap.bind('n', () => {
$globalDropdownMenu.toggleClass('shortcuts');
$globalDropdownToggle.trigger('click');
if (!$globalDropdownMenu.is(':visible')) {
$globalDropdownToggle.blur();
}
});
Mousetrap.bind('shift+t', () => findAndFollowLink('.shortcuts-todos'));
Mousetrap.bind('shift+a', () => findAndFollowLink('.dashboard-shortcuts-activity'));
Mousetrap.bind('shift+i', () => findAndFollowLink('.dashboard-shortcuts-issues'));
Mousetrap.bind('shift+m', () => findAndFollowLink('.dashboard-shortcuts-merge_requests'));
Mousetrap.bind('shift+p', () => findAndFollowLink('.dashboard-shortcuts-projects'));
Mousetrap.bind('shift+g', () => findAndFollowLink('.dashboard-shortcuts-groups'));
Mousetrap.bind('shift+l', () => findAndFollowLink('.dashboard-shortcuts-milestones'));
Mousetrap.bind('shift+s', () => findAndFollowLink('.dashboard-shortcuts-snippets'));
Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], this.toggleMarkdownPreview);
if (typeof findFileURL !== "undefined" && findFileURL !== null) {
Mousetrap.bind('t', function() {
......
/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign */
/* global Mousetrap */
/* global Shortcuts */
require('./shortcuts');
(function() {
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
this.ShortcutsDashboardNavigation = (function(superClass) {
extend(ShortcutsDashboardNavigation, superClass);
function ShortcutsDashboardNavigation() {
ShortcutsDashboardNavigation.__super__.constructor.call(this);
Mousetrap.bind('g a', function() {
return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-activity');
});
Mousetrap.bind('g i', function() {
return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-issues');
});
Mousetrap.bind('g m', function() {
return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-merge_requests');
});
Mousetrap.bind('g t', function() {
return ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-todos');
});
Mousetrap.bind('g p', function() {
return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-projects');
});
}
ShortcutsDashboardNavigation.findAndFollowLink = function(selector) {
var link;
link = $(selector).attr('href');
if (link) {
return window.location = link;
}
};
return ShortcutsDashboardNavigation;
})(Shortcuts);
}).call(window);
/**
* Helper function that finds the href of the fiven selector and updates the location.
*
* @param {String} selector
*/
export default (selector) => {
const link = document.querySelector(selector).getAttribute('href');
if (link) {
window.location = link;
}
};
/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign */
/* global Mousetrap */
/* global Shortcuts */
import findAndFollowLink from './shortcuts_dashboard_navigation';
require('./shortcuts');
......@@ -13,59 +14,23 @@ require('./shortcuts');
function ShortcutsNavigation() {
ShortcutsNavigation.__super__.constructor.call(this);
Mousetrap.bind('g p', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-project');
});
Mousetrap.bind('g e', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity');
});
Mousetrap.bind('g f', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-tree');
});
Mousetrap.bind('g c', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-commits');
});
Mousetrap.bind('g b', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-builds');
});
Mousetrap.bind('g n', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-network');
});
Mousetrap.bind('g g', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-repository-charts');
});
Mousetrap.bind('g i', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues');
});
Mousetrap.bind('g l', function() {
ShortcutsNavigation.findAndFollowLink('.shortcuts-issue-boards');
});
Mousetrap.bind('g m', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests');
});
Mousetrap.bind('g t', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-todos');
});
Mousetrap.bind('g w', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki');
});
Mousetrap.bind('g s', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-snippets');
});
Mousetrap.bind('i', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-new-issue');
});
Mousetrap.bind('g p', () => findAndFollowLink('.shortcuts-project'));
Mousetrap.bind('g e', () => findAndFollowLink('.shortcuts-project-activity'));
Mousetrap.bind('g f', () => findAndFollowLink('.shortcuts-tree'));
Mousetrap.bind('g c', () => findAndFollowLink('.shortcuts-commits'));
Mousetrap.bind('g j', () => findAndFollowLink('.shortcuts-builds'));
Mousetrap.bind('g n', () => findAndFollowLink('.shortcuts-network'));
Mousetrap.bind('g d', () => findAndFollowLink('.shortcuts-repository-charts'));
Mousetrap.bind('g i', () => findAndFollowLink('.shortcuts-issues'));
Mousetrap.bind('g b', () => findAndFollowLink('.shortcuts-issue-boards'));
Mousetrap.bind('g m', () => findAndFollowLink('.shortcuts-merge_requests'));
Mousetrap.bind('g t', () => findAndFollowLink('.shortcuts-todos'));
Mousetrap.bind('g w', () => findAndFollowLink('.shortcuts-wiki'));
Mousetrap.bind('g s', () => findAndFollowLink('.shortcuts-snippets'));
Mousetrap.bind('i', () => findAndFollowLink('.shortcuts-new-issue'));
this.enabledHelp.push('.hidden-shortcut.project');
}
ShortcutsNavigation.findAndFollowLink = function(selector) {
var link;
link = $(selector).attr('href');
if (link) {
return window.location = link;
}
};
return ShortcutsNavigation;
})(Shortcuts);
}).call(window);
import Vue from 'vue';
import Visibility from 'visibilityjs';
import PipelinesService from './services/pipelines_service';
import eventHub from './event_hub';
import PipelinesTableComponent from '../vue_shared/components/pipelines_table';
......@@ -7,6 +8,7 @@ import EmptyState from './components/empty_state';
import ErrorState from './components/error_state';
import NavigationTabs from './components/navigation_tabs';
import NavigationControls from './components/nav_controls';
import Poll from '../lib/utils/poll';
export default {
props: {
......@@ -47,6 +49,7 @@ export default {
pagenum: 1,
isLoading: false,
hasError: false,
isMakingRequest: false,
};
},
......@@ -120,18 +123,49 @@ export default {
tagsPath: this.tagsPath,
};
},
pageParameter() {
return gl.utils.getParameterByName('page') || this.pagenum;
},
scopeParameter() {
return gl.utils.getParameterByName('scope') || this.apiScope;
},
},
created() {
this.service = new PipelinesService(this.endpoint);
this.fetchPipelines();
const poll = new Poll({
resource: this.service,
method: 'getPipelines',
data: { page: this.pageParameter, scope: this.scopeParameter },
successCallback: this.successCallback,
errorCallback: this.errorCallback,
notificationCallback: this.setIsMakingRequest,
});
if (!Visibility.hidden()) {
this.isLoading = true;
poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
poll.restart();
} else {
poll.stop();
}
});
eventHub.$on('refreshPipelines', this.fetchPipelines);
},
beforeUpdate() {
if (this.state.pipelines.length && this.$children) {
if (this.state.pipelines.length &&
this.$children &&
!this.isMakingRequest &&
!this.isLoading) {
this.store.startTimeAgoLoops.call(this, Vue);
}
},
......@@ -154,27 +188,35 @@ export default {
},
fetchPipelines() {
const pageNumber = gl.utils.getParameterByName('page') || this.pagenum;
const scope = gl.utils.getParameterByName('scope') || this.apiScope;
if (!this.isMakingRequest) {
this.isLoading = true;
this.isLoading = true;
return this.service.getPipelines(scope, pageNumber)
.then(resp => ({
headers: resp.headers,
body: resp.json(),
}))
.then((response) => {
this.store.storeCount(response.body.count);
this.store.storePipelines(response.body.pipelines);
this.store.storePagination(response.headers);
})
.then(() => {
this.isLoading = false;
})
.catch(() => {
this.hasError = true;
this.isLoading = false;
});
this.service.getPipelines({ scope: this.scopeParameter, page: this.pageParameter })
.then(response => this.successCallback(response))
.catch(() => this.errorCallback());
}
},
successCallback(resp) {
const response = {
headers: resp.headers,
body: resp.json(),
};
this.store.storeCount(response.body.count);
this.store.storePipelines(response.body.pipelines);
this.store.storePagination(response.headers);
this.isLoading = false;
},
errorCallback() {
this.hasError = true;
this.isLoading = false;
},
setIsMakingRequest(isMakingRequest) {
this.isMakingRequest = isMakingRequest;
},
},
......
......@@ -26,7 +26,8 @@ export default class PipelinesService {
this.pipelines = Vue.resource(endpoint);
}
getPipelines(scope, page) {
getPipelines(data = {}) {
const { scope, page } = data;
return this.pipelines.get({ scope, page });
}
......
......@@ -91,7 +91,7 @@
.award-menu-holder {
display: inline-block;
position: relative;
position: absolute;
.tooltip {
white-space: nowrap;
......@@ -117,11 +117,41 @@
&.active,
&:hover,
&:active {
&:active,
&.is-active {
background-color: $row-hover;
border-color: $row-hover-border;
box-shadow: none;
outline: 0;
.award-control-icon svg {
background: $award-emoji-positive-add-bg;
path {
fill: $award-emoji-positive-add-lines;
}
}
.award-control-icon-neutral {
opacity: 0;
}
.award-control-icon-positive {
opacity: 1;
transform: scale(1.15);
}
}
&.is-active {
.award-control-icon-positive {
opacity: 0;
transform: scale(1);
}
.award-control-icon-super-positive {
opacity: 1;
transform: scale(1);
}
}
&.btn {
......@@ -162,9 +192,33 @@
color: $border-gray-normal;
margin-top: 1px;
padding: 0 2px;
svg {
margin-bottom: 1px;
height: 18px;
width: 18px;
border-radius: 50%;
path {
fill: $border-gray-normal;
}
}
}
.award-control-icon-positive,
.award-control-icon-super-positive {
position: absolute;
left: 7px;
bottom: 9px;
opacity: 0;
@include transition(opacity, transform);
}
.award-control-text {
vertical-align: middle;
}
}
.note-awards .award-control-icon-positive {
left: 6px;
}
......@@ -187,6 +187,15 @@
}
}
.shortcut-mappings {
display: none;
}
&.shortcuts .shortcut-mappings {
display: inline-block;
margin-right: 5px;
}
ul {
margin: 0;
padding: 0;
......
......@@ -446,10 +446,8 @@
}
}
.filter-dropdown-item.dropdown-active {
.btn {
@extend %filter-dropdown-item-btn-hover;
}
.filter-dropdown-item.droplab-item-active .btn {
@extend %filter-dropdown-item-btn-hover;
}
.filter-dropdown-loading {
......
......@@ -16,6 +16,8 @@ body.modal-open {
overflow: hidden;
}
.modal .modal-dialog {
width: 860px;
@media (min-width: $screen-md-min) {
.modal-dialog {
width: 860px;
}
}
.timeline {
@include basic-list;
margin: 0;
padding: 0;
.timeline-entry {
padding: $gl-padding $gl-btn-padding 11px;
padding: $gl-padding $gl-btn-padding 14px;
border-color: $white-normal;
color: $gl-text-color;
border-bottom: 1px solid $border-white-light;
.timeline-entry-inner {
position: relative;
}
&:target {
background: $line-target-blue;
}
......
......@@ -296,6 +296,8 @@ $badge-color: $gl-text-color-secondary;
* Award emoji
*/
$award-emoji-menu-shadow: rgba(0,0,0,.175);
$award-emoji-positive-add-bg: #fed159;
$award-emoji-positive-add-lines: #bb9c13;
/*
* Search Box
......
......@@ -226,7 +226,7 @@
.card {
position: relative;
padding: 10px $gl-padding;
padding: 11px 10px 11px $gl-padding;
background: $white-light;
border-radius: $border-radius-default;
box-shadow: 0 1px 2px $issue-boards-card-shadow;
......@@ -246,6 +246,8 @@
}
.confidential-icon {
position: relative;
top: 1px;
margin-right: 5px;
}
}
......@@ -253,34 +255,43 @@
.card-title {
margin: 0;
font-size: 1em;
line-height: inherit;
a {
color: inherit;
color: $gl-text-color;
word-wrap: break-word;
margin-right: 2px;
}
}
.card-footer {
margin-top: 5px;
line-height: 25px;
.label {
margin-right: 5px;
font-size: (14px / $issue-boards-font-size) * 1em;
}
.card-header {
display: flex;
min-height: 20px;
.card-assignee {
margin-left: auto;
margin-right: 5px;
padding-left: 10px;
height: 20px;
}
.avatar {
margin-left: 0;
margin-right: 0;
margin: 0;
}
}
.card-footer {
margin: 0 0 5px;
.label {
margin-top: 5px;
margin-right: 6px;
}
}
.card-number {
margin-right: 5px;
font-size: 12px;
color: $gl-text-color-secondary;
}
.issue-boards-search {
......
......@@ -57,6 +57,37 @@
margin-right: 5px;
}
}
.truncated-info {
text-align: center;
border-bottom: 1px solid;
background-color: $black-transparent;
height: 45px;
&.affix {
top: 0;
}
// with sidebar
&.affix.sidebar-expanded {
right: 312px;
left: 22px;
}
// without sidebar
&.affix.sidebar-collapsed {
right: 20px;
left: 20px;
}
&.affix-top {
position: absolute;
top: 0;
margin: 0 auto;
right: 5px;
left: 5px;
}
}
}
.scroll-controls {
......@@ -186,6 +217,7 @@
white-space: pre;
overflow-x: auto;
font-size: 12px;
position: relative;
.fa-refresh {
font-size: 24px;
......
......@@ -383,6 +383,15 @@
stroke-width: 1;
}
.prometheus-state {
margin-top: 10px;
display: none;
.state-button-section {
margin-top: 10px;
}
}
.environments-actions {
.external-url,
.monitoring-url,
......
......@@ -4,14 +4,14 @@
*/
.event-item {
font-size: $gl-font-size;
padding: $gl-padding-top 0 $gl-padding-top ($gl-avatar-size + $gl-padding-top);
padding: $gl-padding-top 0 $gl-padding-top 40px;
border-bottom: 1px solid $white-normal;
color: $list-text-color;
position: relative;
&.event-inline {
.avatar {
position: relative;
top: -2px;
.profile-icon {
top: 20px;
}
.event-title,
......@@ -24,8 +24,28 @@
color: $gl-text-color;
}
.avatar {
margin-left: -($gl-avatar-size + $gl-padding-top);
.profile-icon {
position: absolute;
left: 0;
top: 14px;
svg {
width: 20px;
height: auto;
fill: $gl-text-color-secondary;
}
&.open-icon svg {
fill: $green-300;
}
&.closed-icon svg {
fill: $red-300;
}
&.fork-icon svg {
fill: $blue-300;
}
}
.event-title {
......@@ -163,7 +183,7 @@
max-width: 100%;
}
.avatar {
.profile-icon {
display: none;
}
......
......@@ -196,6 +196,7 @@
transition: width .3s;
background: $gray-light;
padding: 10px 20px;
z-index: 2;
&.right-sidebar-expanded {
width: $gutter_width;
......
......@@ -329,8 +329,6 @@
}
#modal_merge_info .modal-dialog {
width: 600px;
.dark {
margin-right: 40px;
}
......
......@@ -16,6 +16,15 @@ ul.notes {
.timeline-icon {
float: left;
svg {
width: 18px;
height: auto;
fill: $gray-darkest;
position: absolute;
left: 30px;
top: 15px;
}
}
.timeline-content {
......@@ -33,6 +42,103 @@ ul.notes {
white-space: nowrap;
}
.discussion-body {
padding-top: 15px;
}
.discussion {
overflow: hidden;
display: block;
position: relative;
}
.note {
display: block;
position: relative;
border-bottom: 1px solid $white-normal;
&.note-discussion {
&.timeline-entry {
padding: 14px 10px;
}
.system-note {
padding: 0;
}
}
&.is-editting {
.note-header,
.note-text,
.edited-text {
display: none;
}
.note-edit-form {
display: block;
&.current-note-edit-form + .note-awards {
display: none;
}
}
}
.note-body {
overflow-x: auto;
overflow-y: hidden;
.note-text {
word-wrap: break-word;
@include md-typography;
// Reset ul style types since we're nested inside a ul already
@include bulleted-list;
ul.task-list {
ul:not(.task-list) {
padding-left: 1.3em;
}
}
}
}
.note-awards {
.js-awards-block {
padding: 2px;
margin-top: 10px;
}
}
.note-header {
padding-bottom: 3px;
padding-right: 20px;
@media (min-width: $screen-sm-min) {
padding-right: 0;
}
@media (max-width: $screen-xs-min) {
.inline {
display: block;
}
}
}
.note-emoji-button {
.fa-spinner {
display: none;
}
&.is-loading {
.fa-smile-o {
display: none;
}
.fa-spinner {
display: inline-block;
}
}
}
}
.system-note {
font-size: 14px;
padding: 0;
......@@ -68,6 +174,10 @@ ul.notes {
padding: 14px 10px;
}
.note-header {
padding-bottom: 0;
}
.note-body {
overflow: hidden;
......@@ -130,116 +240,6 @@ ul.notes {
}
}
}
.timeline-icon {
display: none;
.avatar {
visibility: hidden;
.discussion-body & {
visibility: visible;
}
}
}
}
.discussion-body {
padding-top: 15px;
}
.discussion {
overflow: hidden;
display: block;
position: relative;
}
.note {
display: block;
position: relative;
border-bottom: 1px solid $white-normal;
&.note-discussion {
&.timeline-entry {
padding: 14px 10px;
}
.system-note {
padding: 0;
}
}
&.is-editting {
.note-header,
.note-text,
.edited-text {
display: none;
}
.note-edit-form {
display: block;
&.current-note-edit-form + .note-awards {
display: none;
}
}
}
.note-body {
overflow-x: auto;
overflow-y: hidden;
.note-text {
word-wrap: break-word;
@include md-typography;
// Reset ul style types since we're nested inside a ul already
@include bulleted-list;
ul.task-list {
ul:not(.task-list) {
padding-left: 1.3em;
}
}
}
}
.note-awards {
.js-awards-block {
padding: 2px;
margin-top: 10px;
}
}
.note-header {
padding-bottom: 3px;
padding-right: 20px;
@media (min-width: $screen-sm-min) {
padding-right: 0;
}
@media (max-width: $screen-xs-min) {
.inline {
display: block;
}
}
}
.note-emoji-button {
.fa-spinner {
display: none;
}
&.is-loading {
.fa-smile-o {
display: none;
}
.fa-spinner {
display: inline-block;
}
}
}
}
}
......@@ -398,13 +398,50 @@ ul.notes {
font-size: 17px;
}
&:hover {
svg {
height: 16px;
width: 16px;
fill: $gray-darkest;
vertical-align: text-top;
}
.award-control-icon-positive,
.award-control-icon-super-positive {
position: absolute;
margin-left: -20px;
opacity: 0;
}
&:hover,
&.is-active {
.danger-highlight {
color: $gl-text-red;
}
.link-highlight {
color: $gl-link-color;
svg {
fill: $gl-link-color;
}
}
.award-control-icon-neutral {
opacity: 0;
}
.award-control-icon-positive {
opacity: 1;
}
}
&.is-active {
.award-control-icon-positive {
opacity: 0;
}
.award-control-icon-super-positive {
opacity: 1;
}
}
}
......@@ -508,7 +545,6 @@ ul.notes {
}
.line-resolve-all-container {
.btn-group {
margin-left: -4px;
}
......@@ -537,7 +573,6 @@ ul.notes {
fill: $gray-darkest;
}
}
}
.line-resolve-all {
......
......@@ -230,6 +230,14 @@
font-size: 0;
}
.fade-right {
right: 0;
}
.fade-left {
left: 0;
}
@media (max-width: $screen-xs-max) {
.cover-block {
padding-top: 20px;
......
......@@ -790,10 +790,13 @@ a.allowed-to-push {
}
}
<<<<<<< HEAD
.disabled-item {
@extend .btn.disabled;
}
=======
>>>>>>> ce/master
.protected-tags-list {
.dropdown-menu-toggle {
width: 100%;
......
......@@ -6,6 +6,8 @@
}
.trigger-actions {
white-space: nowrap;
.btn {
margin-left: 10px;
}
......
......@@ -145,8 +145,6 @@
margin: 0;
}
#modal-remove-blob > .modal-dialog { width: 850px; }
.blob-upload-dropzone-previews {
text-align: center;
border: 2px;
......
......@@ -72,7 +72,9 @@ class Admin::GroupsController < Admin::ApplicationController
:name,
:path,
:request_access_enabled,
:visibility_level
:visibility_level,
:require_two_factor_authentication,
:two_factor_grace_period
]
end
......
......@@ -8,12 +8,12 @@ class ApplicationController < ActionController::Base
include PageLayoutHelper
include SentryHelper
include WorkhorseHelper
include EnforcesTwoFactorAuthentication
before_action :authenticate_user_from_private_token!
before_action :authenticate_user!
before_action :validate_user_service_ticket!
before_action :check_password_expiration
before_action :check_2fa_requirement
before_action :ldap_security_check
before_action :sentry_context
before_action :default_headers
......@@ -155,12 +155,6 @@ class ApplicationController < ActionController::Base
end
end
def check_2fa_requirement
if two_factor_authentication_required? && current_user && !current_user.two_factor_enabled? && !skip_two_factor?
redirect_to profile_two_factor_auth_path
end
end
def ldap_security_check
if current_user && current_user.requires_ldap_check?
return unless current_user.try_obtain_ldap_lease
......@@ -269,23 +263,6 @@ class ApplicationController < ActionController::Base
current_application_settings.import_sources.include?('gitlab_project')
end
def two_factor_authentication_required?
current_application_settings.require_two_factor_authentication
end
def two_factor_grace_period
current_application_settings.two_factor_grace_period
end
def two_factor_grace_period_expired?
date = current_user.otp_grace_period_started_at
date && (date + two_factor_grace_period.hours) < Time.current
end
def skip_two_factor?
session[:skip_tfa] && session[:skip_tfa] > Time.current
end
# U2F (universal 2nd factor) devices need a unique identifier for the application
# to perform authentication.
# https://developers.yubico.com/U2F/App_ID.html
......
# == EnforcesTwoFactorAuthentication
#
# Controller concern to enforce two-factor authentication requirements
#
# Upon inclusion, adds `check_two_factor_requirement` as a before_action,
# and makes `two_factor_grace_period_expired?` and `two_factor_skippable?`
# available as view helpers.
module EnforcesTwoFactorAuthentication
extend ActiveSupport::Concern
included do
before_action :check_two_factor_requirement
helper_method :two_factor_grace_period_expired?, :two_factor_skippable?
end
def check_two_factor_requirement
if two_factor_authentication_required? && current_user && !current_user.two_factor_enabled? && !skip_two_factor?
redirect_to profile_two_factor_auth_path
end
end
def two_factor_authentication_required?
current_application_settings.require_two_factor_authentication? ||
current_user.try(:require_two_factor_authentication_from_group?)
end
def two_factor_authentication_reason(global: -> {}, group: -> {})
if two_factor_authentication_required?
if current_application_settings.require_two_factor_authentication?
global.call
else
groups = current_user.expanded_groups_requiring_two_factor_authentication.reorder(name: :asc)
group.call(groups)
end
end
end
def two_factor_grace_period
periods = [current_application_settings.two_factor_grace_period]
periods << current_user.two_factor_grace_period if current_user.try(:require_two_factor_authentication_from_group?)
periods.min
end
def two_factor_grace_period_expired?
date = current_user.otp_grace_period_started_at
date && (date + two_factor_grace_period.hours) < Time.current
end
def two_factor_skippable?
two_factor_authentication_required? &&
!current_user.two_factor_enabled? &&
!two_factor_grace_period_expired?
end
def skip_two_factor?
session[:skip_two_factor] && session[:skip_two_factor] > Time.current
end
end
module RequiresHealthToken
extend ActiveSupport::Concern
included do
before_action :validate_health_check_access!
end
private
def validate_health_check_access!
render_404 unless token_valid?
end
def token_valid?
token = params[:token].presence || request.headers['TOKEN']
token.present? &&
ActiveSupport::SecurityUtils.variable_size_secure_compare(
token,
current_application_settings.health_check_access_token
)
end
def render_404
render file: Rails.root.join('public', '404'), layout: false, status: '404'
end
end
......@@ -151,7 +151,9 @@ class GroupsController < Groups::ApplicationController
:visibility_level,
:parent_id,
:create_chat_team,
:chat_team_name
:chat_team_name,
:require_two_factor_authentication,
:two_factor_grace_period
]
end
......
class HealthCheckController < HealthCheck::HealthCheckController
before_action :validate_health_check_access!
private
def validate_health_check_access!
render_404 unless token_valid?
end
def token_valid?
token = params[:token].presence || request.headers['TOKEN']
token.present? &&
ActiveSupport::SecurityUtils.variable_size_secure_compare(
token,
current_application_settings.health_check_access_token
)
end
def render_404
render file: Rails.root.join('public', '404'), layout: false, status: '404'
end
include RequiresHealthToken
end
class HealthController < ActionController::Base
protect_from_forgery with: :exception
include RequiresHealthToken
CHECKS = [
Gitlab::HealthChecks::DbCheck,
Gitlab::HealthChecks::RedisCheck,
Gitlab::HealthChecks::FsShardsCheck,
].freeze
def readiness
results = CHECKS.map { |check| [check.name, check.readiness] }
render_check_results(results)
end
def liveness
results = CHECKS.map { |check| [check.name, check.liveness] }
render_check_results(results)
end
def metrics
results = CHECKS.flat_map(&:metrics)
response = results.map(&method(:metric_to_prom_line)).join("\n")
render text: response, content_type: 'text/plain; version=0.0.4'
end
private
def metric_to_prom_line(metric)
labels = metric.labels&.map { |key, value| "#{key}=\"#{value}\"" }&.join(',') || ''
if labels.empty?
"#{metric.name} #{metric.value}"
else
"#{metric.name}{#{labels}} #{metric.value}"
end
end
def render_check_results(results)
flattened = results.flat_map do |name, result|
if result.is_a?(Gitlab::HealthChecks::Result)
[[name, result]]
else
result.map { |r| [name, r] }
end
end
success = flattened.all? { |name, r| r.success }
response = flattened.map do |name, r|
info = { status: r.success ? 'ok' : 'failed' }
info['message'] = r.message if r.message
info[:labels] = r.labels if r.labels
[name, info]
end
render json: response.to_h, status: success ? :ok : :service_unavailable
end
end
class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
skip_before_action :check_2fa_requirement
skip_before_action :check_two_factor_requirement
def show
unless current_user.otp_secret
......@@ -13,11 +13,24 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
current_user.save! if current_user.changed?
if two_factor_authentication_required? && !current_user.two_factor_enabled?
if two_factor_grace_period_expired?
flash.now[:alert] = 'You must enable Two-Factor Authentication for your account.'
else
two_factor_authentication_reason(
global: lambda do
flash.now[:alert] =
'The global settings require you to enable Two-Factor Authentication for your account.'
end,
group: lambda do |groups|
group_links = groups.map { |group| view_context.link_to group.full_name, group_path(group) }.to_sentence
flash.now[:alert] = %{
The group settings for #{group_links} require you to enable
Two-Factor Authentication for your account.
}.html_safe
end
)
unless two_factor_grace_period_expired?
grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
flash.now[:alert] = "You must enable Two-Factor Authentication for your account before #{l(grace_period_deadline)}."
flash.now[:alert] << " You need to do this before #{l(grace_period_deadline)}."
end
end
......@@ -71,7 +84,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
if two_factor_grace_period_expired?
redirect_to new_profile_two_factor_auth_path, alert: 'Cannot skip two factor authentication setup'
else
session[:skip_tfa] = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
session[:skip_two_factor] = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
redirect_to root_path
end
end
......
......@@ -31,25 +31,25 @@ class Projects::BuildsController < Projects::ApplicationController
@builds = @project.pipelines.find_by_sha(@build.sha).builds.order('id DESC')
@builds = @builds.where("id not in (?)", @build.id)
@pipeline = @build.pipeline
respond_to do |format|
format.html
format.json do
render json: {
id: @build.id,
status: @build.status,
trace_html: @build.trace_html
}
end
end
end
def trace
respond_to do |format|
format.json do
state = params[:state].presence
render json: @build.trace_with_state(state: state).
merge!(id: @build.id, status: @build.status)
build.trace.read do |stream|
respond_to do |format|
format.json do
result = {
id: @build.id, status: @build.status, complete: @build.complete?
}
if stream.valid?
stream.limit
state = params[:state].presence
trace = stream.html_with_state(state)
result.merge!(trace.to_h)
end
render json: result
end
end
end
end
......@@ -86,10 +86,12 @@ class Projects::BuildsController < Projects::ApplicationController
end
def raw
if @build.has_trace_file?
send_file @build.trace_file_path, type: 'text/plain; charset=utf-8', disposition: 'inline'
else
render_404
build.trace.read do |stream|
if stream.file?
send_file stream.path, type: 'text/plain; charset=utf-8', disposition: 'inline'
else
render_404
end
end
end
......
......@@ -35,6 +35,8 @@ class Projects::CommitController < Projects::ApplicationController
respond_to do |format|
format.html
format.json do
Gitlab::PollingInterval.set_header(response, interval: 10_000)
render json: PipelineSerializer
.new(project: @project, user: @current_user)
.represent(@pipelines)
......
......@@ -236,6 +236,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
format.json do
Gitlab::PollingInterval.set_header(response, interval: 10_000)
render json: PipelineSerializer
.new(project: @project, user: @current_user)
.represent(@pipelines)
......@@ -249,6 +251,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
format.json do
define_pipelines_vars
Gitlab::PollingInterval.set_header(response, interval: 10_000)
render json: {
pipelines: PipelineSerializer
.new(project: @project, user: @current_user)
......@@ -478,7 +482,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
if pipeline
status = pipeline.status
coverage = pipeline.try(:coverage)
coverage = pipeline.coverage
status = "success_with_warnings" if pipeline.success? && pipeline.has_warnings?
......
......@@ -29,6 +29,8 @@ class Projects::PipelinesController < Projects::ApplicationController
respond_to do |format|
format.html
format.json do
Gitlab::PollingInterval.set_header(response, interval: 10_000)
render json: {
pipelines: PipelineSerializer
.new(project: @project, user: @current_user)
......@@ -114,7 +116,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def pipeline
@pipeline ||= project.pipelines.find_by!(id: params[:id])
@pipeline ||= project.pipelines.find_by!(id: params[:id]).present(current_user: current_user)
end
def commit
......
......@@ -23,7 +23,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
def update_params
params.require(:project).permit(
:runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
:public_builds
:public_builds, :auto_cancel_pending_pipelines
)
end
end
......@@ -3,6 +3,7 @@ class Projects::ProtectedBranchesController < Projects::ProtectedRefsController
def project_refs
@project.repository.branches
<<<<<<< HEAD
end
def create_service_class
......@@ -19,6 +20,20 @@ class Projects::ProtectedBranchesController < Projects::ProtectedRefsController
def access_levels
[:merge_access_levels, :push_access_levels]
=======
end
def create_service_class
::ProtectedBranches::CreateService
end
def update_service_class
::ProtectedBranches::UpdateService
end
def load_protected_ref
@protected_ref = @project.protected_branches.find(params[:id])
>>>>>>> ce/master
end
def protected_ref_params
......
......@@ -30,7 +30,11 @@ class Projects::ProtectedRefsController < Projects::ApplicationController
@protected_ref = update_service_class.new(@project, current_user, protected_ref_params).execute(@protected_ref)
if @protected_ref.valid?
<<<<<<< HEAD
render json: @protected_ref, status: :ok, include: access_levels
=======
render json: @protected_ref, status: :ok
>>>>>>> ce/master
else
render json: @protected_ref.errors, status: :unprocessable_entity
end
......
......@@ -17,10 +17,13 @@ class Projects::ProtectedTagsController < Projects::ProtectedRefsController
@protected_ref = @project.protected_tags.find(params[:id])
end
<<<<<<< HEAD
def access_levels
[:create_access_levels]
end
=======
>>>>>>> ce/master
def protected_ref_params
params.require(:protected_tag).permit(:name, create_access_levels_attributes: [:access_level, :id])
end
......
......@@ -8,13 +8,17 @@ module Projects
@deploy_keys = DeployKeysPresenter.new(@project, current_user: current_user)
define_protected_refs
<<<<<<< HEAD
project.create_push_rule unless project.push_rule
@push_rule = project.push_rule
=======
>>>>>>> ce/master
end
private
<<<<<<< HEAD
def remote_mirror
@remote_mirror = @project.remote_mirrors.first_or_initialize
end
......@@ -31,6 +35,18 @@ module Projects
{
selected_merge_access_levels: @protected_branch.merge_access_levels.map { |access_level| access_level.user_id || access_level.access_level },
selected_push_access_levels: @protected_branch.push_access_levels.map { |access_level| access_level.user_id || access_level.access_level },
=======
def define_protected_refs
@protected_branches = @project.protected_branches.order(:name).page(params[:page])
@protected_tags = @project.protected_tags.order(:name).page(params[:page])
@protected_branch = @project.protected_branches.new
@protected_tag = @project.protected_tags.new
load_gon_index
end
def access_levels_options
{
>>>>>>> ce/master
create_access_levels: levels_for_dropdown(ProtectedTag::CreateAccessLevel),
push_access_levels: levels_for_dropdown(ProtectedBranch::PushAccessLevel),
merge_access_levels: levels_for_dropdown(ProtectedBranch::MergeAccessLevel)
......@@ -56,7 +72,10 @@ module Projects
gon.push(protectable_tags_for_dropdown)
gon.push(protectable_branches_for_dropdown)
gon.push(access_levels_options)
<<<<<<< HEAD
gon.push(current_project_id: @project.id) if @project
=======
>>>>>>> ce/master
end
end
end
......
......@@ -3,7 +3,7 @@ class SessionsController < Devise::SessionsController
include Devise::Controllers::Rememberable
include Recaptcha::ClientHelper
skip_before_action :check_2fa_requirement, only: [:destroy]
skip_before_action :check_two_factor_requirement, only: [:destroy]
prepend_before_action :check_initial_setup, only: [:new]
prepend_before_action :authenticate_with_two_factor,
......
......@@ -68,18 +68,6 @@ module AuthHelper
current_user.identities.exists?(provider: provider.to_s)
end
def two_factor_skippable?
current_application_settings.require_two_factor_authentication &&
!current_user.two_factor_enabled? &&
current_application_settings.two_factor_grace_period &&
!two_factor_grace_period_expired?
end
def two_factor_grace_period_expired?
current_user.otp_grace_period_started_at &&
(current_user.otp_grace_period_started_at + current_application_settings.two_factor_grace_period.hours) < Time.current
end
def unlink_allowed?(provider)
%w(saml cas3).exclude?(provider.to_s)
end
......
......@@ -102,7 +102,7 @@ module BlobHelper
if Gitlab::MarkupHelper.previewable?(filename)
'Preview'
else
'Preview Changes'
'Preview changes'
end
end
......@@ -210,13 +210,13 @@ module BlobHelper
end
def copy_file_path_button(file_path)
clipboard_button(clipboard_text: file_path, class: 'btn-clipboard btn-transparent prepend-left-5', title: 'Copy file path to clipboard')
clipboard_button(text: file_path, gfm: "`#{file_path}`", class: 'btn-clipboard btn-transparent prepend-left-5', title: 'Copy file path to clipboard')
end
def copy_blob_content_button(blob)
return if markup?(blob.name)
clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{blob.id}']", class: "btn btn-sm", title: "Copy content to clipboard")
clipboard_button(target: ".blob-content[data-blob-id='#{blob.id}']", class: "btn btn-sm", title: "Copy content to clipboard")
end
def open_raw_file_button(path)
......
......@@ -33,6 +33,7 @@ module BranchesHelper
def protected_branch?(project, branch)
ProtectedBranch.protected?(project, branch.name)
end
<<<<<<< HEAD
def access_levels_data(access_levels)
access_levels.map do |level|
......@@ -52,4 +53,6 @@ module BranchesHelper
end
end
end
=======
>>>>>>> ce/master
end
module ButtonHelper
# Output a "Copy to Clipboard" button
#
# data - Data attributes passed to `content_tag`
# data - Data attributes passed to `content_tag` (default: {}):
# :text - Text to copy (optional)
# :gfm - GitLab Flavored Markdown to copy, if different from `text` (optional)
# :target - Selector for target element to copy from (optional)
#
# Examples:
#
# # Define the clipboard's text
# clipboard_button(clipboard_text: "Foo")
# clipboard_button(text: "Foo")
# # => "<button class='...' data-clipboard-text='Foo'>...</button>"
#
# # Define the target element
# clipboard_button(clipboard_target: "div#foo")
# clipboard_button(target: "div#foo")
# # => "<button class='...' data-clipboard-target='div#foo'>...</button>"
#
# See http://clipboardjs.com/#usage
def clipboard_button(data = {})
css_class = data[:class] || 'btn-clipboard btn-transparent'
title = data[:title] || 'Copy to clipboard'
# This supports code in app/assets/javascripts/copy_to_clipboard.js that
# works around ClipboardJS limitations to allow the context-specific copy/pasting of plain text or GFM.
if text = data.delete(:text)
data[:clipboard_text] =
if gfm = data.delete(:gfm)
{ text: text, gfm: gfm }
else
text
end
end
target = data.delete(:target)
data[:clipboard_target] = target if target
data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
content_tag :button,
icon('clipboard', 'aria-hidden': 'true'),
class: "btn #{css_class}",
......
module SystemNoteHelper
ICON_NAMES_BY_ACTION = {
'commit' => 'icon_commit',
'merge' => 'icon_merge',
'merged' => 'icon_merged',
'opened' => 'icon_status_open',
'closed' => 'icon_status_closed',
'time_tracking' => 'icon_stopwatch',
'assignee' => 'icon_user',
'title' => 'icon_pencil',
'task' => 'icon_check_square_o',
'label' => 'icon_tags',
'cross_reference' => 'icon_random',
'branch' => 'icon_code_fork',
'confidential' => 'icon_eye_slash',
'visible' => 'icon_eye',
'milestone' => 'icon_clock_o',
'discussion' => 'icon_comment_o',
'moved' => 'icon_arrow_circle_o_right'
}.freeze
def icon_for_system_note(note)
icon_name = ICON_NAMES_BY_ACTION[note.system_note_metadata&.action]
custom_icon(icon_name) if icon_name
end
end
This diff is collapsed.
This diff is collapsed.
......@@ -8,6 +8,7 @@ module Ci
belongs_to :owner, class_name: "User"
has_many :trigger_requests, dependent: :destroy
has_one :trigger_schedule, dependent: :destroy
validates :token, presence: true, uniqueness: true
......
module Ci
class TriggerSchedule < ActiveRecord::Base
extend Ci::Model
include Importable
acts_as_paranoid
belongs_to :project
belongs_to :trigger
delegate :ref, to: :trigger
validates :trigger, presence: { unless: :importing? }
validates :cron, cron: true, presence: { unless: :importing? }
validates :cron_timezone, cron_timezone: true, presence: { unless: :importing? }
validates :ref, presence: { unless: :importing? }
before_save :set_next_run_at
def set_next_run_at
self.next_run_at = Gitlab::Ci::CronParser.new(cron, cron_timezone).next_time_from(Time.now)
end
def schedule_next_run!
save! # with set_next_run_at
rescue ActiveRecord::RecordInvalid
update_attribute(:next_run_at, nil) # update without validation
end
end
end
......@@ -7,6 +7,7 @@ class CommitStatus < ActiveRecord::Base
belongs_to :project
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
belongs_to :user
delegate :commit, to: :pipeline
......@@ -137,6 +138,10 @@ class CommitStatus < ActiveRecord::Base
false
end
def auto_canceled?
canceled? && auto_canceled_by_id?
end
# Added in 9.0 to keep backward compatibility for projects exported in 8.17
# and prior.
def gl_project_id
......
......@@ -76,6 +76,7 @@ module HasStatus
scope :canceled, -> { where(status: 'canceled') }
scope :skipped, -> { where(status: 'skipped') }
scope :manual, -> { where(status: 'manual') }
scope :created_or_pending, -> { where(status: [:created, :pending]) }
scope :running_or_pending, -> { where(status: [:running, :pending]) }
scope :finished, -> { where(status: [:success, :failed, :canceled]) }
scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) }
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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