Use modules in common utils
This commit is contained in:
parent
5d952f756b
commit
6a1b84c7b4
|
|
@ -2,6 +2,7 @@
|
|||
/* global Flash */
|
||||
import _ from 'underscore';
|
||||
import Cookies from 'js-cookie';
|
||||
import { isInIssuePage, updateTooltipTitle } from './lib/utils/common_utils';
|
||||
|
||||
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
|
||||
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
|
||||
|
|
@ -237,7 +238,7 @@ class AwardsHandler {
|
|||
addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) {
|
||||
const isMainAwardsBlock = votesBlock.closest('.js-issue-note-awards').length;
|
||||
|
||||
if (gl.utils.isInIssuePage() && !isMainAwardsBlock) {
|
||||
if (isInIssuePage() && !isMainAwardsBlock) {
|
||||
const id = votesBlock.attr('id').replace('note_', '');
|
||||
|
||||
$('.emoji-menu').removeClass('is-visible');
|
||||
|
|
@ -288,7 +289,7 @@ class AwardsHandler {
|
|||
}
|
||||
|
||||
getVotesBlock() {
|
||||
if (gl.utils.isInIssuePage()) {
|
||||
if (isInIssuePage()) {
|
||||
const $el = $('.js-add-award.is-active').closest('.note.timeline-entry');
|
||||
|
||||
if ($el.length) {
|
||||
|
|
@ -452,11 +453,11 @@ class AwardsHandler {
|
|||
userAuthored($emojiButton) {
|
||||
const oldTitle = this.getAwardTooltip($emojiButton);
|
||||
const newTitle = 'You cannot vote on your own issue, MR and note';
|
||||
gl.utils.updateTooltipTitle($emojiButton, newTitle).tooltip('show');
|
||||
updateTooltipTitle($emojiButton, newTitle).tooltip('show');
|
||||
// Restore tooltip back to award list
|
||||
return setTimeout(() => {
|
||||
$emojiButton.tooltip('hide');
|
||||
gl.utils.updateTooltipTitle($emojiButton, oldTitle);
|
||||
updateTooltipTitle($emojiButton, oldTitle);
|
||||
}, 2800);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import '../commons/bootstrap';
|
||||
import { isInIssuePage } from '../lib/utils/common_utils';
|
||||
|
||||
// Quick Submit behavior
|
||||
//
|
||||
|
|
@ -45,7 +46,7 @@ $(document).on('keydown.quick_submit', '.js-quick-submit', (e) => {
|
|||
if (!$submitButton.attr('disabled')) {
|
||||
$submitButton.trigger('click', [e]);
|
||||
|
||||
if (!gl.utils.isInIssuePage()) {
|
||||
if (!isInIssuePage()) {
|
||||
$submitButton.disable();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, camelcase, one-var-declaration-per-line, no-else-return, max-len */
|
||||
import { rstrip } from './lib/utils/common_utils';
|
||||
|
||||
window.ConfirmDangerModal = (function() {
|
||||
function ConfirmDangerModal(form, text) {
|
||||
|
|
@ -12,7 +13,7 @@ window.ConfirmDangerModal = (function() {
|
|||
submit.disable();
|
||||
$('.js-confirm-danger-input').off('input');
|
||||
$('.js-confirm-danger-input').on('input', function() {
|
||||
if (gl.utils.rstrip($(this).val()) === project_path) {
|
||||
if (rstrip($(this).val()) === project_path) {
|
||||
return submit.enable();
|
||||
} else {
|
||||
return submit.disable();
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ import initProjectVisibilitySelector from './project_visibility';
|
|||
import GpgBadges from './gpg_badges';
|
||||
import UserFeatureHelper from './helpers/user_feature_helper';
|
||||
import initChangesDropdown from './init_changes_dropdown';
|
||||
import { ajaxGet } from './lib/utils/common_utils';
|
||||
|
||||
(function() {
|
||||
var Dispatcher;
|
||||
|
|
@ -351,7 +352,7 @@ import initChangesDropdown from './init_changes_dropdown';
|
|||
if ($('.blob-viewer').length) new BlobViewer();
|
||||
if ($('.project-show-activity').length) new gl.Activities();
|
||||
$('#tree-slider').waitForImages(function() {
|
||||
gl.utils.ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
|
||||
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
|
||||
});
|
||||
break;
|
||||
case 'projects:edit':
|
||||
|
|
@ -427,7 +428,7 @@ import initChangesDropdown from './init_changes_dropdown';
|
|||
new NewCommitForm($('.js-create-dir-form'));
|
||||
new UserCallout({ setCalloutPerProject: true });
|
||||
$('#tree-slider').waitForImages(function() {
|
||||
gl.utils.ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
|
||||
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
|
||||
});
|
||||
break;
|
||||
case 'projects:find_file:show':
|
||||
|
|
|
|||
|
|
@ -1,437 +1,438 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-param-reassign, no-else-return, quotes, object-shorthand, comma-dangle, camelcase, one-var, vars-on-top, one-var-declaration-per-line, no-return-assign, consistent-return, max-len, prefer-template */
|
||||
(function() {
|
||||
(function(w) {
|
||||
var base;
|
||||
const faviconEl = document.getElementById('favicon');
|
||||
const originalFavicon = faviconEl ? faviconEl.getAttribute('href') : null;
|
||||
w.gl || (w.gl = {});
|
||||
(base = w.gl).utils || (base.utils = {});
|
||||
w.gl.utils.isInGroupsPage = function() {
|
||||
return gl.utils.getPagePath() === 'groups';
|
||||
};
|
||||
w.gl.utils.isInProjectPage = function() {
|
||||
return gl.utils.getPagePath() === 'projects';
|
||||
};
|
||||
w.gl.utils.getProjectSlug = function() {
|
||||
if (this.isInProjectPage()) {
|
||||
return $('body').data('project');
|
||||
/* eslint-disable no-param-reassing */
|
||||
|
||||
export const getPagePath = (index = 0) => $('body').data('page').split(':')[index];
|
||||
window.gl.utils.getPagePath = getPagePath;
|
||||
|
||||
export const isInGroupsPage = () => getPagePath() === 'groups';
|
||||
window.gl.utils.isInGroupsPage = isInGroupsPage;
|
||||
|
||||
export const isInProjectPage = () => getPagePath() === 'projects';
|
||||
window.gl.utils.isInProjectPage = isInProjectPage;
|
||||
|
||||
export const getProjectSlug = () => {
|
||||
if (isInProjectPage()) {
|
||||
return $('body').data('project');
|
||||
}
|
||||
return null;
|
||||
};
|
||||
window.gl.utils.getProjectSlug = getProjectSlug;
|
||||
|
||||
export const getGroupSlug = () => {
|
||||
if (isInGroupsPage()) {
|
||||
return $('body').data('group');
|
||||
}
|
||||
return null;
|
||||
};
|
||||
window.gl.utils.getGroupSlug = getGroupSlug;
|
||||
|
||||
export const isInIssuePage = () => {
|
||||
const page = getPagePath(1);
|
||||
const action = getPagePath(2);
|
||||
|
||||
return page === 'issues' && action === 'show';
|
||||
};
|
||||
window.gl.utils.isInIssuePage = isInGroupsPage;
|
||||
|
||||
window.gl.utils.ajaxGet = url => $.ajax({
|
||||
type: 'GET',
|
||||
url,
|
||||
dataType: 'script',
|
||||
});
|
||||
export const ajaxGet = window.gl.utils.ajaxGet;
|
||||
|
||||
export const ajaxPost = (url, data) => $.ajax({
|
||||
type: 'POST',
|
||||
url,
|
||||
data,
|
||||
});
|
||||
window.gl.utils.ajaxPost = ajaxPost;
|
||||
|
||||
// TODO: This function seems not to be used anywhere
|
||||
window.gl.utils.extractLast = term => this.split(term).pop();
|
||||
|
||||
export const rstrip = function rstrip(val) {
|
||||
if (val) {
|
||||
return val.replace(/\s+$/, '');
|
||||
}
|
||||
return val;
|
||||
};
|
||||
window.gl.utils.rstrip = rstrip;
|
||||
|
||||
export const updateTooltipTitle = ($tooltipEl, newTitle) => $tooltipEl.attr('title', newTitle).tooltip('fixTitle');
|
||||
window.gl.utils.updateTooltipTitle = updateTooltipTitle;
|
||||
|
||||
export const disableButtonIfEmptyField = (fieldSelector, buttonSelector, eventName = 'input') => {
|
||||
const field = $(fieldSelector);
|
||||
const closestSubmit = field.closest('form').find(buttonSelector);
|
||||
if (rstrip(field.val()) === '') {
|
||||
closestSubmit.disable();
|
||||
}
|
||||
return field.on(eventName, () => {
|
||||
if (rstrip($(this).val()) === '') {
|
||||
return closestSubmit.disable();
|
||||
}
|
||||
return closestSubmit.enable();
|
||||
});
|
||||
};
|
||||
window.gl.utils.disableButtonIfEmptyField = disableButtonIfEmptyField;
|
||||
|
||||
// automatically adjust scroll position for hash urls taking the height of the navbar into account
|
||||
// https://github.com/twitter/bootstrap/issues/1768
|
||||
export const handleLocationHash = () => {
|
||||
let hash = window.gl.utils.getLocationHash();
|
||||
if (!hash) return;
|
||||
|
||||
// This is required to handle non-unicode characters in hash
|
||||
hash = decodeURIComponent(hash);
|
||||
|
||||
const fixedTabs = document.querySelector('.js-tabs-affix');
|
||||
const fixedDiffStats = document.querySelector('.js-diff-files-changed.is-stuck');
|
||||
const fixedNav = document.querySelector('.navbar-gitlab');
|
||||
|
||||
let adjustment = 0;
|
||||
if (fixedNav) adjustment -= fixedNav.offsetHeight;
|
||||
|
||||
// scroll to user-generated markdown anchor if we cannot find a match
|
||||
if (document.getElementById(hash) === null) {
|
||||
const target = document.getElementById(`user-content-${hash}`);
|
||||
if (target && target.scrollIntoView) {
|
||||
target.scrollIntoView(true);
|
||||
window.scrollBy(0, adjustment);
|
||||
}
|
||||
} else {
|
||||
// only adjust for fixedTabs when not targeting user-generated content
|
||||
if (fixedTabs) {
|
||||
adjustment -= fixedTabs.offsetHeight;
|
||||
}
|
||||
|
||||
if (fixedDiffStats) {
|
||||
adjustment -= fixedDiffStats.offsetHeight;
|
||||
}
|
||||
|
||||
window.scrollBy(0, adjustment);
|
||||
}
|
||||
};
|
||||
window.gl.utils.handleLocationHash = handleLocationHash;
|
||||
|
||||
// Check if element scrolled into viewport from above or below
|
||||
// Courtesy http://stackoverflow.com/a/7557433/414749
|
||||
export const isInViewport = (el) => {
|
||||
const rect = el.getBoundingClientRect();
|
||||
|
||||
return (
|
||||
rect.top >= 0 &&
|
||||
rect.left >= 0 &&
|
||||
rect.bottom <= window.innerHeight &&
|
||||
rect.right <= window.innerWidth
|
||||
);
|
||||
};
|
||||
window.gl.utils.isInViewport = isInViewport;
|
||||
|
||||
export const parseUrl = (url) => {
|
||||
const parser = document.createElement('a');
|
||||
parser.href = url;
|
||||
return parser;
|
||||
};
|
||||
window.gl.utils.parseUrl = parseUrl;
|
||||
|
||||
export const parseUrlPathname = (url) => {
|
||||
const parsedUrl = parseUrl(url);
|
||||
// parsedUrl.pathname will return an absolute path for Firefox and a relative path for IE11
|
||||
// We have to make sure we always have an absolute path.
|
||||
return parsedUrl.pathname.charAt(0) === '/' ? parsedUrl.pathname : `/${parsedUrl.pathname}`;
|
||||
};
|
||||
window.gl.utils.parseUrlPathname = parseUrlPathname;
|
||||
|
||||
// We can trust that each param has one & since values containing & will be encoded
|
||||
// Remove the first character of search as it is always ?
|
||||
window.gl.utils.getUrlParamsArray = () => window.location.search.slice(1).split('&').map((param) => {
|
||||
const split = param.split('=');
|
||||
return [decodeURI(split[0]), split[1]].join('=');
|
||||
});
|
||||
|
||||
export const isMetaKey = e => e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
|
||||
window.gl.utils.isMetaKey = isMetaKey;
|
||||
|
||||
// Identify following special clicks
|
||||
// 1) Cmd + Click on Mac (e.metaKey)
|
||||
// 2) Ctrl + Click on PC (e.ctrlKey)
|
||||
// 3) Middle-click or Mouse Wheel Click (e.which is 2)
|
||||
export const isMetaClick = e => e.metaKey || e.ctrlKey || e.which === 2;
|
||||
window.gl.utils.isMetaClick = isMetaClick;
|
||||
|
||||
export const scrollToElement = ($el) => {
|
||||
const top = $el.offset().top;
|
||||
const mrTabsHeight = $('.merge-request-tabs').height() || 0;
|
||||
const headerHeight = $('.navbar-gitlab').height() || 0;
|
||||
|
||||
return $('body, html').animate({
|
||||
scrollTop: top - mrTabsHeight - headerHeight,
|
||||
}, 200);
|
||||
};
|
||||
window.gl.utils.scrollToElement = scrollToElement;
|
||||
|
||||
/**
|
||||
this will take in the `name` of the param you want to parse in the url
|
||||
if the name does not exist this function will return `null`
|
||||
otherwise it will return the value of the param key provided
|
||||
*/
|
||||
export const getParameterByName = (name, urlToParse) => {
|
||||
const url = urlToParse || window.location.href;
|
||||
name = name.replace(/[[\]]/g, '\\$&');
|
||||
const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`);
|
||||
const results = regex.exec(url);
|
||||
if (!results) return null;
|
||||
if (!results[2]) return '';
|
||||
return decodeURIComponent(results[2].replace(/\+/g, ' '));
|
||||
};
|
||||
window.gl.utils.getParameterByName = getParameterByName;
|
||||
|
||||
export const getSelectedFragment = () => {
|
||||
const selection = window.getSelection();
|
||||
if (selection.rangeCount === 0) return null;
|
||||
const documentFragment = document.createDocumentFragment();
|
||||
for (let i = 0; i < selection.rangeCount; i += 1) {
|
||||
documentFragment.appendChild(selection.getRangeAt(i).cloneContents());
|
||||
}
|
||||
if (documentFragment.textContent.length === 0) return null;
|
||||
|
||||
return documentFragment;
|
||||
};
|
||||
window.gl.utils.getSelectedFragment = getSelectedFragment;
|
||||
|
||||
export const insertText = (target, text) => {
|
||||
// Firefox doesn't support `document.execCommand('insertText', false, text)` on textareas
|
||||
const selectionStart = target.selectionStart;
|
||||
const selectionEnd = target.selectionEnd;
|
||||
const value = target.value;
|
||||
|
||||
const textBefore = value.substring(0, selectionStart);
|
||||
const textAfter = value.substring(selectionEnd, value.length);
|
||||
|
||||
const insertedText = text instanceof Function ? text(textBefore, textAfter) : text;
|
||||
const newText = textBefore + insertedText + textAfter;
|
||||
|
||||
target.value = newText;
|
||||
target.selectionStart = target.selectionEnd = selectionStart + insertedText.length;
|
||||
|
||||
// Trigger autosave
|
||||
$(target).trigger('input');
|
||||
|
||||
// Trigger autosize
|
||||
const event = document.createEvent('Event');
|
||||
event.initEvent('autosize:update', true, false);
|
||||
target.dispatchEvent(event);
|
||||
};
|
||||
window.gl.utils.insertText = insertText;
|
||||
|
||||
export const nodeMatchesSelector = (node, selector) => {
|
||||
const matches = Element.prototype.matches ||
|
||||
Element.prototype.matchesSelector ||
|
||||
Element.prototype.mozMatchesSelector ||
|
||||
Element.prototype.msMatchesSelector ||
|
||||
Element.prototype.oMatchesSelector ||
|
||||
Element.prototype.webkitMatchesSelector;
|
||||
|
||||
if (matches) {
|
||||
return matches.call(node, selector);
|
||||
}
|
||||
|
||||
// IE11 doesn't support `node.matches(selector)`
|
||||
|
||||
let parentNode = node.parentNode;
|
||||
if (!parentNode) {
|
||||
parentNode = document.createElement('div');
|
||||
node = node.cloneNode(true);
|
||||
parentNode.appendChild(node);
|
||||
}
|
||||
|
||||
const matchingNodes = parentNode.querySelectorAll(selector);
|
||||
return Array.prototype.indexOf.call(matchingNodes, node) !== -1;
|
||||
};
|
||||
window.gl.utils.nodeMatchesSelector = nodeMatchesSelector;
|
||||
|
||||
/**
|
||||
this will take in the headers from an API response and normalize them
|
||||
this way we don't run into production issues when nginx gives us lowercased header keys
|
||||
*/
|
||||
export const normalizeHeaders = (headers) => {
|
||||
const upperCaseHeaders = {};
|
||||
|
||||
Object.keys(headers).forEach((e) => {
|
||||
upperCaseHeaders[e.toUpperCase()] = headers[e];
|
||||
});
|
||||
|
||||
return upperCaseHeaders;
|
||||
};
|
||||
window.gl.utils.normalizeHeaders = normalizeHeaders;
|
||||
|
||||
/**
|
||||
this will take in the getAllResponseHeaders result and normalize them
|
||||
this way we don't run into production issues when nginx gives us lowercased header keys
|
||||
*/
|
||||
export const normalizeCRLFHeaders = (headers) => {
|
||||
const headersObject = {};
|
||||
const headersArray = headers.split('\n');
|
||||
|
||||
headersArray.forEach((header) => {
|
||||
const keyValue = header.split(': ');
|
||||
headersObject[keyValue[0]] = keyValue[1];
|
||||
});
|
||||
|
||||
return normalizeHeaders(headersObject);
|
||||
};
|
||||
window.gl.utils.normalizeCRLFHeaders = normalizeCRLFHeaders;
|
||||
|
||||
/**
|
||||
* Parses pagination object string values into numbers.
|
||||
*
|
||||
* @param {Object} paginationInformation
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const parseIntPagination = paginationInformation => ({
|
||||
perPage: parseInt(paginationInformation['X-PER-PAGE'], 10),
|
||||
page: parseInt(paginationInformation['X-PAGE'], 10),
|
||||
total: parseInt(paginationInformation['X-TOTAL'], 10),
|
||||
totalPages: parseInt(paginationInformation['X-TOTAL-PAGES'], 10),
|
||||
nextPage: parseInt(paginationInformation['X-NEXT-PAGE'], 10),
|
||||
previousPage: parseInt(paginationInformation['X-PREV-PAGE'], 10),
|
||||
});
|
||||
window.gl.utils.parseIntPagination = parseIntPagination;
|
||||
|
||||
/**
|
||||
* Updates the search parameter of a URL given the parameter and value provided.
|
||||
*
|
||||
* If no search params are present we'll add it.
|
||||
* If param for page is already present, we'll update it
|
||||
* If there are params but not for the given one, we'll add it at the end.
|
||||
* Returns the new search parameters.
|
||||
*
|
||||
* @param {String} param
|
||||
* @param {Number|String|Undefined|Null} value
|
||||
* @return {String}
|
||||
*/
|
||||
export const setParamInURL = (param, value) => {
|
||||
let search;
|
||||
const locationSearch = window.location.search;
|
||||
|
||||
if (locationSearch.length) {
|
||||
const parameters = locationSearch.substring(1, locationSearch.length)
|
||||
.split('&')
|
||||
.reduce((acc, element) => {
|
||||
const val = element.split('=');
|
||||
acc[val[0]] = decodeURIComponent(val[1]);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
parameters[param] = value;
|
||||
|
||||
const toString = Object.keys(parameters)
|
||||
.map(val => `${val}=${encodeURIComponent(parameters[val])}`)
|
||||
.join('&');
|
||||
|
||||
search = `?${toString}`;
|
||||
} else {
|
||||
search = `?${param}=${value}`;
|
||||
}
|
||||
|
||||
return search;
|
||||
};
|
||||
window.gl.utils.setParamInURL = setParamInURL;
|
||||
|
||||
/**
|
||||
* Converts permission provided as strings to booleans.
|
||||
*
|
||||
* @param {String} string
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export const convertPermissionToBoolean = permission => permission === 'true';
|
||||
window.gl.utils.convertPermissionToBoolean = convertPermissionToBoolean;
|
||||
|
||||
/**
|
||||
* Back Off exponential algorithm
|
||||
* backOff :: (Function<next, stop>, Number) -> Promise<Any, Error>
|
||||
*
|
||||
* @param {Function<next, stop>} fn function to be called
|
||||
* @param {Number} timeout
|
||||
* @return {Promise<Any, Error>}
|
||||
* @example
|
||||
* ```
|
||||
* backOff(function (next, stop) {
|
||||
* // Let's perform this function repeatedly for 60s or for the timeout provided.
|
||||
*
|
||||
* ourFunction()
|
||||
* .then(function (result) {
|
||||
* // continue if result is not what we need
|
||||
* next();
|
||||
*
|
||||
* // when result is what we need let's stop with the repetions and jump out of the cycle
|
||||
* stop(result);
|
||||
* })
|
||||
* .catch(function (error) {
|
||||
* // if there is an error, we need to stop this with an error.
|
||||
* stop(error);
|
||||
* })
|
||||
* }, 60000)
|
||||
* .then(function (result) {})
|
||||
* .catch(function (error) {
|
||||
* // deal with errors passed to stop()
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export const backOff = (fn, timeout = 60000) => {
|
||||
const maxInterval = 32000;
|
||||
let nextInterval = 2000;
|
||||
let timeElapsed = 0;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const stop = arg => ((arg instanceof Error) ? reject(arg) : resolve(arg));
|
||||
|
||||
const next = () => {
|
||||
if (timeElapsed < timeout) {
|
||||
setTimeout(() => fn(next, stop), nextInterval);
|
||||
timeElapsed += nextInterval;
|
||||
nextInterval = Math.min(nextInterval + nextInterval, maxInterval);
|
||||
} else {
|
||||
return null;
|
||||
reject(new Error('BACKOFF_TIMEOUT'));
|
||||
}
|
||||
};
|
||||
w.gl.utils.getGroupSlug = function() {
|
||||
if (this.isInGroupsPage()) {
|
||||
return $('body').data('group');
|
||||
|
||||
fn(next, stop);
|
||||
});
|
||||
};
|
||||
window.gl.utils.backOff = backOff;
|
||||
|
||||
export const setFavicon = (faviconPath) => {
|
||||
const faviconEl = document.getElementById('favicon');
|
||||
if (faviconEl && faviconPath) {
|
||||
faviconEl.setAttribute('href', faviconPath);
|
||||
}
|
||||
};
|
||||
window.gl.utils.setFavicon = setFavicon;
|
||||
|
||||
export const resetFavicon = () => {
|
||||
const faviconEl = document.getElementById('favicon');
|
||||
const originalFavicon = faviconEl ? faviconEl.getAttribute('href') : null;
|
||||
if (faviconEl) {
|
||||
faviconEl.setAttribute('href', originalFavicon);
|
||||
}
|
||||
};
|
||||
window.gl.utils.resetFavicon = resetFavicon;
|
||||
|
||||
export const setCiStatusFavicon = (pageUrl) => {
|
||||
$.ajax({
|
||||
url: pageUrl,
|
||||
dataType: 'json',
|
||||
success: (data) => {
|
||||
if (data && data.favicon) {
|
||||
gl.utils.setFavicon(data.favicon);
|
||||
} else {
|
||||
return null;
|
||||
gl.utils.resetFavicon();
|
||||
}
|
||||
};
|
||||
|
||||
w.gl.utils.isInIssuePage = () => {
|
||||
const page = gl.utils.getPagePath(1);
|
||||
const action = gl.utils.getPagePath(2);
|
||||
|
||||
return page === 'issues' && action === 'show';
|
||||
};
|
||||
|
||||
w.gl.utils.ajaxGet = function(url) {
|
||||
return $.ajax({
|
||||
type: "GET",
|
||||
url: url,
|
||||
dataType: "script"
|
||||
});
|
||||
};
|
||||
|
||||
w.gl.utils.ajaxPost = function(url, data) {
|
||||
return $.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
w.gl.utils.extractLast = function(term) {
|
||||
return this.split(term).pop();
|
||||
};
|
||||
|
||||
w.gl.utils.rstrip = function rstrip(val) {
|
||||
if (val) {
|
||||
return val.replace(/\s+$/, '');
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
};
|
||||
|
||||
gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) {
|
||||
return $tooltipEl.attr('title', newTitle).tooltip('fixTitle');
|
||||
};
|
||||
|
||||
w.gl.utils.disableButtonIfEmptyField = function(field_selector, button_selector, event_name) {
|
||||
event_name = event_name || 'input';
|
||||
var closest_submit, field, that;
|
||||
that = this;
|
||||
field = $(field_selector);
|
||||
closest_submit = field.closest('form').find(button_selector);
|
||||
if (this.rstrip(field.val()) === "") {
|
||||
closest_submit.disable();
|
||||
}
|
||||
return field.on(event_name, function() {
|
||||
if (that.rstrip($(this).val()) === "") {
|
||||
return closest_submit.disable();
|
||||
} else {
|
||||
return closest_submit.enable();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// automatically adjust scroll position for hash urls taking the height of the navbar into account
|
||||
// https://github.com/twitter/bootstrap/issues/1768
|
||||
w.gl.utils.handleLocationHash = function() {
|
||||
var hash = w.gl.utils.getLocationHash();
|
||||
if (!hash) return;
|
||||
|
||||
// This is required to handle non-unicode characters in hash
|
||||
hash = decodeURIComponent(hash);
|
||||
|
||||
const fixedTabs = document.querySelector('.js-tabs-affix');
|
||||
const fixedDiffStats = document.querySelector('.js-diff-files-changed.is-stuck');
|
||||
const fixedNav = document.querySelector('.navbar-gitlab');
|
||||
|
||||
var adjustment = 0;
|
||||
if (fixedNav) adjustment -= fixedNav.offsetHeight;
|
||||
|
||||
// scroll to user-generated markdown anchor if we cannot find a match
|
||||
if (document.getElementById(hash) === null) {
|
||||
var target = document.getElementById('user-content-' + hash);
|
||||
if (target && target.scrollIntoView) {
|
||||
target.scrollIntoView(true);
|
||||
window.scrollBy(0, adjustment);
|
||||
}
|
||||
} else {
|
||||
// only adjust for fixedTabs when not targeting user-generated content
|
||||
if (fixedTabs) {
|
||||
adjustment -= fixedTabs.offsetHeight;
|
||||
}
|
||||
|
||||
if (fixedDiffStats) {
|
||||
adjustment -= fixedDiffStats.offsetHeight;
|
||||
}
|
||||
|
||||
window.scrollBy(0, adjustment);
|
||||
}
|
||||
};
|
||||
|
||||
// Check if element scrolled into viewport from above or below
|
||||
// Courtesy http://stackoverflow.com/a/7557433/414749
|
||||
w.gl.utils.isInViewport = function(el) {
|
||||
var rect = el.getBoundingClientRect();
|
||||
|
||||
return (
|
||||
rect.top >= 0 &&
|
||||
rect.left >= 0 &&
|
||||
rect.bottom <= window.innerHeight &&
|
||||
rect.right <= window.innerWidth
|
||||
);
|
||||
};
|
||||
|
||||
gl.utils.getPagePath = function(index) {
|
||||
index = index || 0;
|
||||
return $('body').data('page').split(':')[index];
|
||||
};
|
||||
|
||||
gl.utils.parseUrl = function (url) {
|
||||
var parser = document.createElement('a');
|
||||
parser.href = url;
|
||||
return parser;
|
||||
};
|
||||
|
||||
gl.utils.parseUrlPathname = function (url) {
|
||||
var parsedUrl = gl.utils.parseUrl(url);
|
||||
// parsedUrl.pathname will return an absolute path for Firefox and a relative path for IE11
|
||||
// We have to make sure we always have an absolute path.
|
||||
return parsedUrl.pathname.charAt(0) === '/' ? parsedUrl.pathname : '/' + parsedUrl.pathname;
|
||||
};
|
||||
|
||||
gl.utils.getUrlParamsArray = function () {
|
||||
// We can trust that each param has one & since values containing & will be encoded
|
||||
// Remove the first character of search as it is always ?
|
||||
return window.location.search.slice(1).split('&').map((param) => {
|
||||
const split = param.split('=');
|
||||
return [decodeURI(split[0]), split[1]].join('=');
|
||||
});
|
||||
};
|
||||
|
||||
gl.utils.isMetaKey = function(e) {
|
||||
return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
|
||||
};
|
||||
|
||||
gl.utils.isMetaClick = function(e) {
|
||||
// Identify following special clicks
|
||||
// 1) Cmd + Click on Mac (e.metaKey)
|
||||
// 2) Ctrl + Click on PC (e.ctrlKey)
|
||||
// 3) Middle-click or Mouse Wheel Click (e.which is 2)
|
||||
return e.metaKey || e.ctrlKey || e.which === 2;
|
||||
};
|
||||
|
||||
gl.utils.scrollToElement = function($el) {
|
||||
const top = $el.offset().top;
|
||||
const mrTabsHeight = $('.merge-request-tabs').height() || 0;
|
||||
const headerHeight = $('.navbar-gitlab').height() || 0;
|
||||
|
||||
return $('body, html').animate({
|
||||
scrollTop: top - mrTabsHeight - headerHeight,
|
||||
}, 200);
|
||||
};
|
||||
|
||||
/**
|
||||
this will take in the `name` of the param you want to parse in the url
|
||||
if the name does not exist this function will return `null`
|
||||
otherwise it will return the value of the param key provided
|
||||
*/
|
||||
w.gl.utils.getParameterByName = (name, parseUrl) => {
|
||||
const url = parseUrl || window.location.href;
|
||||
name = name.replace(/[[\]]/g, '\\$&');
|
||||
const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`);
|
||||
const results = regex.exec(url);
|
||||
if (!results) return null;
|
||||
if (!results[2]) return '';
|
||||
return decodeURIComponent(results[2].replace(/\+/g, ' '));
|
||||
};
|
||||
|
||||
w.gl.utils.getSelectedFragment = () => {
|
||||
const selection = window.getSelection();
|
||||
if (selection.rangeCount === 0) return null;
|
||||
const documentFragment = document.createDocumentFragment();
|
||||
for (let i = 0; i < selection.rangeCount; i += 1) {
|
||||
documentFragment.appendChild(selection.getRangeAt(i).cloneContents());
|
||||
}
|
||||
if (documentFragment.textContent.length === 0) return null;
|
||||
|
||||
return documentFragment;
|
||||
};
|
||||
|
||||
w.gl.utils.insertText = (target, text) => {
|
||||
// Firefox doesn't support `document.execCommand('insertText', false, text)` on textareas
|
||||
|
||||
const selectionStart = target.selectionStart;
|
||||
const selectionEnd = target.selectionEnd;
|
||||
const value = target.value;
|
||||
|
||||
const textBefore = value.substring(0, selectionStart);
|
||||
const textAfter = value.substring(selectionEnd, value.length);
|
||||
|
||||
const insertedText = text instanceof Function ? text(textBefore, textAfter) : text;
|
||||
const newText = textBefore + insertedText + textAfter;
|
||||
|
||||
target.value = newText;
|
||||
target.selectionStart = target.selectionEnd = selectionStart + insertedText.length;
|
||||
|
||||
// Trigger autosave
|
||||
$(target).trigger('input');
|
||||
|
||||
// Trigger autosize
|
||||
var event = document.createEvent('Event');
|
||||
event.initEvent('autosize:update', true, false);
|
||||
target.dispatchEvent(event);
|
||||
};
|
||||
|
||||
w.gl.utils.nodeMatchesSelector = (node, selector) => {
|
||||
const matches = Element.prototype.matches ||
|
||||
Element.prototype.matchesSelector ||
|
||||
Element.prototype.mozMatchesSelector ||
|
||||
Element.prototype.msMatchesSelector ||
|
||||
Element.prototype.oMatchesSelector ||
|
||||
Element.prototype.webkitMatchesSelector;
|
||||
|
||||
if (matches) {
|
||||
return matches.call(node, selector);
|
||||
}
|
||||
|
||||
// IE11 doesn't support `node.matches(selector)`
|
||||
|
||||
let parentNode = node.parentNode;
|
||||
if (!parentNode) {
|
||||
parentNode = document.createElement('div');
|
||||
node = node.cloneNode(true);
|
||||
parentNode.appendChild(node);
|
||||
}
|
||||
|
||||
const matchingNodes = parentNode.querySelectorAll(selector);
|
||||
return Array.prototype.indexOf.call(matchingNodes, node) !== -1;
|
||||
};
|
||||
|
||||
/**
|
||||
this will take in the headers from an API response and normalize them
|
||||
this way we don't run into production issues when nginx gives us lowercased header keys
|
||||
*/
|
||||
w.gl.utils.normalizeHeaders = (headers) => {
|
||||
const upperCaseHeaders = {};
|
||||
|
||||
Object.keys(headers).forEach((e) => {
|
||||
upperCaseHeaders[e.toUpperCase()] = headers[e];
|
||||
});
|
||||
|
||||
return upperCaseHeaders;
|
||||
};
|
||||
|
||||
/**
|
||||
this will take in the getAllResponseHeaders result and normalize them
|
||||
this way we don't run into production issues when nginx gives us lowercased header keys
|
||||
*/
|
||||
w.gl.utils.normalizeCRLFHeaders = (headers) => {
|
||||
const headersObject = {};
|
||||
const headersArray = headers.split('\n');
|
||||
|
||||
headersArray.forEach((header) => {
|
||||
const keyValue = header.split(': ');
|
||||
headersObject[keyValue[0]] = keyValue[1];
|
||||
});
|
||||
|
||||
return w.gl.utils.normalizeHeaders(headersObject);
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses pagination object string values into numbers.
|
||||
*
|
||||
* @param {Object} paginationInformation
|
||||
* @returns {Object}
|
||||
*/
|
||||
w.gl.utils.parseIntPagination = paginationInformation => ({
|
||||
perPage: parseInt(paginationInformation['X-PER-PAGE'], 10),
|
||||
page: parseInt(paginationInformation['X-PAGE'], 10),
|
||||
total: parseInt(paginationInformation['X-TOTAL'], 10),
|
||||
totalPages: parseInt(paginationInformation['X-TOTAL-PAGES'], 10),
|
||||
nextPage: parseInt(paginationInformation['X-NEXT-PAGE'], 10),
|
||||
previousPage: parseInt(paginationInformation['X-PREV-PAGE'], 10),
|
||||
});
|
||||
|
||||
/**
|
||||
* Updates the search parameter of a URL given the parameter and value provided.
|
||||
*
|
||||
* If no search params are present we'll add it.
|
||||
* If param for page is already present, we'll update it
|
||||
* If there are params but not for the given one, we'll add it at the end.
|
||||
* Returns the new search parameters.
|
||||
*
|
||||
* @param {String} param
|
||||
* @param {Number|String|Undefined|Null} value
|
||||
* @return {String}
|
||||
*/
|
||||
w.gl.utils.setParamInURL = (param, value) => {
|
||||
let search;
|
||||
const locationSearch = window.location.search;
|
||||
|
||||
if (locationSearch.length) {
|
||||
const parameters = locationSearch.substring(1, locationSearch.length)
|
||||
.split('&')
|
||||
.reduce((acc, element) => {
|
||||
const val = element.split('=');
|
||||
acc[val[0]] = decodeURIComponent(val[1]);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
parameters[param] = value;
|
||||
|
||||
const toString = Object.keys(parameters)
|
||||
.map(val => `${val}=${encodeURIComponent(parameters[val])}`)
|
||||
.join('&');
|
||||
|
||||
search = `?${toString}`;
|
||||
} else {
|
||||
search = `?${param}=${value}`;
|
||||
}
|
||||
|
||||
return search;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts permission provided as strings to booleans.
|
||||
*
|
||||
* @param {String} string
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
w.gl.utils.convertPermissionToBoolean = permission => permission === 'true';
|
||||
|
||||
/**
|
||||
* Back Off exponential algorithm
|
||||
* backOff :: (Function<next, stop>, Number) -> Promise<Any, Error>
|
||||
*
|
||||
* @param {Function<next, stop>} fn function to be called
|
||||
* @param {Number} timeout
|
||||
* @return {Promise<Any, Error>}
|
||||
* @example
|
||||
* ```
|
||||
* backOff(function (next, stop) {
|
||||
* // Let's perform this function repeatedly for 60s or for the timeout provided.
|
||||
*
|
||||
* ourFunction()
|
||||
* .then(function (result) {
|
||||
* // continue if result is not what we need
|
||||
* next();
|
||||
*
|
||||
* // when result is what we need let's stop with the repetions and jump out of the cycle
|
||||
* stop(result);
|
||||
* })
|
||||
* .catch(function (error) {
|
||||
* // if there is an error, we need to stop this with an error.
|
||||
* stop(error);
|
||||
* })
|
||||
* }, 60000)
|
||||
* .then(function (result) {})
|
||||
* .catch(function (error) {
|
||||
* // deal with errors passed to stop()
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
w.gl.utils.backOff = (fn, timeout = 60000) => {
|
||||
const maxInterval = 32000;
|
||||
let nextInterval = 2000;
|
||||
let timeElapsed = 0;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const stop = arg => ((arg instanceof Error) ? reject(arg) : resolve(arg));
|
||||
|
||||
const next = () => {
|
||||
if (timeElapsed < timeout) {
|
||||
setTimeout(() => fn(next, stop), nextInterval);
|
||||
timeElapsed += nextInterval;
|
||||
nextInterval = Math.min(nextInterval + nextInterval, maxInterval);
|
||||
} else {
|
||||
reject(new Error('BACKOFF_TIMEOUT'));
|
||||
}
|
||||
};
|
||||
|
||||
fn(next, stop);
|
||||
});
|
||||
};
|
||||
|
||||
w.gl.utils.setFavicon = (faviconPath) => {
|
||||
if (faviconEl && faviconPath) {
|
||||
faviconEl.setAttribute('href', faviconPath);
|
||||
}
|
||||
};
|
||||
|
||||
w.gl.utils.resetFavicon = () => {
|
||||
if (faviconEl) {
|
||||
faviconEl.setAttribute('href', originalFavicon);
|
||||
}
|
||||
};
|
||||
|
||||
w.gl.utils.setCiStatusFavicon = (pageUrl) => {
|
||||
$.ajax({
|
||||
url: pageUrl,
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
if (data && data.favicon) {
|
||||
gl.utils.setFavicon(data.favicon);
|
||||
} else {
|
||||
gl.utils.resetFavicon();
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
gl.utils.resetFavicon();
|
||||
}
|
||||
});
|
||||
};
|
||||
})(window);
|
||||
}).call(window);
|
||||
},
|
||||
error: () => {
|
||||
gl.utils.resetFavicon();
|
||||
},
|
||||
});
|
||||
};
|
||||
window.gl.utils.setCiStatusFavicon = setCiStatusFavicon;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import './flash';
|
|||
import BlobForkSuggestion from './blob/blob_fork_suggestion';
|
||||
import initChangesDropdown from './init_changes_dropdown';
|
||||
import bp from './breakpoints';
|
||||
import parseUrlPathname from './lib/utils/common_utils';
|
||||
|
||||
/* eslint-disable max-len */
|
||||
// MergeRequestTabs
|
||||
|
|
@ -260,7 +261,7 @@ import bp from './breakpoints';
|
|||
|
||||
// We extract pathname for the current Changes tab anchor href
|
||||
// some pages like MergeRequestsController#new has query parameters on that anchor
|
||||
const urlPathname = gl.utils.parseUrlPathname(source);
|
||||
const urlPathname = parseUrlPathname(source);
|
||||
|
||||
this.ajaxGet({
|
||||
url: `${urlPathname}.json${location.search}`,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import EmptyState from './empty_state.vue';
|
||||
import MonitoringStore from '../stores/monitoring_store';
|
||||
import eventHub from '../event_hub';
|
||||
import { backOff } from '../../lib/utils/common_utils';
|
||||
|
||||
export default {
|
||||
|
||||
|
|
@ -41,7 +42,7 @@
|
|||
getGraphsData() {
|
||||
const maxNumberOfRequests = 3;
|
||||
this.state = 'loading';
|
||||
gl.utils.backOff((next, stop) => {
|
||||
backOff((next, stop) => {
|
||||
this.service.get().then((resp) => {
|
||||
if (resp.status === statusCodes.NO_CONTENT) {
|
||||
this.backOffRequestCounter = this.backOffRequestCounter += 1;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import loadAwardsHandler from './awards_handler';
|
|||
import './autosave';
|
||||
import './dropzone_input';
|
||||
import TaskList from './task_list';
|
||||
import { ajaxPost, isInViewport, getPagePath } from './lib/utils/common_utils';
|
||||
|
||||
window.autosize = autosize;
|
||||
window.Dropzone = Dropzone;
|
||||
|
|
@ -81,7 +82,7 @@ export default class Notes {
|
|||
this.setViewType(view);
|
||||
|
||||
// We are in the Merge Requests page so we need another edit form for Changes tab
|
||||
if (gl.utils.getPagePath(1) === 'merge_requests') {
|
||||
if (getPagePath(1) === 'merge_requests') {
|
||||
$('.note-edit-form').clone()
|
||||
.addClass('mr-note-edit-form').insertAfter('.note-edit-form');
|
||||
}
|
||||
|
|
@ -644,10 +645,10 @@ export default class Notes {
|
|||
}
|
||||
else {
|
||||
var $buttons = $el.find('.note-form-actions');
|
||||
var isWidgetVisible = gl.utils.isInViewport($el.get(0));
|
||||
var isWidgetVisible = isInViewport($el.get(0));
|
||||
|
||||
if (!isWidgetVisible) {
|
||||
gl.utils.scrollToElement($el);
|
||||
scrollToElement($el);
|
||||
}
|
||||
|
||||
$el.find('.js-finish-edit-warning').show();
|
||||
|
|
@ -1188,7 +1189,7 @@ export default class Notes {
|
|||
}
|
||||
|
||||
static checkMergeRequestStatus() {
|
||||
if (gl.utils.getPagePath(1) === 'merge_requests') {
|
||||
if (getPagePath(1) === 'merge_requests') {
|
||||
gl.mrWidget.checkStatus();
|
||||
}
|
||||
}
|
||||
|
|
@ -1326,7 +1327,7 @@ export default class Notes {
|
|||
* 2) Identify comment type; a) Main thread b) Discussion thread c) Discussion resolve
|
||||
* 3) Build temporary placeholder element (using `createPlaceholderNote`)
|
||||
* 4) Show placeholder note on UI
|
||||
* 5) Perform network request to submit the note using `gl.utils.ajaxPost`
|
||||
* 5) Perform network request to submit the note using `ajaxPost`
|
||||
* a) If request is successfully completed
|
||||
* 1. Remove placeholder element
|
||||
* 2. Show submitted Note element
|
||||
|
|
@ -1408,7 +1409,7 @@ export default class Notes {
|
|||
|
||||
/* eslint-disable promise/catch-or-return */
|
||||
// Make request to submit comment on server
|
||||
gl.utils.ajaxPost(formAction, formData)
|
||||
ajaxPost(formAction, formData)
|
||||
.then((note) => {
|
||||
// Submission successful! remove placeholder
|
||||
$notesContainer.find(`#${noteUniqueId}`).remove();
|
||||
|
|
@ -1510,7 +1511,7 @@ export default class Notes {
|
|||
|
||||
/* eslint-disable promise/catch-or-return */
|
||||
// Make request to update comment on server
|
||||
gl.utils.ajaxPost(formAction, formData)
|
||||
ajaxPost(formAction, formData)
|
||||
.then((note) => {
|
||||
// Submission successful! render final note element
|
||||
this.updateNote(note, $editingNote);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import * as constants from '../constants';
|
|||
import service from '../services/issue_notes_service';
|
||||
import loadAwardsHandler from '../../awards_handler';
|
||||
import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
|
||||
import { isInViewport, scrollToElement } from '../../lib/utils/common_utils';
|
||||
|
||||
let eTagPoll;
|
||||
|
||||
|
|
@ -211,7 +212,7 @@ export const toggleAwardRequest = ({ commit, getters, dispatch }, data) => {
|
|||
};
|
||||
|
||||
export const scrollToNoteIfNeeded = (context, el) => {
|
||||
if (!gl.utils.isInViewport(el[0])) {
|
||||
gl.utils.scrollToElement(el);
|
||||
if (!isInViewport(el[0])) {
|
||||
scrollToElement(el);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
/* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */
|
||||
/* global Flash */
|
||||
import { getPagePath } from '../lib/utils/common_utils';
|
||||
|
||||
((global) => {
|
||||
class Profile {
|
||||
|
|
@ -93,7 +94,7 @@
|
|||
return $title.val(comment[1]).change();
|
||||
}
|
||||
});
|
||||
if (global.utils.getPagePath() === 'profiles') {
|
||||
if (getPagePath() === 'profiles') {
|
||||
return new Profile();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import PANEL_STATE from './constants';
|
||||
import { backOff } from '../lib/utils/common_utils';
|
||||
|
||||
export default class PrometheusMetrics {
|
||||
constructor(wrapperSelector) {
|
||||
|
|
@ -79,7 +80,7 @@ export default class PrometheusMetrics {
|
|||
|
||||
loadActiveMetrics() {
|
||||
this.showMonitoringMetricsPanelState(PANEL_STATE.LOADING);
|
||||
gl.utils.backOff((next, stop) => {
|
||||
backOff((next, stop) => {
|
||||
$.getJSON(this.activeMetricsEndpoint)
|
||||
.done((res) => {
|
||||
if (res && res.success) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
/* eslint-disable comma-dangle, no-return-assign, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-unused-vars, no-cond-assign, consistent-return, object-shorthand, prefer-arrow-callback, func-names, space-before-function-paren, prefer-template, quotes, class-methods-use-this, no-unused-expressions, no-sequences, wrap-iife, no-lonely-if, no-else-return, no-param-reassign, vars-on-top, max-len */
|
||||
import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from './lib/utils/common_utils';
|
||||
|
||||
((global) => {
|
||||
const KEYCODE = {
|
||||
|
|
@ -146,14 +147,14 @@
|
|||
}
|
||||
|
||||
getCategoryContents() {
|
||||
var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, userName, utils;
|
||||
var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, userName;
|
||||
userId = gon.current_user_id;
|
||||
userName = gon.current_username;
|
||||
utils = gl.utils, projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions;
|
||||
if (utils.isInGroupsPage() && groupOptions) {
|
||||
options = groupOptions[utils.getGroupSlug()];
|
||||
} else if (utils.isInProjectPage() && projectOptions) {
|
||||
options = projectOptions[utils.getProjectSlug()];
|
||||
projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions;
|
||||
if (isInGroupsPage() && groupOptions) {
|
||||
options = groupOptions[getGroupSlug()];
|
||||
} else if (isInProjectPage() && projectOptions) {
|
||||
options = projectOptions[getProjectSlug()];
|
||||
} else if (dashboardOptions) {
|
||||
options = dashboardOptions;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import statusCodes from '../../lib/utils/http_status';
|
||||
import { bytesToMiB } from '../../lib/utils/number_utils';
|
||||
|
||||
import { backOff } from '../../lib/utils/common_utils';
|
||||
import MemoryGraph from '../../vue_shared/components/memory_graph';
|
||||
import MRWidgetService from '../services/mr_widget_service';
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ export default {
|
|||
}
|
||||
},
|
||||
loadMetrics() {
|
||||
gl.utils.backOff((next, stop) => {
|
||||
backOff((next, stop) => {
|
||||
MRWidgetService.fetchMetrics(this.metricsUrl)
|
||||
.then((res) => {
|
||||
if (res.status === statusCodes.NO_CONTENT) {
|
||||
|
|
|
|||
|
|
@ -1,398 +1,396 @@
|
|||
/* eslint-disable promise/catch-or-return */
|
||||
|
||||
import '~/lib/utils/common_utils';
|
||||
import * as commonUtils from '~/lib/utils/common_utils';
|
||||
|
||||
(() => {
|
||||
describe('common_utils', () => {
|
||||
describe('gl.utils.parseUrl', () => {
|
||||
it('returns an anchor tag with url', () => {
|
||||
expect(gl.utils.parseUrl('/some/absolute/url').pathname).toContain('some/absolute/url');
|
||||
});
|
||||
it('url is escaped', () => {
|
||||
// IE11 will return a relative pathname while other browsers will return a full pathname.
|
||||
// parseUrl uses an anchor element for parsing an url. With relative urls, the anchor
|
||||
// element will create an absolute url relative to the current execution context.
|
||||
// The JavaScript test suite is executed at '/' which will lead to an absolute url
|
||||
// starting with '/'.
|
||||
expect(gl.utils.parseUrl('" test="asf"').pathname).toContain('/%22%20test=%22asf%22');
|
||||
});
|
||||
describe('common_utils', () => {
|
||||
describe('parseUrl', () => {
|
||||
it('returns an anchor tag with url', () => {
|
||||
expect(commonUtils.parseUrl('/some/absolute/url').pathname).toContain('some/absolute/url');
|
||||
});
|
||||
it('url is escaped', () => {
|
||||
// IE11 will return a relative pathname while other browsers will return a full pathname.
|
||||
// parseUrl uses an anchor element for parsing an url. With relative urls, the anchor
|
||||
// element will create an absolute url relative to the current execution context.
|
||||
// The JavaScript test suite is executed at '/' which will lead to an absolute url
|
||||
// starting with '/'.
|
||||
expect(commonUtils.parseUrl('" test="asf"').pathname).toContain('/%22%20test=%22asf%22');
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseUrlPathname', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(gl.utils, 'parseUrl').and.callFake(url => ({
|
||||
pathname: url,
|
||||
}));
|
||||
});
|
||||
it('returns an absolute url when given an absolute url', () => {
|
||||
expect(commonUtils.parseUrlPathname('/some/absolute/url')).toEqual('/some/absolute/url');
|
||||
});
|
||||
it('returns an absolute url when given a relative url', () => {
|
||||
expect(commonUtils.parseUrlPathname('some/relative/url')).toEqual('/some/relative/url');
|
||||
});
|
||||
});
|
||||
|
||||
describe('gl.utils.getUrlParamsArray', () => {
|
||||
it('should return params array', () => {
|
||||
expect(gl.utils.getUrlParamsArray() instanceof Array).toBe(true);
|
||||
});
|
||||
|
||||
describe('gl.utils.parseUrlPathname', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(gl.utils, 'parseUrl').and.callFake(url => ({
|
||||
pathname: url,
|
||||
}));
|
||||
});
|
||||
it('returns an absolute url when given an absolute url', () => {
|
||||
expect(gl.utils.parseUrlPathname('/some/absolute/url')).toEqual('/some/absolute/url');
|
||||
});
|
||||
it('returns an absolute url when given a relative url', () => {
|
||||
expect(gl.utils.parseUrlPathname('some/relative/url')).toEqual('/some/relative/url');
|
||||
});
|
||||
it('should remove the question mark from the search params', () => {
|
||||
const paramsArray = gl.utils.getUrlParamsArray();
|
||||
expect(paramsArray[0][0] !== '?').toBe(true);
|
||||
});
|
||||
|
||||
describe('gl.utils.getUrlParamsArray', () => {
|
||||
it('should return params array', () => {
|
||||
expect(gl.utils.getUrlParamsArray() instanceof Array).toBe(true);
|
||||
});
|
||||
it('should decode params', () => {
|
||||
history.pushState('', '', '?label_name%5B%5D=test');
|
||||
|
||||
it('should remove the question mark from the search params', () => {
|
||||
const paramsArray = gl.utils.getUrlParamsArray();
|
||||
expect(paramsArray[0][0] !== '?').toBe(true);
|
||||
});
|
||||
expect(
|
||||
gl.utils.getUrlParamsArray()[0],
|
||||
).toBe('label_name[]=test');
|
||||
|
||||
it('should decode params', () => {
|
||||
history.pushState('', '', '?label_name%5B%5D=test');
|
||||
history.pushState('', '', '?');
|
||||
});
|
||||
});
|
||||
|
||||
expect(
|
||||
gl.utils.getUrlParamsArray()[0],
|
||||
).toBe('label_name[]=test');
|
||||
|
||||
history.pushState('', '', '?');
|
||||
});
|
||||
describe('handleLocationHash', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(window.document, 'getElementById').and.callThrough();
|
||||
});
|
||||
|
||||
describe('gl.utils.handleLocationHash', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(window.document, 'getElementById').and.callThrough();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.history.pushState({}, null, '');
|
||||
});
|
||||
|
||||
function expectGetElementIdToHaveBeenCalledWith(elementId) {
|
||||
expect(window.document.getElementById).toHaveBeenCalledWith(elementId);
|
||||
}
|
||||
|
||||
it('decodes hash parameter', () => {
|
||||
window.history.pushState({}, null, '#random-hash');
|
||||
gl.utils.handleLocationHash();
|
||||
|
||||
expectGetElementIdToHaveBeenCalledWith('random-hash');
|
||||
expectGetElementIdToHaveBeenCalledWith('user-content-random-hash');
|
||||
});
|
||||
|
||||
it('decodes cyrillic hash parameter', () => {
|
||||
window.history.pushState({}, null, '#definição');
|
||||
gl.utils.handleLocationHash();
|
||||
|
||||
expectGetElementIdToHaveBeenCalledWith('definição');
|
||||
expectGetElementIdToHaveBeenCalledWith('user-content-definição');
|
||||
});
|
||||
|
||||
it('decodes encoded cyrillic hash parameter', () => {
|
||||
window.history.pushState({}, null, '#defini%C3%A7%C3%A3o');
|
||||
gl.utils.handleLocationHash();
|
||||
|
||||
expectGetElementIdToHaveBeenCalledWith('definição');
|
||||
expectGetElementIdToHaveBeenCalledWith('user-content-definição');
|
||||
});
|
||||
afterEach(() => {
|
||||
window.history.pushState({}, null, '');
|
||||
});
|
||||
|
||||
describe('gl.utils.setParamInURL', () => {
|
||||
afterEach(() => {
|
||||
window.history.pushState({}, null, '');
|
||||
});
|
||||
function expectGetElementIdToHaveBeenCalledWith(elementId) {
|
||||
expect(window.document.getElementById).toHaveBeenCalledWith(elementId);
|
||||
}
|
||||
|
||||
it('should return the parameter', () => {
|
||||
window.history.replaceState({}, null, '');
|
||||
it('decodes hash parameter', () => {
|
||||
window.history.pushState({}, null, '#random-hash');
|
||||
commonUtils.handleLocationHash();
|
||||
|
||||
expect(gl.utils.setParamInURL('page', 156)).toBe('?page=156');
|
||||
expect(gl.utils.setParamInURL('page', '156')).toBe('?page=156');
|
||||
});
|
||||
|
||||
it('should update the existing parameter when its a number', () => {
|
||||
window.history.pushState({}, null, '?page=15');
|
||||
|
||||
expect(gl.utils.setParamInURL('page', 16)).toBe('?page=16');
|
||||
expect(gl.utils.setParamInURL('page', '16')).toBe('?page=16');
|
||||
expect(gl.utils.setParamInURL('page', true)).toBe('?page=true');
|
||||
});
|
||||
|
||||
it('should update the existing parameter when its a string', () => {
|
||||
window.history.pushState({}, null, '?scope=all');
|
||||
|
||||
expect(gl.utils.setParamInURL('scope', 'finished')).toBe('?scope=finished');
|
||||
});
|
||||
|
||||
it('should update the existing parameter when more than one parameter exists', () => {
|
||||
window.history.pushState({}, null, '?scope=all&page=15');
|
||||
|
||||
expect(gl.utils.setParamInURL('scope', 'finished')).toBe('?scope=finished&page=15');
|
||||
});
|
||||
|
||||
it('should add a new parameter to the end of the existing ones', () => {
|
||||
window.history.pushState({}, null, '?scope=all');
|
||||
|
||||
expect(gl.utils.setParamInURL('page', 16)).toBe('?scope=all&page=16');
|
||||
expect(gl.utils.setParamInURL('page', '16')).toBe('?scope=all&page=16');
|
||||
expect(gl.utils.setParamInURL('page', true)).toBe('?scope=all&page=true');
|
||||
});
|
||||
expectGetElementIdToHaveBeenCalledWith('random-hash');
|
||||
expectGetElementIdToHaveBeenCalledWith('user-content-random-hash');
|
||||
});
|
||||
|
||||
describe('gl.utils.getParameterByName', () => {
|
||||
beforeEach(() => {
|
||||
window.history.pushState({}, null, '?scope=all&p=2');
|
||||
});
|
||||
it('decodes cyrillic hash parameter', () => {
|
||||
window.history.pushState({}, null, '#definição');
|
||||
commonUtils.handleLocationHash();
|
||||
|
||||
afterEach(() => {
|
||||
window.history.replaceState({}, null, null);
|
||||
});
|
||||
|
||||
it('should return valid parameter', () => {
|
||||
const value = gl.utils.getParameterByName('scope');
|
||||
expect(gl.utils.getParameterByName('p')).toEqual('2');
|
||||
expect(value).toBe('all');
|
||||
});
|
||||
|
||||
it('should return invalid parameter', () => {
|
||||
const value = gl.utils.getParameterByName('fakeParameter');
|
||||
expect(value).toBe(null);
|
||||
});
|
||||
|
||||
it('should return valid paramentes if URL is provided', () => {
|
||||
let value = gl.utils.getParameterByName('foo', 'http://cocteau.twins/?foo=bar');
|
||||
expect(value).toBe('bar');
|
||||
|
||||
value = gl.utils.getParameterByName('manan', 'http://cocteau.twins/?foo=bar&manan=canchu');
|
||||
expect(value).toBe('canchu');
|
||||
});
|
||||
expectGetElementIdToHaveBeenCalledWith('definição');
|
||||
expectGetElementIdToHaveBeenCalledWith('user-content-definição');
|
||||
});
|
||||
|
||||
describe('gl.utils.normalizedHeaders', () => {
|
||||
it('should upperCase all the header keys to keep them consistent', () => {
|
||||
const apiHeaders = {
|
||||
'X-Something-Workhorse': { workhorse: 'ok' },
|
||||
'x-something-nginx': { nginx: 'ok' },
|
||||
};
|
||||
it('decodes encoded cyrillic hash parameter', () => {
|
||||
window.history.pushState({}, null, '#defini%C3%A7%C3%A3o');
|
||||
commonUtils.handleLocationHash();
|
||||
|
||||
const normalized = gl.utils.normalizeHeaders(apiHeaders);
|
||||
expectGetElementIdToHaveBeenCalledWith('definição');
|
||||
expectGetElementIdToHaveBeenCalledWith('user-content-definição');
|
||||
});
|
||||
});
|
||||
|
||||
const WORKHORSE = 'X-SOMETHING-WORKHORSE';
|
||||
const NGINX = 'X-SOMETHING-NGINX';
|
||||
|
||||
expect(normalized[WORKHORSE].workhorse).toBe('ok');
|
||||
expect(normalized[NGINX].nginx).toBe('ok');
|
||||
});
|
||||
describe('gl.utils.setParamInURL', () => {
|
||||
afterEach(() => {
|
||||
window.history.pushState({}, null, '');
|
||||
});
|
||||
|
||||
describe('gl.utils.normalizeCRLFHeaders', () => {
|
||||
beforeEach(function () {
|
||||
this.CLRFHeaders = 'a-header: a-value\nAnother-Header: ANOTHER-VALUE\nLaSt-HeAdEr: last-VALUE';
|
||||
it('should return the parameter', () => {
|
||||
window.history.replaceState({}, null, '');
|
||||
|
||||
spyOn(String.prototype, 'split').and.callThrough();
|
||||
spyOn(gl.utils, 'normalizeHeaders').and.callThrough();
|
||||
|
||||
this.normalizeCRLFHeaders = gl.utils.normalizeCRLFHeaders(this.CLRFHeaders);
|
||||
});
|
||||
|
||||
it('should split by newline', function () {
|
||||
expect(String.prototype.split).toHaveBeenCalledWith('\n');
|
||||
});
|
||||
|
||||
it('should split by colon+space for each header', function () {
|
||||
expect(String.prototype.split.calls.allArgs().filter(args => args[0] === ': ').length).toBe(3);
|
||||
});
|
||||
|
||||
it('should call gl.utils.normalizeHeaders with a parsed headers object', function () {
|
||||
expect(gl.utils.normalizeHeaders).toHaveBeenCalledWith(jasmine.any(Object));
|
||||
});
|
||||
|
||||
it('should return a normalized headers object', function () {
|
||||
expect(this.normalizeCRLFHeaders).toEqual({
|
||||
'A-HEADER': 'a-value',
|
||||
'ANOTHER-HEADER': 'ANOTHER-VALUE',
|
||||
'LAST-HEADER': 'last-VALUE',
|
||||
});
|
||||
});
|
||||
expect(gl.utils.setParamInURL('page', 156)).toBe('?page=156');
|
||||
expect(gl.utils.setParamInURL('page', '156')).toBe('?page=156');
|
||||
});
|
||||
|
||||
describe('gl.utils.parseIntPagination', () => {
|
||||
it('should parse to integers all string values and return pagination object', () => {
|
||||
const pagination = {
|
||||
'X-PER-PAGE': 10,
|
||||
'X-PAGE': 2,
|
||||
'X-TOTAL': 30,
|
||||
'X-TOTAL-PAGES': 3,
|
||||
'X-NEXT-PAGE': 3,
|
||||
'X-PREV-PAGE': 1,
|
||||
};
|
||||
it('should update the existing parameter when its a number', () => {
|
||||
window.history.pushState({}, null, '?page=15');
|
||||
|
||||
const expectedPagination = {
|
||||
perPage: 10,
|
||||
page: 2,
|
||||
total: 30,
|
||||
totalPages: 3,
|
||||
nextPage: 3,
|
||||
previousPage: 1,
|
||||
};
|
||||
|
||||
expect(gl.utils.parseIntPagination(pagination)).toEqual(expectedPagination);
|
||||
});
|
||||
expect(gl.utils.setParamInURL('page', 16)).toBe('?page=16');
|
||||
expect(gl.utils.setParamInURL('page', '16')).toBe('?page=16');
|
||||
expect(gl.utils.setParamInURL('page', true)).toBe('?page=true');
|
||||
});
|
||||
|
||||
describe('gl.utils.isMetaClick', () => {
|
||||
it('should identify meta click on Windows/Linux', () => {
|
||||
const e = {
|
||||
metaKey: false,
|
||||
ctrlKey: true,
|
||||
which: 1,
|
||||
};
|
||||
it('should update the existing parameter when its a string', () => {
|
||||
window.history.pushState({}, null, '?scope=all');
|
||||
|
||||
expect(gl.utils.isMetaClick(e)).toBe(true);
|
||||
});
|
||||
|
||||
it('should identify meta click on macOS', () => {
|
||||
const e = {
|
||||
metaKey: true,
|
||||
ctrlKey: false,
|
||||
which: 1,
|
||||
};
|
||||
|
||||
expect(gl.utils.isMetaClick(e)).toBe(true);
|
||||
});
|
||||
|
||||
it('should identify as meta click on middle-click or Mouse-wheel click', () => {
|
||||
const e = {
|
||||
metaKey: false,
|
||||
ctrlKey: false,
|
||||
which: 2,
|
||||
};
|
||||
|
||||
expect(gl.utils.isMetaClick(e)).toBe(true);
|
||||
});
|
||||
expect(gl.utils.setParamInURL('scope', 'finished')).toBe('?scope=finished');
|
||||
});
|
||||
|
||||
describe('gl.utils.backOff', () => {
|
||||
beforeEach(() => {
|
||||
// shortcut our timeouts otherwise these tests will take a long time to finish
|
||||
const origSetTimeout = window.setTimeout;
|
||||
spyOn(window, 'setTimeout').and.callFake(cb => origSetTimeout(cb, 0));
|
||||
});
|
||||
it('should update the existing parameter when more than one parameter exists', () => {
|
||||
window.history.pushState({}, null, '?scope=all&page=15');
|
||||
|
||||
it('solves the promise from the callback', (done) => {
|
||||
const expectedResponseValue = 'Success!';
|
||||
gl.utils.backOff((next, stop) => (
|
||||
new Promise((resolve) => {
|
||||
resolve(expectedResponseValue);
|
||||
}).then((resp) => {
|
||||
stop(resp);
|
||||
})
|
||||
)).then((respBackoff) => {
|
||||
expect(respBackoff).toBe(expectedResponseValue);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('catches the rejected promise from the callback ', (done) => {
|
||||
const errorMessage = 'Mistakes were made!';
|
||||
gl.utils.backOff((next, stop) => {
|
||||
new Promise((resolve, reject) => {
|
||||
reject(new Error(errorMessage));
|
||||
}).then((resp) => {
|
||||
stop(resp);
|
||||
}).catch(err => stop(err));
|
||||
}).catch((errBackoffResp) => {
|
||||
expect(errBackoffResp instanceof Error).toBe(true);
|
||||
expect(errBackoffResp.message).toBe(errorMessage);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('solves the promise correctly after retrying a third time', (done) => {
|
||||
let numberOfCalls = 1;
|
||||
const expectedResponseValue = 'Success!';
|
||||
gl.utils.backOff((next, stop) => (
|
||||
Promise.resolve(expectedResponseValue)
|
||||
.then((resp) => {
|
||||
if (numberOfCalls < 3) {
|
||||
numberOfCalls += 1;
|
||||
next();
|
||||
} else {
|
||||
stop(resp);
|
||||
}
|
||||
})
|
||||
)).then((respBackoff) => {
|
||||
const timeouts = window.setTimeout.calls.allArgs().map(([, timeout]) => timeout);
|
||||
expect(timeouts).toEqual([2000, 4000]);
|
||||
expect(respBackoff).toBe(expectedResponseValue);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects the backOff promise after timing out', (done) => {
|
||||
gl.utils.backOff(next => next(), 64000)
|
||||
.catch((errBackoffResp) => {
|
||||
const timeouts = window.setTimeout.calls.allArgs().map(([, timeout]) => timeout);
|
||||
expect(timeouts).toEqual([2000, 4000, 8000, 16000, 32000, 32000]);
|
||||
expect(errBackoffResp instanceof Error).toBe(true);
|
||||
expect(errBackoffResp.message).toBe('BACKOFF_TIMEOUT');
|
||||
done();
|
||||
});
|
||||
});
|
||||
expect(gl.utils.setParamInURL('scope', 'finished')).toBe('?scope=finished&page=15');
|
||||
});
|
||||
|
||||
describe('gl.utils.setFavicon', () => {
|
||||
it('should set page favicon to provided favicon', () => {
|
||||
const faviconPath = '//custom_favicon';
|
||||
const fakeLink = {
|
||||
setAttribute() {},
|
||||
};
|
||||
it('should add a new parameter to the end of the existing ones', () => {
|
||||
window.history.pushState({}, null, '?scope=all');
|
||||
|
||||
spyOn(window.document, 'getElementById').and.callFake(() => fakeLink);
|
||||
spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => {
|
||||
expect(attr).toEqual('href');
|
||||
expect(val.indexOf(faviconPath) > -1).toBe(true);
|
||||
});
|
||||
gl.utils.setFavicon(faviconPath);
|
||||
});
|
||||
expect(gl.utils.setParamInURL('page', 16)).toBe('?scope=all&page=16');
|
||||
expect(gl.utils.setParamInURL('page', '16')).toBe('?scope=all&page=16');
|
||||
expect(gl.utils.setParamInURL('page', true)).toBe('?scope=all&page=true');
|
||||
});
|
||||
});
|
||||
|
||||
describe('gl.utils.getParameterByName', () => {
|
||||
beforeEach(() => {
|
||||
window.history.pushState({}, null, '?scope=all&p=2');
|
||||
});
|
||||
|
||||
describe('gl.utils.resetFavicon', () => {
|
||||
it('should reset page favicon to tanuki', () => {
|
||||
const fakeLink = {
|
||||
setAttribute() {},
|
||||
};
|
||||
|
||||
spyOn(window.document, 'getElementById').and.callFake(() => fakeLink);
|
||||
spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => {
|
||||
expect(attr).toEqual('href');
|
||||
expect(val).toMatch(/favicon/);
|
||||
});
|
||||
gl.utils.resetFavicon();
|
||||
});
|
||||
afterEach(() => {
|
||||
window.history.replaceState({}, null, null);
|
||||
});
|
||||
|
||||
describe('gl.utils.setCiStatusFavicon', () => {
|
||||
it('should set page favicon to CI status favicon based on provided status', () => {
|
||||
const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1/status.json`;
|
||||
const FAVICON_PATH = '//icon_status_success';
|
||||
const spySetFavicon = spyOn(gl.utils, 'setFavicon').and.stub();
|
||||
const spyResetFavicon = spyOn(gl.utils, 'resetFavicon').and.stub();
|
||||
spyOn($, 'ajax').and.callFake(function (options) {
|
||||
options.success({ favicon: FAVICON_PATH });
|
||||
expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH);
|
||||
options.success();
|
||||
expect(spyResetFavicon).toHaveBeenCalled();
|
||||
options.error();
|
||||
expect(spyResetFavicon).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
gl.utils.setCiStatusFavicon(BUILD_URL);
|
||||
});
|
||||
it('should return valid parameter', () => {
|
||||
const value = gl.utils.getParameterByName('scope');
|
||||
expect(gl.utils.getParameterByName('p')).toEqual('2');
|
||||
expect(value).toBe('all');
|
||||
});
|
||||
|
||||
describe('gl.utils.ajaxPost', () => {
|
||||
it('should perform `$.ajax` call and do `POST` request', () => {
|
||||
const requestURL = '/some/random/api';
|
||||
const data = { keyname: 'value' };
|
||||
const ajaxSpy = spyOn($, 'ajax').and.callFake(() => {});
|
||||
it('should return invalid parameter', () => {
|
||||
const value = gl.utils.getParameterByName('fakeParameter');
|
||||
expect(value).toBe(null);
|
||||
});
|
||||
|
||||
gl.utils.ajaxPost(requestURL, data);
|
||||
expect(ajaxSpy.calls.allArgs()[0][0].type).toEqual('POST');
|
||||
it('should return valid paramentes if URL is provided', () => {
|
||||
let value = gl.utils.getParameterByName('foo', 'http://cocteau.twins/?foo=bar');
|
||||
expect(value).toBe('bar');
|
||||
|
||||
value = gl.utils.getParameterByName('manan', 'http://cocteau.twins/?foo=bar&manan=canchu');
|
||||
expect(value).toBe('canchu');
|
||||
});
|
||||
});
|
||||
|
||||
describe('gl.utils.normalizedHeaders', () => {
|
||||
it('should upperCase all the header keys to keep them consistent', () => {
|
||||
const apiHeaders = {
|
||||
'X-Something-Workhorse': { workhorse: 'ok' },
|
||||
'x-something-nginx': { nginx: 'ok' },
|
||||
};
|
||||
|
||||
const normalized = gl.utils.normalizeHeaders(apiHeaders);
|
||||
|
||||
const WORKHORSE = 'X-SOMETHING-WORKHORSE';
|
||||
const NGINX = 'X-SOMETHING-NGINX';
|
||||
|
||||
expect(normalized[WORKHORSE].workhorse).toBe('ok');
|
||||
expect(normalized[NGINX].nginx).toBe('ok');
|
||||
});
|
||||
});
|
||||
|
||||
describe('gl.utils.normalizeCRLFHeaders', () => {
|
||||
beforeEach(function () {
|
||||
this.CLRFHeaders = 'a-header: a-value\nAnother-Header: ANOTHER-VALUE\nLaSt-HeAdEr: last-VALUE';
|
||||
|
||||
spyOn(String.prototype, 'split').and.callThrough();
|
||||
spyOn(gl.utils, 'normalizeHeaders').and.callThrough();
|
||||
|
||||
this.normalizeCRLFHeaders = gl.utils.normalizeCRLFHeaders(this.CLRFHeaders);
|
||||
});
|
||||
|
||||
it('should split by newline', function () {
|
||||
expect(String.prototype.split).toHaveBeenCalledWith('\n');
|
||||
});
|
||||
|
||||
it('should split by colon+space for each header', function () {
|
||||
expect(String.prototype.split.calls.allArgs().filter(args => args[0] === ': ').length).toBe(3);
|
||||
});
|
||||
|
||||
it('should call gl.utils.normalizeHeaders with a parsed headers object', function () {
|
||||
expect(gl.utils.normalizeHeaders).toHaveBeenCalledWith(jasmine.any(Object));
|
||||
});
|
||||
|
||||
it('should return a normalized headers object', function () {
|
||||
expect(this.normalizeCRLFHeaders).toEqual({
|
||||
'A-HEADER': 'a-value',
|
||||
'ANOTHER-HEADER': 'ANOTHER-VALUE',
|
||||
'LAST-HEADER': 'last-VALUE',
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
describe('gl.utils.parseIntPagination', () => {
|
||||
it('should parse to integers all string values and return pagination object', () => {
|
||||
const pagination = {
|
||||
'X-PER-PAGE': 10,
|
||||
'X-PAGE': 2,
|
||||
'X-TOTAL': 30,
|
||||
'X-TOTAL-PAGES': 3,
|
||||
'X-NEXT-PAGE': 3,
|
||||
'X-PREV-PAGE': 1,
|
||||
};
|
||||
|
||||
const expectedPagination = {
|
||||
perPage: 10,
|
||||
page: 2,
|
||||
total: 30,
|
||||
totalPages: 3,
|
||||
nextPage: 3,
|
||||
previousPage: 1,
|
||||
};
|
||||
|
||||
expect(gl.utils.parseIntPagination(pagination)).toEqual(expectedPagination);
|
||||
});
|
||||
});
|
||||
|
||||
describe('gl.utils.isMetaClick', () => {
|
||||
it('should identify meta click on Windows/Linux', () => {
|
||||
const e = {
|
||||
metaKey: false,
|
||||
ctrlKey: true,
|
||||
which: 1,
|
||||
};
|
||||
|
||||
expect(gl.utils.isMetaClick(e)).toBe(true);
|
||||
});
|
||||
|
||||
it('should identify meta click on macOS', () => {
|
||||
const e = {
|
||||
metaKey: true,
|
||||
ctrlKey: false,
|
||||
which: 1,
|
||||
};
|
||||
|
||||
expect(gl.utils.isMetaClick(e)).toBe(true);
|
||||
});
|
||||
|
||||
it('should identify as meta click on middle-click or Mouse-wheel click', () => {
|
||||
const e = {
|
||||
metaKey: false,
|
||||
ctrlKey: false,
|
||||
which: 2,
|
||||
};
|
||||
|
||||
expect(gl.utils.isMetaClick(e)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('backOff', () => {
|
||||
beforeEach(() => {
|
||||
// shortcut our timeouts otherwise these tests will take a long time to finish
|
||||
const origSetTimeout = window.setTimeout;
|
||||
spyOn(window, 'setTimeout').and.callFake(cb => origSetTimeout(cb, 0));
|
||||
});
|
||||
|
||||
it('solves the promise from the callback', (done) => {
|
||||
const expectedResponseValue = 'Success!';
|
||||
commonUtils.backOff((next, stop) => (
|
||||
new Promise((resolve) => {
|
||||
resolve(expectedResponseValue);
|
||||
}).then((resp) => {
|
||||
stop(resp);
|
||||
})
|
||||
)).then((respBackoff) => {
|
||||
expect(respBackoff).toBe(expectedResponseValue);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('catches the rejected promise from the callback ', (done) => {
|
||||
const errorMessage = 'Mistakes were made!';
|
||||
commonUtils.backOff((next, stop) => {
|
||||
new Promise((resolve, reject) => {
|
||||
reject(new Error(errorMessage));
|
||||
}).then((resp) => {
|
||||
stop(resp);
|
||||
}).catch(err => stop(err));
|
||||
}).catch((errBackoffResp) => {
|
||||
expect(errBackoffResp instanceof Error).toBe(true);
|
||||
expect(errBackoffResp.message).toBe(errorMessage);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('solves the promise correctly after retrying a third time', (done) => {
|
||||
let numberOfCalls = 1;
|
||||
const expectedResponseValue = 'Success!';
|
||||
commonUtils.backOff((next, stop) => (
|
||||
Promise.resolve(expectedResponseValue)
|
||||
.then((resp) => {
|
||||
if (numberOfCalls < 3) {
|
||||
numberOfCalls += 1;
|
||||
next();
|
||||
} else {
|
||||
stop(resp);
|
||||
}
|
||||
})
|
||||
)).then((respBackoff) => {
|
||||
const timeouts = window.setTimeout.calls.allArgs().map(([, timeout]) => timeout);
|
||||
expect(timeouts).toEqual([2000, 4000]);
|
||||
expect(respBackoff).toBe(expectedResponseValue);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects the backOff promise after timing out', (done) => {
|
||||
commonUtils.backOff(next => next(), 64000)
|
||||
.catch((errBackoffResp) => {
|
||||
const timeouts = window.setTimeout.calls.allArgs().map(([, timeout]) => timeout);
|
||||
expect(timeouts).toEqual([2000, 4000, 8000, 16000, 32000, 32000]);
|
||||
expect(errBackoffResp instanceof Error).toBe(true);
|
||||
expect(errBackoffResp.message).toBe('BACKOFF_TIMEOUT');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('gl.utils.setFavicon', () => {
|
||||
it('should set page favicon to provided favicon', () => {
|
||||
const faviconPath = '//custom_favicon';
|
||||
const fakeLink = {
|
||||
setAttribute() {},
|
||||
};
|
||||
|
||||
spyOn(window.document, 'getElementById').and.callFake(() => fakeLink);
|
||||
spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => {
|
||||
expect(attr).toEqual('href');
|
||||
expect(val.indexOf(faviconPath) > -1).toBe(true);
|
||||
});
|
||||
gl.utils.setFavicon(faviconPath);
|
||||
});
|
||||
});
|
||||
|
||||
describe('gl.utils.resetFavicon', () => {
|
||||
it('should reset page favicon to tanuki', () => {
|
||||
const fakeLink = {
|
||||
setAttribute() {},
|
||||
};
|
||||
|
||||
spyOn(window.document, 'getElementById').and.callFake(() => fakeLink);
|
||||
spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => {
|
||||
expect(attr).toEqual('href');
|
||||
expect(val).toMatch(/favicon/);
|
||||
});
|
||||
gl.utils.resetFavicon();
|
||||
});
|
||||
});
|
||||
|
||||
describe('gl.utils.setCiStatusFavicon', () => {
|
||||
it('should set page favicon to CI status favicon based on provided status', () => {
|
||||
const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1/status.json`;
|
||||
const FAVICON_PATH = '//icon_status_success';
|
||||
const spySetFavicon = spyOn(gl.utils, 'setFavicon').and.stub();
|
||||
const spyResetFavicon = spyOn(gl.utils, 'resetFavicon').and.stub();
|
||||
spyOn($, 'ajax').and.callFake(function (options) {
|
||||
options.success({ favicon: FAVICON_PATH });
|
||||
expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH);
|
||||
options.success();
|
||||
expect(spyResetFavicon).toHaveBeenCalled();
|
||||
options.error();
|
||||
expect(spyResetFavicon).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
gl.utils.setCiStatusFavicon(BUILD_URL);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ajaxPost', () => {
|
||||
it('should perform `$.ajax` call and do `POST` request', () => {
|
||||
const requestURL = '/some/random/api';
|
||||
const data = { keyname: 'value' };
|
||||
const ajaxSpy = spyOn($, 'ajax').and.callFake(() => {});
|
||||
|
||||
commonUtils.ajaxPost(requestURL, data);
|
||||
expect(ajaxSpy.calls.allArgs()[0][0].type).toEqual('POST');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue