324 lines
10 KiB
JavaScript
324 lines
10 KiB
JavaScript
/* eslint-disable func-names, wrap-iife, no-use-before-define,
|
|
consistent-return, prefer-rest-params */
|
|
/* global Breakpoints */
|
|
|
|
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) {
|
|
this.options = options || $('.js-build-options').data();
|
|
|
|
this.pageUrl = this.options.pageUrl;
|
|
this.buildUrl = this.options.buildUrl;
|
|
this.buildStatus = this.options.buildStatus;
|
|
this.state = this.options.logState;
|
|
this.buildStage = this.options.buildStage;
|
|
this.$document = $(document);
|
|
this.logBytes = 0;
|
|
|
|
this.updateDropdown = bind(this.updateDropdown, 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');
|
|
|
|
clearTimeout(Build.timeout);
|
|
// Init breakpoint checker
|
|
this.bp = Breakpoints.get();
|
|
|
|
this.initSidebar();
|
|
this.populateJobs(this.buildStage);
|
|
this.updateStageDropdownText(this.buildStage);
|
|
this.sidebarOnResize();
|
|
|
|
this.$document
|
|
.off('click', '.js-sidebar-build-toggle')
|
|
.on('click', '.js-sidebar-build-toggle', this.sidebarOnClick.bind(this));
|
|
|
|
this.$document
|
|
.off('click', '.stage-item')
|
|
.on('click', '.stage-item', this.updateDropdown);
|
|
|
|
this.$document.on('scroll', this.initScrollMonitor.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();
|
|
}
|
|
|
|
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');
|
|
|
|
gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`);
|
|
|
|
if (log.state) {
|
|
this.state = log.state;
|
|
}
|
|
|
|
if (log.append) {
|
|
$buildContainer.append(log.html);
|
|
this.logBytes += log.size;
|
|
} else {
|
|
$buildContainer.html(log.html);
|
|
this.logBytes = log.size;
|
|
}
|
|
|
|
// if the incremental sum of logBytes we received is less than the total
|
|
// we need to show a message warning the user about that.
|
|
if (this.logBytes < log.total) {
|
|
// size is in bytes, we need to calculate KiB
|
|
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) {
|
|
Build.timeout = setTimeout(() => {
|
|
this.invokeBuildTrace();
|
|
}, 4000);
|
|
} else {
|
|
this.$buildRefreshAnimation.remove();
|
|
}
|
|
|
|
if (log.status !== this.buildStatus) {
|
|
let pageUrl = this.pageUrl;
|
|
|
|
if (this.$autoScrollStatus.data('state') === 'enabled') {
|
|
pageUrl += DOWN_BUILD_TRACE;
|
|
}
|
|
|
|
gl.utils.visitUrl(pageUrl);
|
|
}
|
|
}),
|
|
error: () => {
|
|
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 () {
|
|
const bootstrapBreakpoint = this.bp.getBreakpointSize();
|
|
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
|
|
};
|
|
|
|
Build.prototype.toggleSidebar = function (shouldHide) {
|
|
const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
|
|
|
|
this.$buildScroll.toggleClass('sidebar-expanded', shouldShow)
|
|
.toggleClass('sidebar-collapsed', shouldHide);
|
|
this.$truncatedInfo.toggleClass('sidebar-expanded', shouldShow)
|
|
.toggleClass('sidebar-collapsed', shouldHide);
|
|
this.$sidebar.toggleClass('right-sidebar-expanded', shouldShow)
|
|
.toggleClass('right-sidebar-collapsed', shouldHide);
|
|
};
|
|
|
|
Build.prototype.sidebarOnResize = function () {
|
|
this.toggleSidebar(this.shouldHideSidebarForViewport());
|
|
};
|
|
|
|
Build.prototype.sidebarOnClick = function () {
|
|
if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
|
|
};
|
|
|
|
Build.prototype.updateArtifactRemoveDate = function () {
|
|
const $date = $('.js-artifacts-remove');
|
|
if ($date.length) {
|
|
const date = $date.text();
|
|
return $date.text(
|
|
gl.utils.timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '),
|
|
);
|
|
}
|
|
};
|
|
|
|
Build.prototype.populateJobs = function (stage) {
|
|
$('.build-job').hide();
|
|
$(`.build-job[data-stage="${stage}"]`).show();
|
|
};
|
|
|
|
Build.prototype.updateStageDropdownText = function (stage) {
|
|
$('.stage-selection').text(stage);
|
|
};
|
|
|
|
Build.prototype.updateDropdown = function (e) {
|
|
e.preventDefault();
|
|
const stage = e.currentTarget.text;
|
|
this.updateStageDropdownText(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;
|
|
})();
|