Update looks job log
This commit is contained in:
parent
763a3acd03
commit
cd023d42e0
|
@ -2,15 +2,11 @@
|
||||||
consistent-return, prefer-rest-params */
|
consistent-return, prefer-rest-params */
|
||||||
/* global Breakpoints */
|
/* global Breakpoints */
|
||||||
|
|
||||||
|
import _ from 'underscore';
|
||||||
import { bytesToKiB } from './lib/utils/number_utils';
|
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 () {
|
window.Build = (function () {
|
||||||
Build.timeout = null;
|
Build.timeout = null;
|
||||||
|
|
||||||
Build.state = null;
|
Build.state = null;
|
||||||
|
|
||||||
function Build(options) {
|
function Build(options) {
|
||||||
|
@ -23,21 +19,22 @@ window.Build = (function () {
|
||||||
this.buildStage = this.options.buildStage;
|
this.buildStage = this.options.buildStage;
|
||||||
this.$document = $(document);
|
this.$document = $(document);
|
||||||
this.logBytes = 0;
|
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.$body = $('body');
|
||||||
this.$buildTrace = $('#build-trace');
|
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.$buildRefreshAnimation = $('.js-build-refresh');
|
||||||
this.$buildScroll = $('#js-build-scroll');
|
|
||||||
this.$truncatedInfo = $('.js-truncated-info');
|
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);
|
clearTimeout(Build.timeout);
|
||||||
// Init breakpoint checker
|
// Init breakpoint checker
|
||||||
|
@ -56,54 +53,149 @@ window.Build = (function () {
|
||||||
.off('click', '.stage-item')
|
.off('click', '.stage-item')
|
||||||
.on('click', '.stage-item', this.updateDropdown);
|
.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)
|
$(window)
|
||||||
.off('resize.build')
|
.off('resize.build')
|
||||||
.on('resize.build', this.sidebarOnResize.bind(this));
|
.on('resize.build', this.sidebarOnResize.bind(this));
|
||||||
|
|
||||||
$('a', this.$buildScroll)
|
|
||||||
.off('click.stepTrace')
|
|
||||||
.on('click.stepTrace', this.stepTrace);
|
|
||||||
|
|
||||||
this.updateArtifactRemoveDate();
|
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 () {
|
Build.prototype.initSidebar = function () {
|
||||||
this.$sidebar = $('.js-build-sidebar');
|
this.$sidebar = $('.js-build-sidebar');
|
||||||
this.$sidebar.niceScroll();
|
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 () {
|
Build.prototype.getBuildTrace = function () {
|
||||||
return $.ajax({
|
return $.ajax({
|
||||||
url: `${this.pageUrl}/trace.json`,
|
url: `${this.pageUrl}/trace.json`,
|
||||||
dataType: 'json',
|
data: this.state,
|
||||||
data: {
|
})
|
||||||
state: this.state,
|
.done((log) => {
|
||||||
},
|
|
||||||
success: ((log) => {
|
|
||||||
const $buildContainer = $('.js-build-output');
|
|
||||||
|
|
||||||
gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`);
|
gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`);
|
||||||
|
|
||||||
if (log.state) {
|
if (log.state) {
|
||||||
this.state = log.state;
|
this.state = log.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (log.append) {
|
if (log.append) {
|
||||||
$buildContainer.append(log.html);
|
this.$buildTraceOutput.append(log.html);
|
||||||
this.logBytes += log.size;
|
this.logBytes += log.size;
|
||||||
} else {
|
} else {
|
||||||
$buildContainer.html(log.html);
|
this.$buildTraceOutput.html(log.html);
|
||||||
this.logBytes = log.size;
|
this.logBytes = log.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,141 +206,30 @@ window.Build = (function () {
|
||||||
const size = bytesToKiB(this.logBytes);
|
const size = bytesToKiB(this.logBytes);
|
||||||
$('.js-truncated-info-size').html(`${size}`);
|
$('.js-truncated-info-size').html(`${size}`);
|
||||||
this.$truncatedInfo.removeClass('hidden');
|
this.$truncatedInfo.removeClass('hidden');
|
||||||
this.initAffixTruncatedInfo();
|
|
||||||
} else {
|
} else {
|
||||||
this.$truncatedInfo.addClass('hidden');
|
this.$truncatedInfo.addClass('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.checkAutoscroll();
|
|
||||||
|
|
||||||
if (!log.complete) {
|
if (!log.complete) {
|
||||||
|
this.toggleScrollAnimation(true);
|
||||||
|
|
||||||
Build.timeout = setTimeout(() => {
|
Build.timeout = setTimeout(() => {
|
||||||
this.invokeBuildTrace();
|
//eslint-disable-next-line
|
||||||
|
this.getBuildTrace()
|
||||||
|
.then(() => this.scrollToBottom());
|
||||||
}, 4000);
|
}, 4000);
|
||||||
} else {
|
} else {
|
||||||
this.$buildRefreshAnimation.remove();
|
this.$buildRefreshAnimation.remove();
|
||||||
|
this.toggleScrollAnimation(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (log.status !== this.buildStatus) {
|
if (log.status !== this.buildStatus) {
|
||||||
let pageUrl = this.pageUrl;
|
gl.utils.visitUrl(this.pageUrl);
|
||||||
|
|
||||||
if (this.$autoScrollStatus.data('state') === 'enabled') {
|
|
||||||
pageUrl += DOWN_BUILD_TRACE;
|
|
||||||
}
|
|
||||||
|
|
||||||
gl.utils.visitUrl(pageUrl);
|
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
error: () => {
|
.fail(() => {
|
||||||
this.$buildRefreshAnimation.remove();
|
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 () {
|
Build.prototype.shouldHideSidebarForViewport = function () {
|
||||||
|
@ -257,18 +238,23 @@ window.Build = (function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
Build.prototype.toggleSidebar = function (shouldHide) {
|
Build.prototype.toggleSidebar = function (shouldHide) {
|
||||||
const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
|
const shouldShow = !shouldHide;
|
||||||
|
|
||||||
this.$buildScroll.toggleClass('sidebar-expanded', shouldShow)
|
this.$buildTrace
|
||||||
|
.toggleClass('sidebar-expanded', shouldShow)
|
||||||
.toggleClass('sidebar-collapsed', shouldHide);
|
.toggleClass('sidebar-collapsed', shouldHide);
|
||||||
this.$truncatedInfo.toggleClass('sidebar-expanded', shouldShow)
|
this.$sidebar
|
||||||
.toggleClass('sidebar-collapsed', shouldHide);
|
.toggleClass('right-sidebar-expanded', shouldShow)
|
||||||
this.$sidebar.toggleClass('right-sidebar-expanded', shouldShow)
|
|
||||||
.toggleClass('right-sidebar-collapsed', shouldHide);
|
.toggleClass('right-sidebar-collapsed', shouldHide);
|
||||||
};
|
};
|
||||||
|
|
||||||
Build.prototype.sidebarOnResize = function () {
|
Build.prototype.sidebarOnResize = function () {
|
||||||
this.toggleSidebar(this.shouldHideSidebarForViewport());
|
this.toggleSidebar(this.shouldHideSidebarForViewport());
|
||||||
|
this.verifyTopPosition();
|
||||||
|
|
||||||
|
if (this.$scrollContainer.getNiceScroll(0)) {
|
||||||
|
this.toggleScroll();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Build.prototype.sidebarOnClick = function () {
|
Build.prototype.sidebarOnClick = function () {
|
||||||
|
@ -301,24 +287,5 @@ window.Build = (function () {
|
||||||
this.populateJobs(stage);
|
this.populateJobs(stage);
|
||||||
};
|
};
|
||||||
|
|
||||||
Build.prototype.stepTrace = function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const $currentTarget = $(e.currentTarget);
|
|
||||||
$.scrollTo($currentTarget.attr('href'), {
|
|
||||||
offset: 0,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Build.prototype.initAffixTruncatedInfo = function () {
|
|
||||||
const offsetTop = this.$buildTrace.offset().top;
|
|
||||||
|
|
||||||
this.$truncatedInfo.affix({
|
|
||||||
offset: {
|
|
||||||
top: offsetTop,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return Build;
|
return Build;
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -29,20 +29,117 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes blinking-scroll-button {
|
||||||
|
0% { opacity: 0.2; }
|
||||||
|
25% { opacity: 0.5; }
|
||||||
|
50% { opacity: 0.7; }
|
||||||
|
100% { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
.build-page {
|
.build-page {
|
||||||
pre.trace {
|
.sticky {
|
||||||
background: $builds-trace-bg;
|
position: absolute;
|
||||||
color: $white-light;
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.build-trace-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 225px;
|
||||||
|
left: 15px;
|
||||||
|
bottom: 10px;
|
||||||
|
background: $black;
|
||||||
|
color: $gray-darkest;
|
||||||
font-family: $monospace_font;
|
font-family: $monospace_font;
|
||||||
white-space: pre-wrap;
|
|
||||||
overflow: auto;
|
|
||||||
overflow-y: hidden;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
||||||
.fa-spinner {
|
&.sidebar-expanded {
|
||||||
font-size: 24px;
|
right: 305px;
|
||||||
margin-left: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.sidebar-collapsed {
|
||||||
|
right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: $black;
|
||||||
|
color: $gray-darkest;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-bar {
|
||||||
|
top: 0;
|
||||||
|
height: 35px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
border-bottom: 1px outset $white-light;
|
||||||
|
|
||||||
|
.truncated-info {
|
||||||
|
margin: 0 auto;
|
||||||
|
align-self: center;
|
||||||
|
|
||||||
|
.truncated-info-size {
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.raw-link {
|
||||||
|
color: inherit;
|
||||||
|
margin-left: 5px;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.controllers {
|
||||||
|
display: flex;
|
||||||
|
align-self: center;
|
||||||
|
font-size: 15px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
height: 15px;
|
||||||
|
display: block;
|
||||||
|
fill: $white-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
.btn-scroll {
|
||||||
|
margin: 0 8px;
|
||||||
|
color: $white-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-scroll.animate {
|
||||||
|
.first-triangle {
|
||||||
|
animation: blinking-scroll-button 1s ease infinite;
|
||||||
|
animation-delay: .3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.second-triangle {
|
||||||
|
animation: blinking-scroll-button 1s ease infinite;
|
||||||
|
animation-delay: .2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.third-triangle {
|
||||||
|
animation: blinking-scroll-button 1s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-scroll:disabled {
|
||||||
|
opacity: 0.35;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bash {
|
||||||
|
top: 35px;
|
||||||
|
left: 10px;
|
||||||
|
bottom: 0;
|
||||||
|
overflow-y: hidden;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.environment-information {
|
.environment-information {
|
||||||
|
@ -58,99 +155,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.truncated-info {
|
.build-loader-animation {
|
||||||
text-align: center;
|
position: relative;
|
||||||
border-bottom: 1px solid;
|
width: 6px;
|
||||||
background-color: $black;
|
height: 6px;
|
||||||
height: 45px;
|
margin: auto auto 12px 2px;
|
||||||
padding: 15px;
|
border-radius: 50%;
|
||||||
|
animation: blinking-dots 1s linear infinite;
|
||||||
&.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.truncated-info-size {
|
|
||||||
margin: 0 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.raw-link {
|
|
||||||
color: inherit;
|
|
||||||
margin-left: 5px;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroll-controls {
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.scroll-step {
|
|
||||||
width: 31px;
|
|
||||||
margin: 0 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroll-link,
|
|
||||||
.autoscroll-container {
|
|
||||||
right: 25px;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroll-link {
|
|
||||||
position: fixed;
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
|
|
||||||
&.scroll-top .gitlab-icon-scroll-up-hover,
|
|
||||||
&.scroll-top:hover .gitlab-icon-scroll-up,
|
|
||||||
&.scroll-bottom .gitlab-icon-scroll-down-hover,
|
|
||||||
&.scroll-bottom:hover .gitlab-icon-scroll-down {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.scroll-top:hover .gitlab-icon-scroll-up-hover,
|
|
||||||
&.scroll-bottom:hover .gitlab-icon-scroll-down-hover {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.scroll-top {
|
|
||||||
top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.scroll-bottom {
|
|
||||||
bottom: -2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.autoscroll-container {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.sidebar-expanded {
|
|
||||||
|
|
||||||
.scroll-link,
|
|
||||||
.autoscroll-container {
|
|
||||||
right: ($gutter_width + ($gl-padding * 2));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,32 +234,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.build-trace {
|
|
||||||
background: $black;
|
|
||||||
color: $gray-darkest;
|
|
||||||
white-space: pre;
|
|
||||||
overflow-x: auto;
|
|
||||||
font-size: 12px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.fa-spinner {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bash {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.build-loader-animation {
|
|
||||||
position: relative;
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
margin: auto auto 12px 2px;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: blinking-dots 1s linear infinite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-sidebar.build-sidebar {
|
.right-sidebar.build-sidebar {
|
||||||
padding: $gl-padding 0;
|
padding: $gl-padding 0;
|
||||||
|
|
||||||
|
|
|
@ -68,15 +68,8 @@
|
||||||
- elsif @build.runner
|
- elsif @build.runner
|
||||||
\##{@build.runner.id}
|
\##{@build.runner.id}
|
||||||
.btn-group.btn-group-justified{ role: :group }
|
.btn-group.btn-group-justified{ role: :group }
|
||||||
- if @build.has_trace?
|
|
||||||
= link_to 'Raw', raw_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default'
|
|
||||||
- if @build.active?
|
- if @build.active?
|
||||||
= link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post
|
= link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post
|
||||||
- if can?(current_user, :update_build, @project) && @build.erasable?
|
|
||||||
= link_to erase_namespace_project_build_path(@project.namespace, @project, @build),
|
|
||||||
class: "btn btn-sm btn-default", method: :post,
|
|
||||||
data: { confirm: "Are you sure you want to erase this build?" } do
|
|
||||||
Erase
|
|
||||||
|
|
||||||
- if @build.trigger_request
|
- if @build.trigger_request
|
||||||
.build-widget
|
.build-widget
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
- if @build.stuck?
|
- if @build.stuck?
|
||||||
- unless @build.any_runners_online?
|
- unless @build.any_runners_online?
|
||||||
.bs-callout.bs-callout-warning
|
.bs-callout.bs-callout-warning.js-build-stuck
|
||||||
%p
|
%p
|
||||||
- if no_runners_for_project?(@build.project)
|
- if no_runners_for_project?(@build.project)
|
||||||
This job is stuck, because the project doesn't have any runners online assigned to it.
|
This job is stuck, because the project doesn't have any runners online assigned to it.
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
Runners page
|
Runners page
|
||||||
|
|
||||||
- if @build.starts_environment?
|
- if @build.starts_environment?
|
||||||
.prepend-top-default
|
.prepend-top-default.js-environment-container
|
||||||
.environment-information
|
.environment-information
|
||||||
- if @build.outdated_deployment?
|
- if @build.outdated_deployment?
|
||||||
= ci_icon_for_status('success_with_warnings')
|
= ci_icon_for_status('success_with_warnings')
|
||||||
|
@ -47,39 +47,51 @@
|
||||||
- if environment.try(:last_deployment)
|
- if environment.try(:last_deployment)
|
||||||
and will overwrite the #{deployment_link(environment.last_deployment, text: 'latest deployment')}
|
and will overwrite the #{deployment_link(environment.last_deployment, text: 'latest deployment')}
|
||||||
|
|
||||||
.prepend-top-default
|
.prepend-top-default.js-build-erased
|
||||||
- if @build.erased?
|
- if @build.erased?
|
||||||
.erased.alert.alert-warning
|
.erased.alert.alert-warning
|
||||||
- if @build.erased_by_user?
|
- if @build.erased_by_user?
|
||||||
Job has been erased by #{link_to(@build.erased_by_name, user_path(@build.erased_by))} #{time_ago_with_tooltip(@build.erased_at)}
|
Job has been erased by #{link_to(@build.erased_by_name, user_path(@build.erased_by))} #{time_ago_with_tooltip(@build.erased_at)}
|
||||||
- else
|
- else
|
||||||
Job has been erased #{time_ago_with_tooltip(@build.erased_at)}
|
Job has been erased #{time_ago_with_tooltip(@build.erased_at)}
|
||||||
- else
|
|
||||||
#js-build-scroll.scroll-controls
|
.prepend-top-default
|
||||||
.scroll-step
|
.build-trace-container#build-trace
|
||||||
%a.scroll-link.scroll-top{ href: '#up-build-trace', id: 'scroll-top', title: 'Scroll to top' }
|
.top-bar.sticky
|
||||||
= custom_icon('scroll_up')
|
|
||||||
= custom_icon('scroll_up_hover_active')
|
|
||||||
%a.scroll-link.scroll-bottom{ href: '#down-build-trace', id: 'scroll-bottom', title: 'Scroll to bottom' }
|
|
||||||
= custom_icon('scroll_down')
|
|
||||||
= custom_icon('scroll_down_hover_active')
|
|
||||||
- if @build.active?
|
|
||||||
.autoscroll-container
|
|
||||||
%span.status-message#autoscroll-status{ data: { state: 'disabled' } }
|
|
||||||
%span.status-text Autoscroll active
|
|
||||||
%i.status-icon
|
|
||||||
= custom_icon('scroll_down_hover_active')
|
|
||||||
#up-build-trace
|
|
||||||
%pre.build-trace#build-trace
|
|
||||||
.js-truncated-info.truncated-info.hidden<
|
.js-truncated-info.truncated-info.hidden<
|
||||||
Showing last
|
Showing last
|
||||||
%span.js-truncated-info-size.truncated-info-size><
|
%span.js-truncated-info-size.truncated-info-size><
|
||||||
KiB of log -
|
KiB of log -
|
||||||
%a.js-raw-link.raw-link{ :href => raw_namespace_project_build_path(@project.namespace, @project, @build) }>< Complete Raw
|
%a.js-raw-link.raw-link{ href: raw_namespace_project_build_path(@project.namespace, @project, @build) }>< Complete Raw
|
||||||
%code.bash.js-build-output
|
.controllers
|
||||||
.build-loader-animation.js-build-refresh
|
- if @build.has_trace?
|
||||||
|
= link_to raw_namespace_project_build_path(@project.namespace, @project, @build),
|
||||||
|
title: 'Open raw trace',
|
||||||
|
data: { placement: 'top', container: 'body' },
|
||||||
|
class: 'js-raw-link-controller has-tooltip' do
|
||||||
|
= icon('download')
|
||||||
|
|
||||||
#down-build-trace
|
- if can?(current_user, :update_build, @project) && @build.erasable?
|
||||||
|
= link_to erase_namespace_project_build_path(@project.namespace, @project, @build),
|
||||||
|
method: :post,
|
||||||
|
data: { confirm: 'Are you sure you want to erase this build?', placement: 'top', container: 'body' },
|
||||||
|
title: 'Erase Build',
|
||||||
|
class: 'has-tooltip js-erase-link' do
|
||||||
|
= icon('trash')
|
||||||
|
|
||||||
|
%button.js-scroll-up.btn-scroll.btn-transparent.btn-blank.has-tooltip{ type: 'button',
|
||||||
|
disabled: true,
|
||||||
|
title: 'Scroll Up',
|
||||||
|
data: { placement: 'top', container: 'body'} }
|
||||||
|
= custom_icon('scroll_up')
|
||||||
|
%button.js-scroll-down.btn-scroll.btn-transparent.btn-blank.has-tooltip{ type: 'button',
|
||||||
|
disabled: true,
|
||||||
|
title: 'Scroll Down',
|
||||||
|
data: { placement: 'top', container: 'body'} }
|
||||||
|
= custom_icon('scroll_down')
|
||||||
|
.bash.sticky.js-scroll-container
|
||||||
|
%code.js-build-output
|
||||||
|
.build-loader-animation.js-build-refresh
|
||||||
|
|
||||||
= render "sidebar"
|
= render "sidebar"
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
<svg width="16" height="33" class="gitlab-icon-scroll-down" viewBox="0 0 16 33" xmlns="http://www.w3.org/2000/svg">
|
<svg width="12" height="16" viewBox="0 0 12 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill="#ffffff" d="M1.385 5.534v12.47a4.145 4.145 0 0 0 4.144 4.15h4.942a4.151 4.151 0 0 0 4.144-4.15V5.535a4.145 4.145 0 0 0-4.144-4.15H5.53a4.151 4.151 0 0 0-4.144 4.15zM8.88 30.27v-4.351a.688.688 0 0 0-.69-.688.687.687 0 0 0-.69.688v4.334l-1.345-1.346a.69.69 0 0 0-.976.976l2.526 2.526a.685.685 0 0 0 .494.2.685.685 0 0 0 .493-.2l2.526-2.526a.69.69 0 1 0-.976-.976L8.88 30.27zM0 5.534A5.536 5.536 0 0 1 5.529 0h4.942A5.53 5.53 0 0 1 16 5.534v12.47a5.536 5.536 0 0 1-5.529 5.534H5.53A5.53 5.53 0 0 1 0 18.005V5.534zm7 1.01a1 1 0 1 1 2 0v2.143a1 1 0 1 1-2 0V6.544z" fill-rule="evenodd"/>
|
<path class="first-triangle" d="M1.048 14.155a.508.508 0 0 0-.32.105c-.091.07-.136.154-.136.25v.71c0 .095.045.178.135.249.09.07.197.105.321.105h10.043c.124 0 .23-.035.321-.105.09-.07.136-.154.136-.25v-.71c0-.095-.045-.178-.136-.249a.508.508 0 0 0-.32-.105"/>
|
||||||
|
<path class="second-triangle" d="M.687 8.027c-.09-.087-.122-.16-.093-.22.028-.06.104-.09.228-.09h10.5c.123 0 .2.03.228.09.029.06-.002.133-.093.22L6.393 12.91a.458.458 0 0 1-.136.089h-.37a.626.626 0 0 1-.136-.09"/>
|
||||||
|
<path class="third-triangle" d="M.687 1.027C.597.94.565.867.594.807c.028-.06.104-.09.228-.09h10.5c.123 0 .2.03.228.09.029.06-.002.133-.093.22L6.393 5.91A.458.458 0 0 1 6.257 6h-.37a.626.626 0 0 1-.136-.09"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 719 B After Width: | Height: | Size: 778 B |
|
@ -1,3 +0,0 @@
|
||||||
<svg width="16" height="33" class="gitlab-icon-scroll-down-hover" viewBox="0 0 16 33" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill="#ffffff" d="M8.88 30.27v-4.351a.688.688 0 0 0-.69-.688.687.687 0 0 0-.69.688v4.334l-1.345-1.346a.69.69 0 0 0-.976.976l2.526 2.526a.685.685 0 0 0 .494.2.685.685 0 0 0 .493-.2l2.526-2.526a.69.69 0 1 0-.976-.976L8.88 30.27zM0 5.534A5.536 5.536 0 0 1 5.529 0h4.942A5.53 5.53 0 0 1 16 5.534v12.47a5.536 5.536 0 0 1-5.529 5.534H5.53A5.53 5.53 0 0 1 0 18.005V5.534zm7 1.01a1 1 0 1 1 2 0v2.143a1 1 0 1 1-2 0V6.544z" fill-rule="evenodd"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 573 B |
|
@ -1,3 +1 @@
|
||||||
<svg width="16" height="33" class="gitlab-icon-scroll-up" viewBox="0 0 16 33" xmlns="http://www.w3.org/2000/svg">
|
<svg width="12" height="16" viewBox="0 0 12 16" xmlns="http://www.w3.org/2000/svg"><path d="M1.048 1.845a.508.508 0 0 1-.32-.105c-.091-.07-.136-.154-.136-.25V.78c0-.095.045-.178.135-.249a.508.508 0 0 1 .321-.105h10.043c.124 0 .23.035.321.105.09.07.136.154.136.25v.71c0 .095-.045.178-.136.249a.508.508 0 0 1-.32.105"/><path d="M.687 7.973c-.09.087-.122.16-.093.22.028.06.104.09.228.09h10.5c.123 0 .2-.03.228-.09.029-.06-.002-.133-.093-.22L6.393 3.09A.458.458 0 0 0 6.257 3h-.37a.626.626 0 0 0-.136.09"/><path d="M.687 14.973c-.09.087-.122.16-.093.22.028.06.104.09.228.09h10.5c.123 0 .2-.03.228-.09.029-.06-.002-.133-.093-.22L6.393 10.09A.458.458 0 0 0 6.257 10h-.37a.626.626 0 0 0-.136.09"/></svg>
|
||||||
<path fill="#ffffff" d="M1.385 14.534v12.47a4.145 4.145 0 0 0 4.144 4.15h4.942a4.151 4.151 0 0 0 4.144-4.15v-12.47a4.145 4.145 0 0 0-4.144-4.15H5.53a4.151 4.151 0 0 0-4.144 4.15zM8.88 2.609V6.96a.688.688 0 0 1-.69.688.687.687 0 0 1-.69-.688V2.627L6.155 3.972a.69.69 0 0 1-.976-.976L7.705.47a.685.685 0 0 1 .494-.2.685.685 0 0 1 .493.2l2.526 2.526a.69.69 0 1 1-.976.976L8.88 2.609zM0 14.534A5.536 5.536 0 0 1 5.529 9h4.942A5.53 5.53 0 0 1 16 14.534v12.47a5.536 5.536 0 0 1-5.529 5.534H5.53A5.53 5.53 0 0 1 0 27.005V14.534zm7 1.01a1 1 0 1 1 2 0v2.143a1 1 0 1 1-2 0v-2.143z" fill-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
|
|
Before Width: | Height: | Size: 717 B After Width: | Height: | Size: 697 B |
|
@ -1,3 +0,0 @@
|
||||||
<svg width="16" height="33" class="gitlab-icon-scroll-up-hover" viewBox="0 0 16 33" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill="#ffffff" d="M8.88 2.646l1.362 1.362a.69.69 0 0 0 .976-.976L8.692.507A.685.685 0 0 0 8.2.306a.685.685 0 0 0-.494.2L5.179 3.033a.69.69 0 1 0 .976.976L7.5 2.663v4.179c0 .38.306.688.69.688.381 0 .69-.306.69-.688V2.646zM0 14.534A5.536 5.536 0 0 1 5.529 9h4.942A5.53 5.53 0 0 1 16 14.534v12.47a5.536 5.536 0 0 1-5.529 5.534H5.53A5.53 5.53 0 0 1 0 27.005V14.534zm7 1.01a1 1 0 1 1 2 0v2.143a1 1 0 1 1-2 0v-2.143z" fill-rule="evenodd"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 569 B |
|
@ -190,7 +190,7 @@ feature 'Builds', :feature do
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
expect(page).to have_link 'Raw'
|
expect(page).to have_css('.js-raw-link')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -369,14 +369,14 @@ feature 'Builds', :feature do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET /:project/builds/:id/raw' do
|
describe 'GET /:project/builds/:id/raw', :js do
|
||||||
context 'access source' do
|
context 'access source' do
|
||||||
context 'build from project' do
|
context 'build from project' do
|
||||||
before do
|
before do
|
||||||
Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
|
Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' }
|
||||||
build.run!
|
build.run!
|
||||||
visit namespace_project_build_path(project.namespace, project, build)
|
visit namespace_project_build_path(project.namespace, project, build)
|
||||||
page.within('.js-build-sidebar') { click_link 'Raw' }
|
find('.js-raw-link-controller').click()
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sends the right headers' do
|
it 'sends the right headers' do
|
||||||
|
@ -388,7 +388,7 @@ feature 'Builds', :feature do
|
||||||
|
|
||||||
context 'build from other project' do
|
context 'build from other project' do
|
||||||
before do
|
before do
|
||||||
Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
|
Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' }
|
||||||
build2.run!
|
build2.run!
|
||||||
visit raw_namespace_project_build_path(project.namespace, project, build2)
|
visit raw_namespace_project_build_path(project.namespace, project, build2)
|
||||||
end
|
end
|
||||||
|
@ -403,7 +403,7 @@ feature 'Builds', :feature do
|
||||||
let(:existing_file) { Tempfile.new('existing-trace-file').path }
|
let(:existing_file) { Tempfile.new('existing-trace-file').path }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
|
Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' }
|
||||||
|
|
||||||
build.run!
|
build.run!
|
||||||
|
|
||||||
|
@ -413,13 +413,13 @@ feature 'Builds', :feature do
|
||||||
visit namespace_project_build_path(project.namespace, project, build)
|
visit namespace_project_build_path(project.namespace, project, build)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when build has trace in file' do
|
context 'when build has trace in file', :js do
|
||||||
let(:paths) do
|
let(:paths) do
|
||||||
[existing_file]
|
[existing_file]
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
page.within('.js-build-sidebar') { click_link 'Raw' }
|
find('.js-raw-link-controller').click()
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sends the right headers' do
|
it 'sends the right headers' do
|
||||||
|
@ -433,7 +433,7 @@ feature 'Builds', :feature do
|
||||||
let(:paths) { [] }
|
let(:paths) { [] }
|
||||||
|
|
||||||
it 'sends the right headers' do
|
it 'sends the right headers' do
|
||||||
expect(page.status_code).not_to have_link('Raw')
|
expect(page.status_code).not_to have_selector('.js-raw-link-controller')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,7 +14,6 @@ describe('Build', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
loadFixtures('builds/build-with-artifacts.html.raw');
|
loadFixtures('builds/build-with-artifacts.html.raw');
|
||||||
spyOn($, 'ajax');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('class constructor', () => {
|
describe('class constructor', () => {
|
||||||
|
@ -33,7 +32,6 @@ describe('Build', () => {
|
||||||
|
|
||||||
it('copies build options', function () {
|
it('copies build options', function () {
|
||||||
expect(this.build.pageUrl).toBe(BUILD_URL);
|
expect(this.build.pageUrl).toBe(BUILD_URL);
|
||||||
expect(this.build.buildUrl).toBe(`${BUILD_URL}.json`);
|
|
||||||
expect(this.build.buildStatus).toBe('success');
|
expect(this.build.buildStatus).toBe('success');
|
||||||
expect(this.build.buildStage).toBe('test');
|
expect(this.build.buildStage).toBe('test');
|
||||||
expect(this.build.state).toBe('');
|
expect(this.build.state).toBe('');
|
||||||
|
@ -65,27 +63,14 @@ describe('Build', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('running build', () => {
|
describe('running build', () => {
|
||||||
beforeEach(function () {
|
|
||||||
this.build = new Build();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('updates the build trace on an interval', function () {
|
it('updates the build trace on an interval', function () {
|
||||||
|
const deferred1 = $.Deferred();
|
||||||
|
const deferred2 = $.Deferred();
|
||||||
|
const deferred3 = $.Deferred();
|
||||||
|
spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise());
|
||||||
spyOn(gl.utils, 'visitUrl');
|
spyOn(gl.utils, 'visitUrl');
|
||||||
|
|
||||||
jasmine.clock().tick(4001);
|
deferred1.resolve({
|
||||||
|
|
||||||
expect($.ajax.calls.count()).toBe(1);
|
|
||||||
|
|
||||||
// We have to do it this way to prevent Webpack to fail to compile
|
|
||||||
// when destructuring assignments and reusing
|
|
||||||
// the same variables names inside the same scope
|
|
||||||
let args = $.ajax.calls.argsFor(0)[0];
|
|
||||||
|
|
||||||
expect(args.url).toBe(`${BUILD_URL}/trace.json`);
|
|
||||||
expect(args.dataType).toBe('json');
|
|
||||||
expect(args.success).toEqual(jasmine.any(Function));
|
|
||||||
|
|
||||||
args.success.call($, {
|
|
||||||
html: '<span>Update<span>',
|
html: '<span>Update<span>',
|
||||||
status: 'running',
|
status: 'running',
|
||||||
state: 'newstate',
|
state: 'newstate',
|
||||||
|
@ -93,20 +78,9 @@ describe('Build', () => {
|
||||||
complete: false,
|
complete: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
|
deferred2.resolve();
|
||||||
expect(this.build.state).toBe('newstate');
|
|
||||||
|
|
||||||
jasmine.clock().tick(4001);
|
deferred3.resolve({
|
||||||
|
|
||||||
expect($.ajax.calls.count()).toBe(3);
|
|
||||||
|
|
||||||
args = $.ajax.calls.argsFor(2)[0];
|
|
||||||
expect(args.url).toBe(`${BUILD_URL}/trace.json`);
|
|
||||||
expect(args.dataType).toBe('json');
|
|
||||||
expect(args.data.state).toBe('newstate');
|
|
||||||
expect(args.success).toEqual(jasmine.any(Function));
|
|
||||||
|
|
||||||
args.success.call($, {
|
|
||||||
html: '<span>More</span>',
|
html: '<span>More</span>',
|
||||||
status: 'running',
|
status: 'running',
|
||||||
state: 'finalstate',
|
state: 'finalstate',
|
||||||
|
@ -114,150 +88,222 @@ describe('Build', () => {
|
||||||
complete: true,
|
complete: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.build = new Build();
|
||||||
|
|
||||||
|
expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
|
||||||
|
expect(this.build.state).toBe('newstate');
|
||||||
|
|
||||||
|
jasmine.clock().tick(4001);
|
||||||
|
|
||||||
expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/);
|
expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/);
|
||||||
expect(this.build.state).toBe('finalstate');
|
expect(this.build.state).toBe('finalstate');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('replaces the entire build trace', () => {
|
it('replaces the entire build trace', () => {
|
||||||
|
const deferred1 = $.Deferred();
|
||||||
|
const deferred2 = $.Deferred();
|
||||||
|
const deferred3 = $.Deferred();
|
||||||
|
|
||||||
|
spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise());
|
||||||
|
|
||||||
spyOn(gl.utils, 'visitUrl');
|
spyOn(gl.utils, 'visitUrl');
|
||||||
|
|
||||||
jasmine.clock().tick(4001);
|
deferred1.resolve({
|
||||||
let args = $.ajax.calls.argsFor(0)[0];
|
html: '<span>Update<span>',
|
||||||
args.success.call($, {
|
|
||||||
html: '<span>Update</span>',
|
|
||||||
status: 'running',
|
status: 'running',
|
||||||
append: false,
|
append: false,
|
||||||
complete: false,
|
complete: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
|
deferred2.resolve();
|
||||||
|
|
||||||
jasmine.clock().tick(4001);
|
deferred3.resolve({
|
||||||
args = $.ajax.calls.argsFor(2)[0];
|
|
||||||
args.success.call($, {
|
|
||||||
html: '<span>Different</span>',
|
html: '<span>Different</span>',
|
||||||
status: 'running',
|
status: 'running',
|
||||||
append: false,
|
append: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.build = new Build();
|
||||||
|
|
||||||
|
expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
|
||||||
|
|
||||||
|
jasmine.clock().tick(4001);
|
||||||
|
|
||||||
expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/);
|
expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/);
|
||||||
expect($('#build-trace .js-build-output').text()).toMatch(/Different/);
|
expect($('#build-trace .js-build-output').text()).toMatch(/Different/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reloads the page when the build is done', () => {
|
it('reloads the page when the build is done', () => {
|
||||||
spyOn(gl.utils, 'visitUrl');
|
spyOn(gl.utils, 'visitUrl');
|
||||||
|
const deferred = $.Deferred();
|
||||||
|
|
||||||
jasmine.clock().tick(4001);
|
spyOn($, 'ajax').and.returnValue(deferred.promise());
|
||||||
const [{ success }] = $.ajax.calls.argsFor(0);
|
deferred.resolve({
|
||||||
success.call($, {
|
|
||||||
html: '<span>Final</span>',
|
html: '<span>Final</span>',
|
||||||
status: 'passed',
|
status: 'passed',
|
||||||
append: true,
|
append: true,
|
||||||
complete: true,
|
complete: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.build = new Build();
|
||||||
|
|
||||||
expect(gl.utils.visitUrl).toHaveBeenCalledWith(BUILD_URL);
|
expect(gl.utils.visitUrl).toHaveBeenCalledWith(BUILD_URL);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('truncated information', () => {
|
describe('truncated information', () => {
|
||||||
describe('when size is less than total', () => {
|
describe('when size is less than total', () => {
|
||||||
it('shows information about truncated log', () => {
|
it('shows information about truncated log', () => {
|
||||||
jasmine.clock().tick(4001);
|
spyOn(gl.utils, 'visitUrl');
|
||||||
const [{ success }] = $.ajax.calls.argsFor(0);
|
const deferred = $.Deferred();
|
||||||
|
spyOn($, 'ajax').and.returnValue(deferred.promise());
|
||||||
|
|
||||||
success.call($, {
|
deferred.resolve({
|
||||||
html: '<span>Update</span>',
|
html: '<span>Update</span>',
|
||||||
status: 'success',
|
status: 'success',
|
||||||
append: false,
|
append: false,
|
||||||
size: 50,
|
size: 50,
|
||||||
total: 100,
|
total: 100,
|
||||||
});
|
|
||||||
|
|
||||||
expect(document.querySelector('.js-truncated-info').classList).not.toContain('hidden');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows the size in KiB', () => {
|
this.build = new Build();
|
||||||
jasmine.clock().tick(4001);
|
|
||||||
const [{ success }] = $.ajax.calls.argsFor(0);
|
|
||||||
const size = 50;
|
|
||||||
|
|
||||||
success.call($, {
|
expect(document.querySelector('.js-truncated-info').classList).not.toContain('hidden');
|
||||||
html: '<span>Update</span>',
|
|
||||||
status: 'success',
|
|
||||||
append: false,
|
|
||||||
size,
|
|
||||||
total: 100,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
document.querySelector('.js-truncated-info-size').textContent.trim(),
|
|
||||||
).toEqual(`${bytesToKiB(size)}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows incremented size', () => {
|
|
||||||
jasmine.clock().tick(4001);
|
|
||||||
let args = $.ajax.calls.argsFor(0)[0];
|
|
||||||
args.success.call($, {
|
|
||||||
html: '<span>Update</span>',
|
|
||||||
status: 'success',
|
|
||||||
append: false,
|
|
||||||
size: 50,
|
|
||||||
total: 100,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
document.querySelector('.js-truncated-info-size').textContent.trim(),
|
|
||||||
).toEqual(`${bytesToKiB(50)}`);
|
|
||||||
|
|
||||||
jasmine.clock().tick(4001);
|
|
||||||
args = $.ajax.calls.argsFor(2)[0];
|
|
||||||
args.success.call($, {
|
|
||||||
html: '<span>Update</span>',
|
|
||||||
status: 'success',
|
|
||||||
append: true,
|
|
||||||
size: 10,
|
|
||||||
total: 100,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
document.querySelector('.js-truncated-info-size').textContent.trim(),
|
|
||||||
).toEqual(`${bytesToKiB(60)}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders the raw link', () => {
|
|
||||||
jasmine.clock().tick(4001);
|
|
||||||
const [{ success }] = $.ajax.calls.argsFor(0);
|
|
||||||
|
|
||||||
success.call($, {
|
|
||||||
html: '<span>Update</span>',
|
|
||||||
status: 'success',
|
|
||||||
append: false,
|
|
||||||
size: 50,
|
|
||||||
total: 100,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
document.querySelector('.js-raw-link').textContent.trim(),
|
|
||||||
).toContain('Complete Raw');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when size is equal than total', () => {
|
it('shows the size in KiB', () => {
|
||||||
it('does not show the trunctated information', () => {
|
const size = 50;
|
||||||
jasmine.clock().tick(4001);
|
spyOn(gl.utils, 'visitUrl');
|
||||||
const [{ success }] = $.ajax.calls.argsFor(0);
|
const deferred = $.Deferred();
|
||||||
|
|
||||||
success.call($, {
|
spyOn($, 'ajax').and.returnValue(deferred.promise());
|
||||||
html: '<span>Update</span>',
|
deferred.resolve({
|
||||||
status: 'success',
|
html: '<span>Update</span>',
|
||||||
append: false,
|
status: 'success',
|
||||||
size: 100,
|
append: false,
|
||||||
total: 100,
|
size,
|
||||||
});
|
total: 100,
|
||||||
|
|
||||||
expect(document.querySelector('.js-truncated-info').classList).toContain('hidden');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.build = new Build();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
document.querySelector('.js-truncated-info-size').textContent.trim(),
|
||||||
|
).toEqual(`${bytesToKiB(size)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('shows incremented size', () => {
|
||||||
|
const deferred1 = $.Deferred();
|
||||||
|
const deferred2 = $.Deferred();
|
||||||
|
const deferred3 = $.Deferred();
|
||||||
|
|
||||||
|
spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise());
|
||||||
|
|
||||||
|
spyOn(gl.utils, 'visitUrl');
|
||||||
|
|
||||||
|
deferred1.resolve({
|
||||||
|
html: '<span>Update</span>',
|
||||||
|
status: 'success',
|
||||||
|
append: false,
|
||||||
|
size: 50,
|
||||||
|
total: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
deferred2.resolve();
|
||||||
|
|
||||||
|
this.build = new Build();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
document.querySelector('.js-truncated-info-size').textContent.trim(),
|
||||||
|
).toEqual(`${bytesToKiB(50)}`);
|
||||||
|
|
||||||
|
jasmine.clock().tick(4001);
|
||||||
|
|
||||||
|
deferred3.resolve({
|
||||||
|
html: '<span>Update</span>',
|
||||||
|
status: 'success',
|
||||||
|
append: true,
|
||||||
|
size: 10,
|
||||||
|
total: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
document.querySelector('.js-truncated-info-size').textContent.trim(),
|
||||||
|
).toEqual(`${bytesToKiB(60)}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the raw link', () => {
|
||||||
|
const deferred = $.Deferred();
|
||||||
|
spyOn(gl.utils, 'visitUrl');
|
||||||
|
|
||||||
|
spyOn($, 'ajax').and.returnValue(deferred.promise());
|
||||||
|
deferred.resolve({
|
||||||
|
html: '<span>Update</span>',
|
||||||
|
status: 'success',
|
||||||
|
append: false,
|
||||||
|
size: 50,
|
||||||
|
total: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.build = new Build();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
document.querySelector('.js-raw-link').textContent.trim(),
|
||||||
|
).toContain('Complete Raw');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when size is equal than total', () => {
|
||||||
|
it('does not show the trunctated information', () => {
|
||||||
|
const deferred = $.Deferred();
|
||||||
|
spyOn(gl.utils, 'visitUrl');
|
||||||
|
|
||||||
|
spyOn($, 'ajax').and.returnValue(deferred.promise());
|
||||||
|
deferred.resolve({
|
||||||
|
html: '<span>Update</span>',
|
||||||
|
status: 'success',
|
||||||
|
append: false,
|
||||||
|
size: 100,
|
||||||
|
total: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.build = new Build();
|
||||||
|
|
||||||
|
expect(document.querySelector('.js-truncated-info').classList).toContain('hidden');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('output trace', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const deferred = $.Deferred();
|
||||||
|
spyOn(gl.utils, 'visitUrl');
|
||||||
|
|
||||||
|
spyOn($, 'ajax').and.returnValue(deferred.promise());
|
||||||
|
deferred.resolve({
|
||||||
|
html: '<span>Update</span>',
|
||||||
|
status: 'success',
|
||||||
|
append: false,
|
||||||
|
size: 50,
|
||||||
|
total: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.build = new Build();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render trace controls', () => {
|
||||||
|
const controllers = document.querySelector('.controllers');
|
||||||
|
|
||||||
|
expect(controllers.querySelector('.js-raw-link-controller')).toBeDefined();
|
||||||
|
expect(controllers.querySelector('.js-erase-link')).toBeDefined();
|
||||||
|
expect(controllers.querySelector('.js-scroll-up')).toBeDefined();
|
||||||
|
expect(controllers.querySelector('.js-scroll-down')).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render received output', () => {
|
||||||
|
expect(
|
||||||
|
document.querySelector('.js-build-output').innerHTML,
|
||||||
|
).toEqual('<span>Update</span>');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue