Commit 99b142bc authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into 25680-CI_ENVIRONMENT_URL

* upstream/master: (251 commits)
  Don't match email addresses or foo@bar as user references
  Revert "Update GITLAB_SHELL_VERSION"
  Update GITLAB_SHELL_VERSION
  Add feature toggles through Flipper
  Change no_limits to limits
  Move includes call to scope
  Add GitLab Resources to University
  Add Documentation for GIT_CHECKOUT variable
  Remove entry variable
  Do not try to preload Commits when using Note.includes(:noteable)
  Ui improvements for count badges and permission badges
  Rename the other jobs
  Update jobs_spec for changes from builds_spec
  Introduce source to pipeline entity
  Update docs related to protected actions
  Add changelog for protected branches abilities fix
  Ask for an example project for bug reports
  Center loading spinner in issuable filters
  Fix chat commands specs related to protected actions
  Fix builds controller specs related to protected actions
  ...
parents 9e60d57d f74f0338
---
engines:
brakeman:
enabled: true
bundler-audit:
enabled: true
duplication:
enabled: true
config:
languages:
- ruby
- javascript
eslint:
enabled: true
fixme:
enabled: true
rubocop:
enabled: true
ratings:
paths:
- Gemfile.lock
- "**.erb"
- "**.haml"
- "**.rb"
- "**.rhtml"
- "**.slim"
- "**.inc"
- "**.js"
- "**.jsx"
- "**.module"
exclude_paths:
- config/
- db/
- features/
- node_modules/
- spec/
- vendor/
- lib/api/v3/
......@@ -20,6 +20,12 @@ Please remove this notice if you're confident your issue isn't a duplicate.
(How one can reproduce the issue - this is very important)
### Example Project
(If possible, please create an example project here on GitLab.com that exhibits the problematic behaviour, and link to it here in the bug report)
(If you are using an older version of GitLab, this will also determine whether the bug has been fixed in a more recent version)
### What is the current *bug* behavior?
(What actually happens)
......
......@@ -57,7 +57,7 @@ linters:
# Reports when you define the same property twice in a single rule set.
DuplicateProperty:
enabled: false
enabled: true
# Separate rule, function, and mixin declarations with empty lines.
EmptyLineBetweenBlocks:
......
......@@ -109,7 +109,7 @@ gem 'seed-fu', '~> 2.3.5'
# Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0'
gem 'deckar01-task_list', '1.0.6', require: 'task_list/railtie'
gem 'deckar01-task_list', '2.0.0'
gem 'gitlab-markup', '~> 1.5.1'
gem 'redcarpet', '~> 3.4'
gem 'RedCloth', '~> 4.3.2'
......@@ -370,3 +370,7 @@ gem 'sys-filesystem', '~> 1.1.6'
gem 'gitaly', '~> 0.7.0'
gem 'toml-rb', '~> 0.3.15', require: false
# Feature toggles
gem 'flipper', '~> 0.10.2'
gem 'flipper-active_record', '~> 0.10.2'
......@@ -141,10 +141,8 @@ GEM
database_cleaner (1.5.3)
debug_inspector (0.0.2)
debugger-ruby_core_source (1.3.8)
deckar01-task_list (1.0.6)
activesupport (~> 4.0)
deckar01-task_list (2.0.0)
html-pipeline
rack (~> 1.0)
default_value_for (3.0.2)
activerecord (>= 3.2.0, < 5.1)
descendants_tracker (0.0.4)
......@@ -208,6 +206,10 @@ GEM
path_expander (~> 1.0)
ruby_parser (~> 3.0)
sexp_processor (~> 4.0)
flipper (0.10.2)
flipper-active_record (0.10.2)
activerecord (>= 3.2, < 6)
flipper (~> 0.10.2)
flowdock (0.7.1)
httparty (~> 0.7)
multi_json
......@@ -341,7 +343,7 @@ GEM
grape-entity (0.6.0)
activesupport
multi_json (>= 1.3.2)
grpc (1.3.4)
grpc (1.2.5)
google-protobuf (~> 3.1)
googleauth (~> 0.5.1)
haml (4.0.7)
......@@ -499,11 +501,10 @@ GEM
omniauth (~> 1.0)
omniauth-oauth2 (~> 1.0)
omniauth-google-oauth2 (0.4.1)
addressable (~> 2.3)
jwt (~> 1.0)
jwt (~> 1.5.2)
multi_json (~> 1.3)
omniauth (>= 1.1.1)
omniauth-oauth2 (~> 1.3.1)
omniauth-oauth2 (>= 1.3.1)
omniauth-kerberos (0.3.0)
omniauth-multipassword
timfel-krb5-auth (~> 0.8)
......@@ -896,7 +897,7 @@ DEPENDENCIES
creole (~> 0.5.0)
d3_rails (~> 3.5.0)
database_cleaner (~> 1.5.0)
deckar01-task_list (= 1.0.6)
deckar01-task_list (= 2.0.0)
default_value_for (~> 3.0.0)
devise (~> 4.2)
devise-two-factor (~> 3.0.0)
......@@ -910,6 +911,8 @@ DEPENDENCIES
faraday (~> 0.11.0)
ffaker (~> 2.4)
flay (~> 2.8.0)
flipper (~> 0.10.2)
flipper-active_record (~> 0.10.2)
fog-aws (~> 0.9)
fog-core (~> 1.44)
fog-google (~> 0.5)
......@@ -1060,4 +1063,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
1.14.6
1.15.0
......@@ -111,7 +111,7 @@ export default class BlobViewer {
BlobViewer.loadViewer(newViewer)
.then((viewer) => {
$(viewer).syntaxHighlight();
$(viewer).renderGFM();
this.$fileHolder.trigger('highlight:line');
gl.utils.handleLocationHash();
......
......@@ -70,6 +70,7 @@ $(() => {
gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId);
this.filterManager = new FilteredSearchBoards(Store.filter, true);
this.filterManager.setup();
// Listen for updateTokens event
eventHub.$on('updateTokens', this.updateTokens);
......
......@@ -13,6 +13,7 @@ export default {
FilteredSearchContainer.container = this.$el;
this.filteredSearch = new FilteredSearchBoards(this.store);
this.filteredSearch.setup();
this.filteredSearch.removeTokens();
this.filteredSearch.handleInputPlaceholder();
this.filteredSearch.toggleClearSearchButton();
......
......@@ -2,7 +2,7 @@
import FilteredSearchContainer from '../filtered_search/container';
export default class FilteredSearchBoards extends gl.FilteredSearchManager {
constructor(store, updateUrl = false) {
constructor(store, updateUrl = false, cantEdit = []) {
super('boards');
this.store = store;
......@@ -11,6 +11,8 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
// Issue boards is slightly different, we handle all the requests async
// instead or reloading the page, we just re-fire the list ajax requests
this.isHandledAsync = true;
this.cantEdit = cantEdit;
}
updateObject(path) {
......@@ -40,4 +42,8 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
// Get the placeholder back if search is empty
this.filteredSearchInput.dispatchEvent(new Event('input'));
}
canEdit(tokenName) {
return this.cantEdit.indexOf(tokenName) === -1;
}
}
......@@ -2,15 +2,11 @@
consistent-return, prefer-rest-params */
/* global Breakpoints */
import _ from 'underscore';
import { bytesToKiB } from './lib/utils/number_utils';
const bind = function (fn, me) { return function () { return fn.apply(me, arguments); }; };
const AUTO_SCROLL_OFFSET = 75;
const DOWN_BUILD_TRACE = '#down-build-trace';
window.Build = (function () {
Build.timeout = null;
Build.state = null;
function Build(options) {
......@@ -23,21 +19,22 @@ window.Build = (function () {
this.buildStage = this.options.buildStage;
this.$document = $(document);
this.logBytes = 0;
this.scrollOffsetPadding = 30;
this.updateDropdown = bind(this.updateDropdown, this);
this.updateDropdown = this.updateDropdown.bind(this);
this.getBuildTrace = this.getBuildTrace.bind(this);
this.scrollToBottom = this.scrollToBottom.bind(this);
this.$body = $('body');
this.$buildTrace = $('#build-trace');
this.$autoScrollContainer = $('.autoscroll-container');
this.$autoScrollStatus = $('#autoscroll-status');
this.$autoScrollStatusText = this.$autoScrollStatus.find('.status-text');
this.$upBuildTrace = $('#up-build-trace');
this.$downBuildTrace = $(DOWN_BUILD_TRACE);
this.$scrollTopBtn = $('#scroll-top');
this.$scrollBottomBtn = $('#scroll-bottom');
this.$buildRefreshAnimation = $('.js-build-refresh');
this.$buildScroll = $('#js-build-scroll');
this.$truncatedInfo = $('.js-truncated-info');
this.$buildTraceOutput = $('.js-build-output');
this.$scrollContainer = $('.js-scroll-container');
// Scroll controllers
this.$scrollTopBtn = $('.js-scroll-up');
this.$scrollBottomBtn = $('.js-scroll-down');
clearTimeout(Build.timeout);
// Init breakpoint checker
......@@ -56,54 +53,149 @@ window.Build = (function () {
.off('click', '.stage-item')
.on('click', '.stage-item', this.updateDropdown);
this.$document.on('scroll', this.initScrollMonitor.bind(this));
// add event listeners to the scroll buttons
this.$scrollTopBtn
.off('click')
.on('click', this.scrollToTop.bind(this));
this.$scrollBottomBtn
.off('click')
.on('click', this.scrollToBottom.bind(this));
$(window)
.off('resize.build')
.on('resize.build', this.sidebarOnResize.bind(this));
$('a', this.$buildScroll)
.off('click.stepTrace')
.on('click.stepTrace', this.stepTrace);
this.updateArtifactRemoveDate();
this.initScrollButtonAffix();
this.invokeBuildTrace();
// eslint-disable-next-line
this.getBuildTrace()
.then(() => this.makeTraceScrollable())
.then(() => this.scrollToBottom());
this.verifyTopPosition();
}
Build.prototype.makeTraceScrollable = function () {
this.$scrollContainer.niceScroll({
cursorcolor: '#fff',
cursoropacitymin: 1,
cursorwidth: '3px',
railpadding: { top: 5, bottom: 5, right: 5 },
});
this.$scrollContainer.on('scroll', _.throttle(this.toggleScroll.bind(this), 100));
this.toggleScroll();
};
Build.prototype.canScroll = function () {
return (this.$scrollContainer.prop('scrollHeight') - this.scrollOffsetPadding) > this.$scrollContainer.height();
};
/**
* | | Up | Down |
* |--------------------------|----------|----------|
* | on scroll bottom | active | disabled |
* | on scroll top | disabled | active |
* | no scroll | disabled | disabled |
* | on.('scroll') is on top | disabled | active |
* | on('scroll) is on bottom | active | disabled |
*
*/
Build.prototype.toggleScroll = function () {
const bottomScroll = this.$scrollContainer.scrollTop() +
this.scrollOffsetPadding +
this.$scrollContainer.height();
if (this.canScroll()) {
if (this.$scrollContainer.scrollTop() === 0) {
this.toggleDisableButton(this.$scrollTopBtn, true);
this.toggleDisableButton(this.$scrollBottomBtn, false);
} else if (bottomScroll === this.$scrollContainer.prop('scrollHeight')) {
this.toggleDisableButton(this.$scrollTopBtn, false);
this.toggleDisableButton(this.$scrollBottomBtn, true);
} else {
this.toggleDisableButton(this.$scrollTopBtn, false);
this.toggleDisableButton(this.$scrollBottomBtn, false);
}
}
};
Build.prototype.scrollToTop = function () {
this.$scrollContainer.getNiceScroll(0).doScrollTop(0);
this.toggleScroll();
};
Build.prototype.scrollToBottom = function () {
this.$scrollContainer.getNiceScroll(0).doScrollTo(this.$scrollContainer.prop('scrollHeight'));
this.toggleScroll();
};
Build.prototype.toggleDisableButton = function ($button, disable) {
if (disable && $button.prop('disabled')) return;
$button.prop('disabled', disable);
};
Build.prototype.toggleScrollAnimation = function (toggle) {
this.$scrollBottomBtn.toggleClass('animate', toggle);
};
/**
* Build trace top position depends on the space ocupied by the elments rendered before
*/
Build.prototype.verifyTopPosition = function () {
const $buildPage = $('.build-page');
const $header = $('.build-header', $buildPage);
const $runnersStuck = $('.js-build-stuck', $buildPage);
const $startsEnvironment = $('.js-environment-container', $buildPage);
const $erased = $('.js-build-erased', $buildPage);
let topPostion = 168;
if ($header) {
topPostion += $header.outerHeight();
}
if ($runnersStuck) {
topPostion += $runnersStuck.outerHeight();
}
if ($startsEnvironment) {
topPostion += $startsEnvironment.outerHeight();
}
if ($erased) {
topPostion += $erased.outerHeight() + 10;
}
this.$buildTrace.css({
top: topPostion,
});
};
Build.prototype.initSidebar = function () {
this.$sidebar = $('.js-build-sidebar');
this.$sidebar.niceScroll();
this.$document
.off('click', '.js-sidebar-build-toggle')
.on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
};
Build.prototype.invokeBuildTrace = function () {
return this.getBuildTrace();
};
Build.prototype.getBuildTrace = function () {
return $.ajax({
url: `${this.pageUrl}/trace.json`,
dataType: 'json',
data: {
state: this.state,
},
success: ((log) => {
const $buildContainer = $('.js-build-output');
data: this.state,
})
.done((log) => {
gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`);
if (log.state) {
this.state = log.state;
}
if (log.append) {
$buildContainer.append(log.html);
this.$buildTraceOutput.append(log.html);
this.logBytes += log.size;
} else {
$buildContainer.html(log.html);
this.$buildTraceOutput.html(log.html);
this.logBytes = log.size;
}
......@@ -114,141 +206,30 @@ window.Build = (function () {
const size = bytesToKiB(this.logBytes);
$('.js-truncated-info-size').html(`${size}`);
this.$truncatedInfo.removeClass('hidden');
this.initAffixTruncatedInfo();
} else {
this.$truncatedInfo.addClass('hidden');
}
this.checkAutoscroll();
if (!log.complete) {
this.toggleScrollAnimation(true);
Build.timeout = setTimeout(() => {
this.invokeBuildTrace();
//eslint-disable-next-line
this.getBuildTrace()
.then(() => this.scrollToBottom());
}, 4000);
} else {
this.$buildRefreshAnimation.remove();
this.toggleScrollAnimation(false);
}
if (log.status !== this.buildStatus) {
let pageUrl = this.pageUrl;
if (this.$autoScrollStatus.data('state') === 'enabled') {
pageUrl += DOWN_BUILD_TRACE;
}
gl.utils.visitUrl(pageUrl);
gl.utils.visitUrl(this.pageUrl);
}
}),
error: () => {
})
.fail(() => {
this.$buildRefreshAnimation.remove();
return this.initScrollMonitor();
},
});
};
Build.prototype.checkAutoscroll = function () {
if (this.$autoScrollStatus.data('state') === 'enabled') {
return $('html,body').scrollTop(this.$buildTrace.height());
}
// Handle a situation where user started new build
// but never scrolled a page
if (!this.$scrollTopBtn.is(':visible') &&
!this.$scrollBottomBtn.is(':visible') &&
!gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
this.$scrollBottomBtn.show();
}
};
Build.prototype.initScrollButtonAffix = function () {
// Hide everything initially
this.$scrollTopBtn.hide();
this.$scrollBottomBtn.hide();
this.$autoScrollContainer.hide();
};
// Page scroll listener to detect if user has scrolling page
// and handle following cases
// 1) User is at Top of Build Log;
// - Hide Top Arrow button
// - Show Bottom Arrow button
// - Disable Autoscroll and hide indicator (when build is running)
// 2) User is at Bottom of Build Log;
// - Show Top Arrow button
// - Hide Bottom Arrow button
// - Enable Autoscroll and show indicator (when build is running)
// 3) User is somewhere in middle of Build Log;
// - Show Top Arrow button
// - Show Bottom Arrow button
// - Disable Autoscroll and hide indicator (when build is running)
Build.prototype.initScrollMonitor = function () {
if (!gl.utils.isInViewport(this.$upBuildTrace.get(0)) &&
!gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
// User is somewhere in middle of Build Log
this.$scrollTopBtn.show();
if (this.buildStatus === 'success' || this.buildStatus === 'failed') { // Check if Build is completed
this.$scrollBottomBtn.show();
} else if (this.$buildRefreshAnimation.is(':visible') &&
!gl.utils.isInViewport(this.$buildRefreshAnimation.get(0))) {
this.$scrollBottomBtn.show();
} else {
this.$scrollBottomBtn.hide();
}
// Hide Autoscroll Status Indicator
if (this.$scrollBottomBtn.is(':visible')) {
this.$autoScrollContainer.hide();
this.$autoScrollStatusText.removeClass('animate');
} else {
this.$autoScrollContainer.css({
top: this.$body.outerHeight() - AUTO_SCROLL_OFFSET,
}).show();
this.$autoScrollStatusText.addClass('animate');
}
} else if (gl.utils.isInViewport(this.$upBuildTrace.get(0)) &&
!gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
// User is at Top of Build Log
this.$scrollTopBtn.hide();
this.$scrollBottomBtn.show();
this.$autoScrollContainer.hide();
this.$autoScrollStatusText.removeClass('animate');
} else if ((!gl.utils.isInViewport(this.$upBuildTrace.get(0)) &&
gl.utils.isInViewport(this.$downBuildTrace.get(0))) ||
(this.$buildRefreshAnimation.is(':visible') &&
gl.utils.isInViewport(this.$buildRefreshAnimation.get(0)))) {
// User is at Bottom of Build Log
this.$scrollTopBtn.show();
this.$scrollBottomBtn.hide();
// Show and Reposition Autoscroll Status Indicator
this.$autoScrollContainer.css({
top: this.$body.outerHeight() - AUTO_SCROLL_OFFSET,
}).show();
this.$autoScrollStatusText.addClass('animate');
} else if (gl.utils.isInViewport(this.$upBuildTrace.get(0)) &&
gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
// Build Log height is small
this.$scrollTopBtn.hide();
this.$scrollBottomBtn.hide();
// Hide Autoscroll Status Indicator
this.$autoScrollContainer.hide();
this.$autoScrollStatusText.removeClass('animate');
}
if (this.buildStatus === 'running' || this.buildStatus === 'pending') {
// Check if Refresh Animation is in Viewport and enable Autoscroll, disable otherwise.
this.$autoScrollStatus.data(
'state',
gl.utils.isInViewport(this.$buildRefreshAnimation.get(0)) ? 'enabled' : 'disabled',
);
}
});
};
Build.prototype.shouldHideSidebarForViewport = function () {
......@@ -257,18 +238,23 @@ window.Build = (function () {
};
Build.prototype.toggleSidebar = function (shouldHide) {
const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
const shouldShow = !shouldHide;
this.$buildScroll.toggleClass('sidebar-expanded', shouldShow)