Commit 051daa03 authored by Phil Hughes's avatar Phil Hughes

Merge branch 'ee-37220-es-modules' into 'master'

Port of 37220-es-modules to EE

See merge request gitlab-org/gitlab-ee!2914
parents 7c627ac8 2328e10a
......@@ -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);
}
......
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();
}
}
......
/* global Flash */
import { handleLocationHash } from '../../lib/utils/common_utils';
export default class BlobViewer {
constructor() {
BlobViewer.initAuxiliaryViewer();
......@@ -114,7 +116,7 @@ export default class BlobViewer {
$(viewer).renderGFM();
this.$fileHolder.trigger('highlight:line');
gl.utils.handleLocationHash();
handleLocationHash();
this.toggleCopyButtonState();
})
......
......@@ -24,6 +24,7 @@ import './components/board_sidebar';
import './components/new_list_dropdown';
import './components/modal/index';
import '../vue_shared/vue_resource_interceptor';
import { convertPermissionToBoolean } from '../lib/utils/common_utils';
import './components/boards_selector';
import collapseIcon from './icons/fullscreen_collapse.svg';
......@@ -153,10 +154,10 @@ $(() => {
modal: ModalStore.store,
store: Store.state,
isFullscreen: false,
focusModeAvailable: gl.utils.convertPermissionToBoolean(
focusModeAvailable: convertPermissionToBoolean(
$boardApp.dataset.focusModeAvailable,
),
canAdminList: this.$options.el && gl.utils.convertPermissionToBoolean(
canAdminList: this.$options.el && convertPermissionToBoolean(
this.$options.el.dataset.canAdminList,
),
};
......@@ -226,7 +227,7 @@ $(() => {
modal: ModalStore.store,
store: Store.state,
isFullscreen: false,
focusModeAvailable: gl.utils.convertPermissionToBoolean($boardApp.dataset.focusModeAvailable),
focusModeAvailable: convertPermissionToBoolean($boardApp.dataset.focusModeAvailable),
},
methods: {
toggleFocusMode() {
......
......@@ -3,6 +3,7 @@
import _ from 'underscore';
import Cookies from 'js-cookie';
import boardsStoreEE from 'ee/boards/stores/boards_store_ee';
import { getUrlParamsArray } from '../../lib/utils/common_utils';
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
......@@ -22,7 +23,7 @@ gl.issueBoards.BoardsStore = {
},
create () {
this.state.lists = [];
this.filter.path = gl.utils.getUrlParamsArray().join('&');
this.filter.path = getUrlParamsArray().join('&');
this.detail = { issue: {} };
},
createNewListDropdownData() {
......
......@@ -3,6 +3,7 @@ consistent-return, prefer-rest-params */
import _ from 'underscore';
import bp from './breakpoints';
import { bytesToKiB } from './lib/utils/number_utils';
import { setCiStatusFavicon } from './lib/utils/common_utils';
window.Build = (function () {
Build.timeout = null;
......@@ -169,7 +170,7 @@ window.Build = (function () {
data: this.state,
})
.done((log) => {
gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`);
setCiStatusFavicon(`${this.pageUrl}/status.json`);
if (log.state) {
this.state = log.state;
......
/* 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, arg) {
......@@ -16,7 +17,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();
......
/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
import _ from 'underscore';
import './lib/utils/common_utils';
import { insertText, getSelectedFragment, nodeMatchesSelector } from './lib/utils/common_utils';
import { placeholderImage } from './lazy_loader';
const gfmRules = {
......@@ -295,7 +295,7 @@ class CopyAsGFM {
const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return;
const documentFragment = window.gl.utils.getSelectedFragment();
const documentFragment = getSelectedFragment();
if (!documentFragment) return;
const el = transformer(documentFragment.cloneNode(true));
......@@ -412,7 +412,7 @@ class CopyAsGFM {
for (const selector in rules) {
const func = rules[selector];
if (!window.gl.utils.nodeMatchesSelector(node, selector)) continue;
if (!nodeMatchesSelector(node, selector)) continue;
let result;
if (func.length === 2) {
......
......@@ -83,6 +83,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, convertPermissionToBoolean } from './lib/utils/common_utils';
// EE-only
import ApproversSelect from './approvers_select';
......@@ -112,7 +113,7 @@ import initGroupAnalytics from './init_group_analytics';
$('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => {
const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
const enableGFM = gl.utils.convertPermissionToBoolean(el.dataset.supportsAutocomplete);
const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete);
gfm.setup($(el), {
emojis: true,
members: enableGFM,
......@@ -385,7 +386,7 @@ import initGroupAnalytics from './init_group_analytics';
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);
});
initGeoInfoModal();
......@@ -475,7 +476,7 @@ import initGroupAnalytics from './init_group_analytics';
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':
......
......@@ -6,7 +6,7 @@ import environmentTable from './environments_table.vue';
import EnvironmentsStore from '../stores/environments_store';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import '../../lib/utils/common_utils';
import { convertPermissionToBoolean, getParameterByName, setParamInURL } from '../../lib/utils/common_utils';
import eventHub from '../event_hub';
import Poll from '../../lib/utils/poll';
import environmentsMixin from '../mixins/environments_mixin';
......@@ -52,19 +52,19 @@ export default {
computed: {
scope() {
return gl.utils.getParameterByName('scope');
return getParameterByName('scope');
},
canReadEnvironmentParsed() {
return gl.utils.convertPermissionToBoolean(this.canReadEnvironment);
return convertPermissionToBoolean(this.canReadEnvironment);
},
canCreateDeploymentParsed() {
return gl.utils.convertPermissionToBoolean(this.canCreateDeployment);
return convertPermissionToBoolean(this.canCreateDeployment);
},
canCreateEnvironmentParsed() {
return gl.utils.convertPermissionToBoolean(this.canCreateEnvironment);
return convertPermissionToBoolean(this.canCreateEnvironment);
},
/**
......@@ -83,8 +83,8 @@ export default {
* Toggles loading property.
*/
created() {
const scope = gl.utils.getParameterByName('scope') || this.visibility;
const page = gl.utils.getParameterByName('page') || this.pageNumber;
const scope = getParameterByName('scope') || this.visibility;
const page = getParameterByName('page') || this.pageNumber;
this.service = new EnvironmentsService(this.endpoint);
......@@ -154,15 +154,15 @@ export default {
* @return {String}
*/
changePage(pageNumber) {
const param = gl.utils.setParamInURL('page', pageNumber);
const param = setParamInURL('page', pageNumber);
gl.utils.visitUrl(param);
return param;
},
fetchEnvironments() {
const scope = gl.utils.getParameterByName('scope') || this.visibility;
const page = gl.utils.getParameterByName('page') || this.pageNumber;
const scope = getParameterByName('scope') || this.visibility;
const page = getParameterByName('page') || this.pageNumber;
this.isLoading = true;
......
......@@ -9,7 +9,7 @@ import tablePagination from '../../vue_shared/components/table_pagination.vue';
import Poll from '../../lib/utils/poll';
import eventHub from '../event_hub';
import environmentsMixin from '../mixins/environments_mixin';
import '../../lib/utils/common_utils';
import { convertPermissionToBoolean, getParameterByName, setParamInURL } from '../../lib/utils/common_utils';
export default {
components: {
......@@ -48,15 +48,15 @@ export default {
computed: {
scope() {
return gl.utils.getParameterByName('scope');
return getParameterByName('scope');
},
canReadEnvironmentParsed() {
return gl.utils.convertPermissionToBoolean(this.canReadEnvironment);
return convertPermissionToBoolean(this.canReadEnvironment);
},
canCreateDeploymentParsed() {
return gl.utils.convertPermissionToBoolean(this.canCreateDeployment);
return convertPermissionToBoolean(this.canCreateDeployment);
},
/**
......@@ -83,8 +83,8 @@ export default {
* Toggles loading property.
*/
created() {
const scope = gl.utils.getParameterByName('scope') || this.visibility;
const page = gl.utils.getParameterByName('page') || this.pageNumber;
const scope = getParameterByName('scope') || this.visibility;
const page = getParameterByName('page') || this.pageNumber;
this.service = new EnvironmentsService(this.endpoint);
......@@ -137,15 +137,15 @@ export default {
* @param {Number} pageNumber desired page to go to.
*/
changePage(pageNumber) {
const param = gl.utils.setParamInURL('page', pageNumber);
const param = setParamInURL('page', pageNumber);
gl.utils.visitUrl(param);
return param;
},
fetchEnvironments() {
const scope = gl.utils.getParameterByName('scope') || this.visibility;
const page = gl.utils.getParameterByName('page') || this.pageNumber;
const scope = getParameterByName('scope') || this.visibility;
const page = getParameterByName('page') || this.pageNumber;
this.isLoading = true;
......
import '~/lib/utils/common_utils';
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
/**
* Environments Store.
*
......@@ -97,8 +97,8 @@ export default class EnvironmentsStore {
* @return {Object}
*/
setPagination(pagination = {}) {
const normalizedHeaders = gl.utils.normalizeHeaders(pagination);
const paginationInformation = gl.utils.parseIntPagination(normalizedHeaders);
const normalizedHeaders = normalizeHeaders(pagination);
const paginationInformation = parseIntPagination(normalizedHeaders);
this.state.paginationInformation = paginationInformation;
return paginationInformation;
......
<script>
import tablePagination from '~/vue_shared/components/table_pagination.vue';
import eventHub from '../event_hub';
import { getParameterByName } from '../../lib/utils/common_utils';
export default {
props: {
......@@ -18,8 +19,8 @@ export default {
},
methods: {
change(page) {
const filterGroupsParam = gl.utils.getParameterByName('filter_groups');
const sortParam = gl.utils.getParameterByName('sort');
const filterGroupsParam = getParameterByName('filter_groups');
const sortParam = getParameterByName('sort');
eventHub.$emit('fetchPage', page, filterGroupsParam, sortParam);
},
},
......
import FilterableList from '~/filterable_list';
import eventHub from './event_hub';
import { getParameterByName } from '../lib/utils/common_utils';
export default class GroupFilterableList extends FilterableList {
constructor({ form, filter, holder, filterEndpoint, pagePath }) {
......@@ -54,7 +55,7 @@ export default class GroupFilterableList extends FilterableList {
e.preventDefault();
const queryData = {};
const sortParam = gl.utils.getParameterByName('sort', e.currentTarget.href);
const sortParam = getParameterByName('sort', e.currentTarget.href);
if (sortParam) {
queryData.sort = sortParam;
......
......@@ -8,6 +8,7 @@ import GroupItem from './components/group_item.vue';
import GroupsStore from './stores/groups_store';
import GroupsService from './services/groups_service';
import eventHub from './event_hub';
import { getParameterByName } from '../lib/utils/common_utils';
document.addEventListener('DOMContentLoaded', () => {
const el = document.getElementById('dashboard-group-app');
......@@ -58,17 +59,17 @@ document.addEventListener('DOMContentLoaded', () => {
this.isLoading = true;
}
pageParam = gl.utils.getParameterByName('page');
pageParam = getParameterByName('page');
if (pageParam) {
page = pageParam;
}
filterGroupsParam = gl.utils.getParameterByName('filter_groups');
filterGroupsParam = getParameterByName('filter_groups');
if (filterGroupsParam) {
filterGroups = filterGroupsParam;
}
sortParam = gl.utils.getParameterByName('sort');
sortParam = getParameterByName('sort');
if (sortParam) {
sort = sortParam;
}
......
import Vue from 'vue';
import { parseIntPagination, normalizeHeaders } from '../../lib/utils/common_utils';
export default class GroupsStore {
constructor() {
......@@ -30,8 +31,8 @@ export default class GroupsStore {
let paginationInfo;
if (Object.keys(pagination).length) {
const normalizedHeaders = gl.utils.normalizeHeaders(pagination);
paginationInfo = gl.utils.parseIntPagination(normalizedHeaders);
const normalizedHeaders = normalizeHeaders(pagination);
paginationInfo = parseIntPagination(normalizedHeaders);
} else {
paginationInfo = pagination;
}
......
......@@ -4,6 +4,7 @@
prefer-rest-params, prefer-spread, no-unused-vars, prefer-template,
promise/catch-or-return */
import Api from './api';
import { normalizeCRLFHeaders } from './lib/utils/common_utils';
var slice = [].slice;
......@@ -30,7 +31,7 @@ window.GroupsSelect = (function() {
$.ajax(params).then((data, status, xhr) => {
const results = data || [];
const headers = gl.utils.normalizeCRLFHeaders(xhr.getAllResponseHeaders());
const headers = normalizeCRLFHeaders(xhr.getAllResponseHeaders());
const currentPage = parseInt(headers['X-PAGE'], 10) || 0;
const totalPages = parseInt(headers['X-TOTAL-PAGES'], 10) || 0;
const more = currentPage < totalPages;
......
import Vue from 'vue';
import RelatedIssuesRoot from './related_issues/components/related_issues_root.vue';
import { convertPermissionToBoolean } from '../lib/utils/common_utils';
document.addEventListener('DOMContentLoaded', () => {
const relatedIssuesRootElement = document.querySelector('.js-related-issues-root');
......@@ -13,7 +14,7 @@ document.addEventListener('DOMContentLoaded', () => {
render: createElement => createElement('related-issues-root', {
props: {
endpoint: relatedIssuesRootElement.dataset.endpoint,
canAddRelatedIssues: gl.utils.convertPermissionToBoolean(
canAddRelatedIssues: convertPermissionToBoolean(
relatedIssuesRootElement.dataset.canAddRelatedIssues,
),
helpPath: relatedIssuesRootElement.dataset.helpPath,
......
/* 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()) {
export const getPagePath = (index = 0) => $('body').data('page').split(':')[index];
export const isInGroupsPage = () => getPagePath() === 'groups';
export const isInProjectPage = () => getPagePath() === 'projects';
export const getProjectSlug = () => {
if (isInProjectPage()) {
return $('body').data('project');
} else {
return null;
}
};
w.gl.utils.getGroupSlug = function() {
if (this.isInGroupsPage()) {
return $('body').data('group');
} else {
return null;
};
export const getGroupSlug = () => {
if (isInGroupsPage()) {
return $('body').data('group');
}
};
return null;
};
w.gl.utils.isInIssuePage = () => {
const page = gl.utils.getPagePath(1);
const action = gl.utils.getPagePath(2);
export const isInIssuePage = () => {
const page = getPagePath(1);
const action = getPagePath(2);
return page === 'issues' && action === 'show';
};
};
w.gl.utils.ajaxGet = function(url) {
return $.ajax({
type: "GET",
url: url,
dataType: "script"
});
};
export const ajaxGet = url => $.ajax({
type: 'GET',
url,
dataType: 'script',
});
w.gl.utils.ajaxPost = function(url, data) {
return $.ajax({
export const ajaxPost = (url, data) => $.ajax({
type: 'POST',
url: url,
data: data,
});
};
w.gl.utils.extractLast = function(term) {
return this.split(term).pop();
};
url,
data,
});
w.gl.utils.rstrip = function rstrip(val) {
export const rstrip = (val) => {
if (val) {
return val.replace(/\s+$/, '');
} else {
return val;
}
};
return val;
};
gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) {
return $tooltipEl.attr('title', newTitle).tooltip('fixTitle');
};
export const updateTooltipTitle = ($tooltipEl, newTitle) => $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();
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(event_name, function() {
if (that.rstrip($(this).val()) === "") {
return closest_submit.disable();
} else {
return closest_submit.enable();
// eslint-disable-next-line func-names
return field.on(eventName, function () {
if (rstrip($(this).val()) === '') {
return closestSubmit.disable();
}
return closestSubmit.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();
// 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
......@@ -97,12 +75,12 @@
const fixedDiffStats = document.querySelector('.js-diff-files-changed.is-stuck');
const fixedNav = document.querySelector('.navbar-gitlab');
var adjustment = 0;
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) {
var target = document.getElementById('user-content-' + hash);
const target = document.getElementById(`user-content-${hash}`);
if (target && target.scrollIntoView) {
target.scrollIntoView(true);
window.scrollBy(0, adjustment);
......@@ -119,12 +97,12 @@
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();
// 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 &&
......@@ -132,48 +110,37 @@
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');
export const parseUrl = (url) => {
const parser = document.createElement('a');
parser.href = url;
return parser;
};
};
gl.utils.parseUrlPathname = function (url) {
var parsedUrl = gl.utils.parseUrl(url);
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;
};
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) => {
// We can trust that each param has one & since values containing & will be encoded
// Remove the first character of search as it is always ?
export const getUrlParamsArray = () => 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;
};
export const isMetaKey = e => 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;
};
// 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;
gl.utils.scrollToElement = function($el) {
export const scrollToElement = ($el) => {
const top = $el.offset().top;
const mrTabsHeight = $('.merge-request-tabs').height() || 0;
const headerHeight = $('.navbar-gitlab').height() || 0;
......@@ -181,24 +148,24 @@
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}(=([^&#]*)|&|#|$)`);
*/
export const getParameterByName = (name, urlToParse) => {
const url = urlToParse || window.location.href;
const parsedName = name.replace(/[[\]]/g, '\\$&');
const regex = new RegExp(`[?&]${parsedName}(=([^&#]*)|&|#|$)`);
const results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, ' '));
};
};
w.gl.utils.getSelectedFragment = () => {
export const getSelectedFragment = () => {
const selection = window.getSelection();
if (selection.rangeCount === 0) return null;
const documentFragment = document.createDocumentFragment();
......@@ -208,11 +175,11 @@
if (documentFragment.textContent.length === 0) return null;
return documentFragment;
};
};
w.gl.utils.insertText = (target, text) => {
// TODO: Update this name, there is a gl.text.insertText function.
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;
......@@ -223,19 +190,21 @@
const insertedText = text instanceof Function ? text(textBefore, textAfter) : text;
const newText = textBefore + insertedText + textAfter;
// eslint-disable-next-line no-param-reassign
target.value = newText;
// eslint-disable-next-line no-param-reassign
target.selectionStart = target.selectionEnd = selectionStart + insertedText.length;
// Trigger autosave
$(target).trigger('input');
// Trigger autosize
var event = document.createEvent('Event');
const event = document.createEvent('Event');
event.initEvent('autosize:update', true, false);
target.dispatchEvent(event);
};
};
w.gl.utils.nodeMatchesSelector = (node, selector) => {
export const nodeMatchesSelector = (node, selector) => {
const matches = Element.prototype.matches ||
Element.prototype.matchesSelector ||
Element.prototype.mozMatchesSelector ||
......@@ -252,19 +221,20 @@
let parentNode = node.parentNode;
if (!parentNode) {
parentNode = document.createElement('div');
// eslint-disable-next-line no-param-reassign
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) => {
*/
export const normalizeHeaders = (headers) => {
const upperCaseHeaders = {};
Object.keys(headers).forEach((e) => {
......@@ -272,13 +242,13 @@
});
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) => {
*/
export const normalizeCRLFHeaders = (headers) => {
const headersObject = {};
const headersArray = headers.split('\n');
......@@ -287,25 +257,25 @@
headersObject[keyValue[0]] = keyValue[1];
});
return w.gl.utils.normalizeHeaders(headersObject);
};
return normalizeHeaders(headersObject);
};
/**
/**
* Parses pagination object string values into numbers.
*
* @param {Object} paginationInformation
* @returns {Object}
*/
w.gl.utils.parseIntPagination = paginationInformation => ({
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),
});
});
/**
/**
* Updates the search parameter of a URL given the parameter and value provided.
*
* If no search params are present we'll add it.
......@@ -317,7 +287,7 @@
* @param {Number|String|Undefined|Null} value
* @return {String}
*/
w.gl.utils.setParamInURL = (param, value) => {
export const setParamInURL = (param, value) => {
let search;
const locationSearch = window.location.search;
......@@ -326,6 +296,7 @@
.split('&')
.reduce((acc, element) => {
const val = element.split('=');
// eslint-disable-next-line no-param-reassign
acc[val[0]] = decodeURIComponent(val[1]);
return acc;
}, {});
......@@ -342,17 +313,17 @@
}
return search;
};
};
/**
/**
* Converts permission provided as strings to booleans.
*
* @param {String} string
* @returns {Boolean}
*/
w.gl.utils.convertPermissionToBoolean = permission => permission === 'true';
export const convertPermissionToBoolean = permission => permission === 'true';
/**
/**
* Back Off exponential algorithm
* backOff :: (Function<next, stop>, Number) -> Promise<Any, Error>
*
......@@ -383,7 +354,7 @@
* })
* ```
*/
w.gl.utils.backOff = (fn, timeout = 60000) => {
export const backOff = (fn, timeout = 60000) => {
const maxInterval = 32000;
let nextInterval = 2000;
let timeElapsed = 0;
......@@ -403,35 +374,64 @@
fn(next, stop);
});
};
};
w.gl.utils.setFavicon = (faviconPath) => {
export const setFavicon = (faviconPath) => {
const faviconEl = document.getElementById('favicon');
if (faviconEl && faviconPath) {
faviconEl.setAttribute('href', faviconPath);
}
};
};
w.gl.utils.resetFavicon = () => {
export const resetFavicon = () => {
const faviconEl = document.getElementById('favicon');
const originalFavicon = faviconEl ? faviconEl.getAttribute('href') : null;
if (faviconEl) {
faviconEl.setAttribute('href', originalFavicon);
}
};
};
w.gl.utils.setCiStatusFavicon = (pageUrl) => {
export const setCiStatusFavicon = (pageUrl) => {
$.ajax({
url: pageUrl,
dataType: 'json',
success: function(data) {
success: (data) => {
if (data && data.favicon) {
gl.utils.setFavicon(data.favicon);
setFavicon(data.favicon);
} else {
gl.utils.resetFavicon();
resetFavicon();
}
},
error: function() {
gl.utils.resetFavicon();
}
error: () => {
resetFavicon();
},
});
};
})(window);
}).call(window);
};
window.gl = window.gl || {};
window.gl.utils = {
...(window.gl.utils || {}),
getPagePath,
isInGroupsPage,
isInProjectPage,
getProjectSlug,
getGroupSlug,
isInIssuePage,
ajaxGet,
ajaxPost,
rstrip,
updateTooltipTitle,
disableButtonIfEmptyField,
handleLocationHash,
isInViewport,
parseUrl,
parseUrlPathname,
getUrlParamsArray,
isMetaKey,
isMetaClick,
scrollToElement,
getParameterByName,
getSelectedFragment,
insertText,
nodeMatchesSelector,
};
import httpStatusCodes from './http_status';
import { normalizeHeaders } from './common_utils';
/**
* Polling utility for handling realtime updates.
......@@ -57,7 +58,7 @@ export default class Poll {
}
checkConditions(response) {
const headers = gl.utils.normalizeHeaders(response.headers);
const headers = normalizeHeaders(response.headers);
const pollInterval = parseInt(headers[this.intervalHeader], 10);
if (pollInterval > 0 && response.status === httpStatusCodes.OK && this.canPoll) {
......
......@@ -40,7 +40,7 @@ import './commit/image_file';
// lib/utils
import './lib/utils/bootstrap_linked_tabs';
import './lib/utils/common_utils';
import { handleLocationHash } from './lib/utils/common_utils';
import './lib/utils/datetime_utility';
import './lib/utils/pretty_time';
import './lib/utils/text_utility';
......@@ -169,10 +169,10 @@ document.addEventListener('beforeunload', function () {
$('[data-toggle="popover"]').popover('destroy');
});
window.addEventListener('hashchange', gl.utils.handleLocationHash);
window.addEventListener('hashchange', handleLocationHash);
window.addEventListener('load', function onLoad() {
window.removeEventListener('load', onLoad, false);
gl.utils.handleLocationHash();
handleLocationHash();
}, false);
gl.lazyLoader = new LazyLoader({
......@@ -198,7 +198,7 @@ $(function () {
$body.on('click', 'a[href^="#"]', function() {
var href = this.getAttribute('href');
if (href.substr(1) === gl.utils.getLocationHash()) {
setTimeout(gl.utils.handleLocationHash, 1);
setTimeout(handleLocationHash, 1);
}
});
......
......@@ -7,6 +7,11 @@ import './flash';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import initChangesDropdown from './init_changes_dropdown';
import bp from './breakpoints';
import {
parseUrlPathname,
handleLocationHash,
isMetaClick,
} from './lib/utils/common_utils';
/* eslint-disable max-len */
// MergeRequestTabs
......@@ -114,7 +119,7 @@ import bp from './breakpoints';
}
clickTab(e) {
if (e.currentTarget && gl.utils.isMetaClick(e)) {
if (e.currentTarget && isMetaClick(e)) {
const targetLink = e.currentTarget.getAttribute('href');
e.stopImmediatePropagation();
e.preventDefault();
......@@ -260,7 +265,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}`,
......@@ -309,7 +314,7 @@ import bp from './breakpoints';
forceShow: true,
});
anchor[0].scrollIntoView();
window.gl.utils.handleLocationHash();
handleLocationHash();
// We have multiple elements on the page with `#note_xxx`
// (discussion and diff tabs) and `:target` only applies to the first
anchor.addClass('target');
......
/* global Flash */
import AUTH_METHOD from './constants';
import { backOff } from '../lib/utils/common_utils';
export default class MirrorPull {
constructor(formSelector) {
......@@ -65,7 +66,7 @@ export default class MirrorPull {
$btnLoadSpinner.removeClass('hidden');
// Make backOff polling to get data
gl.utils.backOff((next, stop) => {
backOff((next, stop) => {
$.getJSON(`${projectMirrorSSHEndpoint}?ssh_url=${repositoryUrl}`)
.done((res, statusText, header) => {
if (header.status === 204) {
......
......@@ -7,6 +7,7 @@
import EmptyState from './empty_state.vue';
import MonitoringStore from '../stores/monitoring_store';
import eventHub from '../event_hub';
import { convertPermissionToBoolean } from '../../lib/utils/common_utils';
export default {
......@@ -17,7 +18,7 @@
return {
store,
state: 'gettingStarted',
hasMetrics: gl.utils.convertPermissionToBoolean(metricsData.hasMetrics),
hasMetrics: convertPermissionToBoolean(metricsData.hasMetrics),
documentationPath: metricsData.documentationPath,
settingsPath: metricsData.settingsPath,
metricsEndpoint: metricsData.additionalMetrics,
......
import Vue from 'vue';
import VueResource from 'vue-resource';
import statusCodes from '../../lib/utils/http_status';
import { backOff } from '../../lib/utils/common_utils';
Vue.use(VueResource);
......@@ -8,7 +9,7 @@ const MAX_REQUESTS = 3;
function backOffRequest(makeRequestCallback) {
let requestCounter = 0;
return gl.utils.backOff((next, stop) => {
return backOff((next, stop) => {
makeRequestCallback().then((resp) => {
if (resp.status === statusCodes.NO_CONTENT) {
requestCounter += 1;
......
......@@ -23,6 +23,7 @@ import loadAwardsHandler from './awards_handler';
import './autosave';
import './dropzone_input';
import TaskList from './task_list';
import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } 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');
}
......@@ -175,7 +176,7 @@ export default class Notes {
keydownNoteText(e) {
var $textarea, discussionNoteForm, editNote, myLastNote, myLastNoteEditBtn, newText, originalText;
if (gl.utils.isMetaKey(e)) {
if (isMetaKey(e)) {
return;
}
......@@ -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();
......@@ -1481,7 +1482,7 @@ export default class Notes {
*
* 1) Get Form metadata
* 2) Update note element with new content
* 3) Perform network request to submit the updated note using `gl.utils.ajaxPost`
* 3) Perform network request to submit the updated note using `ajaxPost`
* a) If request is successfully completed
* 1. Show submitted Note element
* b) If request failed
......@@ -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);
}
};
import '~/lib/utils/common_utils';
import { getParameterByName } from '~/lib/utils/common_utils';
import '~/lib/utils/url_utility';
(() => {
......@@ -9,7 +9,7 @@ import '~/lib/utils/url_utility';
init(limit = 0, preload = false, disable = false, prepareData = $.noop, callback = $.noop) {
this.url = $('.content_list').data('href') || gl.utils.removeParams(['limit', 'offset']);
this.limit = limit;
this.offset = parseInt(gl.utils.getParameterByName('offset'), 10) || this.limit;
this.offset = parseInt(getParameterByName('offset'), 10) || this.limit;
this.disable = disable;
this.prepareData = prepareData;
this.callback = callback;
......
import { convertPermissionToBoolean } from '../lib/utils/common_utils';
function insertRow($row) {
const $rowClone = $row.clone();
$rowClone.removeAttr('data-is-persisted');
......@@ -6,7 +8,7 @@ function insertRow($row) {
}
function removeRow($row) {
const isPersisted = gl.utils.convertPermissionToBoolean($row.attr('data-is-persisted'));
const isPersisted = convertPermissionToBoolean($row.attr('data-is-persisted'));
if (isPersisted) {
$row.hide();
......
import LinkedTabs from './lib/utils/bootstrap_linked_tabs';
import { setCiStatusFavicon } from './lib/utils/common_utils';
export default class Pipelines {
constructor(options = {}) {
......@@ -8,7 +9,7 @@ export default class Pipelines {
}
if (options.pipelineStatusUrl) {
gl.utils.setCiStatusFavicon(options.pipelineStatusUrl);
setCiStatusFavicon(options.pipelineStatusUrl);
}
}
}
......@@ -4,6 +4,7 @@
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import navigationTabs from './navigation_tabs.vue';
import navigationControls from './nav_controls.vue';
import { convertPermissionToBoolean, getParameterByName, setParamInURL } from '../../lib/utils/common_utils';
export default {
props: {
......@@ -44,10 +45,10 @@
},
computed: {
canCreatePipelineParsed() {
return gl.utils.convertPermissionToBoolean(this.canCreatePipeline);
return convertPermissionToBoolean(this.canCreatePipeline);
},
scope() {
const scope = gl.utils.getParameterByName('scope');
const scope = getParameterByName('scope');
return scope === null ? 'all' : scope;
},
......@@ -105,10 +106,10 @@
};
},
pageParameter() {
return gl.utils.getParameterByName('page') || this.pagenum;
return getParameterByName('page') || this.pagenum;
},
scopeParameter() {
return gl.utils.getParameterByName('scope') || this.apiScope;
return getParameterByName('scope') || this.apiScope;
},
},
created() {
......@@ -122,7 +123,7 @@
* @param {Number} pageNumber desired page to go to.
*/
change(pageNumber) {
const param = gl.utils.setParamInURL('page', pageNumber);
const param = setParamInURL('page', pageNumber);
gl.utils.visitUrl(param);
return param;
......
import { parseIntPagination, normalizeHeaders } from '../../lib/utils/common_utils';
export default class PipelinesStore {
constructor() {
this.state = {};
......@@ -19,8 +21,8 @@ export default class PipelinesStore {
let paginationInfo;
if (Object.keys(pagination).length) {
const normalizedHeaders = gl.utils.normalizeHeaders(pagination);
paginationInfo = gl.utils.parseIntPagination(normalizedHeaders);
const normalizedHeaders = normalizeHeaders(pagination);
paginationInfo = parseIntPagination(normalizedHeaders);
} else {
paginationInfo = pagination;
}
......
/* 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();
}
});
......
import Vue from 'vue';
import serviceDeskRoot from './components/service_desk_root.vue';
import { convertPermissionToBoolean } from '../../lib/utils/common_utils';
document.addEventListener('DOMContentLoaded', () => {
const serviceDeskRootElement = document.querySelector('.js-service-desk-setting-root');
......@@ -10,7 +11,7 @@ document.addEventListener('DOMContentLoaded', () => {
data() {
const dataset = serviceDeskRootElement.dataset;
return {
initialIsEnabled: gl.utils.convertPermissionToBoolean(
initialIsEnabled: convertPermissionToBoolean(
dataset.enabled,
),
endpoint: dataset.endpoint,
......
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) {
......
/* 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;
}
......
/* eslint-disable class-methods-use-this, no-unneeded-ternary, quote-props */
import UsersSelect from './users_select';
import { isMetaClick } from './lib/utils/common_utils';
export default class Todos {
constructor() {
......@@ -137,22 +138,17 @@ export default class Todos {
goToTodoUrl(e) {
const todoLink = this.dataset.url;
if (!todoLink) {
if (!todoLink || e.target.tagName === 'A' || e.target.tagName === 'IMG') {
return;
}
if (gl.utils.isMetaClick(e)) {
const windowTarget = '_blank';
const selected = e.target;
e.stopPropagation();
e.preventDefault();
if (selected.tagName === 'IMG') {
const avatarUrl = selected.parentElement.getAttribute('href');
window.open(avatarUrl, windowTarget);
} else {
if (isMetaClick(e)) {
const windowTarget = '_blank';
window.open(todoLink, windowTarget);
}
} else {
gl.utils.visitUrl(todoLink);
}
......
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) {
......
......@@ -31,6 +31,7 @@ import {
SquashBeforeMerge,
notify,
} from './dependencies';
import { setFavicon } from '../lib/utils/common_utils';
export default {
el: '#js-vue-mr-widget',
......@@ -88,7 +89,7 @@ export default {
.then((res) => {
this.handleNotification(res);
this.mr.setData(res);
this.setFavicon();
this.setFaviconHelper();
if (cb) {
cb.call(null, res);
......@@ -115,9 +116,9 @@ export default {
immediateExecution: true,
});
},
setFavicon() {
setFaviconHelper() {
if (this.mr.ciStatusFaviconPath) {
gl.utils.setFavicon(this.mr.ciStatusFaviconPath);
setFavicon(this.mr.ciStatusFaviconPath);
}
},
fetchDeployments() {
......@@ -191,7 +192,7 @@ export default {
});
},
handleMounted() {
this.setFavicon();
this.setFaviconHelper();
this.initDeploymentsPolling();
},
},
......
---
title: Exports common_utils utility functions as modules
merge_request:
author:
type: other
......@@ -14,6 +14,10 @@ describe('Environments Folder View', () => {
window.history.pushState({}, null, 'environments/folders/build');
});
afterEach(() => {
window.history.pushState({}, null, '/');
});
let component;
describe('successfull request', () => {
......
/* 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', () => {
describe('common_utils', () => {
describe('parseUrl', () => {
it('returns an anchor tag with url', () => {
expect(gl.utils.parseUrl('/some/absolute/url').pathname).toContain('some/absolute/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.
......@@ -14,31 +13,27 @@ import '~/lib/utils/common_utils';
// 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');
expect(commonUtils.parseUrl('" test="asf"').pathname).toContain('/%22%20test=%22asf%22');
});
});
describe('gl.utils.parseUrlPathname', () => {
beforeEach(() => {
spyOn(gl.utils, 'parseUrl').and.callFake(url => ({
pathname: url,
}));
});
describe('parseUrlPathname', () => {
it('returns an absolute url when given an absolute url', () => {
expect(gl.utils.parseUrlPathname('/some/absolute/url')).toEqual('/some/absolute/url');
expect(commonUtils.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');
expect(commonUtils.parseUrlPathname('some/relative/url')).toEqual('/some/relative/url');
});
});
describe('gl.utils.getUrlParamsArray', () => {
describe('getUrlParamsArray', () => {
it('should return params array', () => {
expect(gl.utils.getUrlParamsArray() instanceof Array).toBe(true);
expect(commonUtils.getUrlParamsArray() instanceof Array).toBe(true);
});
it('should remove the question mark from the search params', () => {
const paramsArray = gl.utils.getUrlParamsArray();
const paramsArray = commonUtils.getUrlParamsArray();
expect(paramsArray[0][0] !== '?').toBe(true);
});
......@@ -46,14 +41,14 @@ import '~/lib/utils/common_utils';
history.pushState('', '', '?label_name%5B%5D=test');
expect(
gl.utils.getUrlParamsArray()[0],
commonUtils.getUrlParamsArray()[0],
).toBe('label_name[]=test');
history.pushState('', '', '?');
});
});
describe('gl.utils.handleLocationHash', () => {
describe('handleLocationHash', () => {
beforeEach(() => {
spyOn(window.document, 'getElementById').and.callThrough();
});
......@@ -68,7 +63,7 @@ import '~/lib/utils/common_utils';
it('decodes hash parameter', () => {
window.history.pushState({}, null, '#random-hash');
gl.utils.handleLocationHash();
commonUtils.handleLocationHash();
expectGetElementIdToHaveBeenCalledWith('random-hash');
expectGetElementIdToHaveBeenCalledWith('user-content-random-hash');
......@@ -76,7 +71,7 @@ import '~/lib/utils/common_utils';
it('decodes cyrillic hash parameter', () => {
window.history.pushState({}, null, '#definição');
gl.utils.handleLocationHash();
commonUtils.handleLocationHash();
expectGetElementIdToHaveBeenCalledWith('definição');
expectGetElementIdToHaveBeenCalledWith('user-content-definição');
......@@ -84,14 +79,14 @@ import '~/lib/utils/common_utils';
it('decodes encoded cyrillic hash parameter', () => {
window.history.pushState({}, null, '#defini%C3%A7%C3%A3o');
gl.utils.handleLocationHash();
commonUtils.handleLocationHash();
expectGetElementIdToHaveBeenCalledWith('definição');
expectGetElementIdToHaveBeenCalledWith('user-content-definição');
});
});
describe('gl.utils.setParamInURL', () => {
describe('setParamInURL', () => {
afterEach(() => {
window.history.pushState({}, null, '');
});
......@@ -99,40 +94,40 @@ import '~/lib/utils/common_utils';
it('should return the parameter', () => {
window.history.replaceState({}, null, '');
expect(gl.utils.setParamInURL('page', 156)).toBe('?page=156');
expect(gl.utils.setParamInURL('page', '156')).toBe('?page=156');
expect(commonUtils.setParamInURL('page', 156)).toBe('?page=156');
expect(commonUtils.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');
expect(commonUtils.setParamInURL('page', 16)).toBe('?page=16');
expect(commonUtils.setParamInURL('page', '16')).toBe('?page=16');
expect(commonUtils.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');
expect(commonUtils.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');
expect(commonUtils.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');
expect(commonUtils.setParamInURL('page', 16)).toBe('?scope=all&page=16');
expect(commonUtils.setParamInURL('page', '16')).toBe('?scope=all&page=16');
expect(commonUtils.setParamInURL('page', true)).toBe('?scope=all&page=true');
});
});
describe('gl.utils.getParameterByName', () => {
describe('getParameterByName', () => {
beforeEach(() => {
window.history.pushState({}, null, '?scope=all&p=2');
});
......@@ -142,33 +137,33 @@ import '~/lib/utils/common_utils';
});
it('should return valid parameter', () => {
const value = gl.utils.getParameterByName('scope');
expect(gl.utils.getParameterByName('p')).toEqual('2');
const value = commonUtils.getParameterByName('scope');
expect(commonUtils.getParameterByName('p')).toEqual('2');
expect(value).toBe('all');
});
it('should return invalid parameter', () => {
const value = gl.utils.getParameterByName('fakeParameter');
const value = commonUtils.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');
let value = commonUtils.getParameterByName('foo', 'http://cocteau.twins/?foo=bar');
expect(value).toBe('bar');
value = gl.utils.getParameterByName('manan', 'http://cocteau.twins/?foo=bar&manan=canchu');
value = commonUtils.getParameterByName('manan', 'http://cocteau.twins/?foo=bar&manan=canchu');
expect(value).toBe('canchu');
});
});
describe('gl.utils.normalizedHeaders', () => {
describe('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 normalized = commonUtils.normalizeHeaders(apiHeaders);
const WORKHORSE = 'X-SOMETHING-WORKHORSE';
const NGINX = 'X-SOMETHING-NGINX';
......@@ -178,14 +173,11 @@ import '~/lib/utils/common_utils';
});
});
describe('gl.utils.normalizeCRLFHeaders', () => {
describe('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);
this.normalizeCRLFHeaders = commonUtils.normalizeCRLFHeaders(this.CLRFHeaders);
});
it('should split by newline', function () {
......@@ -196,10 +188,6 @@ import '~/lib/utils/common_utils';
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',
......@@ -209,7 +197,7 @@ import '~/lib/utils/common_utils';
});
});
describe('gl.utils.parseIntPagination', () => {
describe('parseIntPagination', () => {
it('should parse to integers all string values and return pagination object', () => {
const pagination = {
'X-PER-PAGE': 10,
......@@ -229,11 +217,11 @@ import '~/lib/utils/common_utils';
previousPage: 1,
};
expect(gl.utils.parseIntPagination(pagination)).toEqual(expectedPagination);
expect(commonUtils.parseIntPagination(pagination)).toEqual(expectedPagination);
});
});
describe('gl.utils.isMetaClick', () => {
describe('isMetaClick', () => {
it('should identify meta click on Windows/Linux', () => {
const e = {
metaKey: false,
......@@ -241,7 +229,7 @@ import '~/lib/utils/common_utils';
which: 1,
};
expect(gl.utils.isMetaClick(e)).toBe(true);
expect(commonUtils.isMetaClick(e)).toBe(true);
});
it('should identify meta click on macOS', () => {
......@@ -251,7 +239,7 @@ import '~/lib/utils/common_utils';
which: 1,
};
expect(gl.utils.isMetaClick(e)).toBe(true);
expect(commonUtils.isMetaClick(e)).toBe(true);
});
it('should identify as meta click on middle-click or Mouse-wheel click', () => {
......@@ -261,11 +249,18 @@ import '~/lib/utils/common_utils';
which: 2,
};
expect(gl.utils.isMetaClick(e)).toBe(true);
expect(commonUtils.isMetaClick(e)).toBe(true);
});
});
describe('convertPermissionToBoolean', () => {
it('should convert a boolean in a string to a boolean', () => {
expect(commonUtils.convertPermissionToBoolean('true')).toEqual(true);
expect(commonUtils.convertPermissionToBoolean('false')).toEqual(false);
});
});
describe('gl.utils.backOff', () => {
describe('backOff', () => {
beforeEach(() => {
// shortcut our timeouts otherwise these tests will take a long time to finish
const origSetTimeout = window.setTimeout;
......@@ -274,7 +269,7 @@ import '~/lib/utils/common_utils';
it('solves the promise from the callback', (done) => {
const expectedResponseValue = 'Success!';
gl.utils.backOff((next, stop) => (
commonUtils.backOff((next, stop) => (
new Promise((resolve) => {
resolve(expectedResponseValue);
}).then((resp) => {
......@@ -288,7 +283,7 @@ import '~/lib/utils/common_utils';
it('catches the rejected promise from the callback ', (done) => {
const errorMessage = 'Mistakes were made!';
gl.utils.backOff((next, stop) => {
commonUtils.backOff((next, stop) => {
new Promise((resolve, reject) => {
reject(new Error(errorMessage));
}).then((resp) => {
......@@ -304,7 +299,7 @@ import '~/lib/utils/common_utils';
it('solves the promise correctly after retrying a third time', (done) => {
let numberOfCalls = 1;
const expectedResponseValue = 'Success!';
gl.utils.backOff((next, stop) => (
commonUtils.backOff((next, stop) => (
Promise.resolve(expectedResponseValue)
.then((resp) => {
if (numberOfCalls < 3) {
......@@ -323,7 +318,7 @@ import '~/lib/utils/common_utils';
});
it('rejects the backOff promise after timing out', (done) => {
gl.utils.backOff(next => next(), 64000)
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]);
......@@ -334,65 +329,87 @@ import '~/lib/utils/common_utils';
});
});
describe('gl.utils.setFavicon', () => {
describe('setFavicon', () => {
beforeEach(() => {
const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon');
favicon.setAttribute('href', 'default/favicon');
document.body.appendChild(favicon);
});
afterEach(() => {
document.body.removeChild(document.getElementById('favicon'));
});
it('should set page favicon to provided favicon', () => {
const faviconPath = '//custom_favicon';
const fakeLink = {
setAttribute() {},
};
commonUtils.setFavicon(faviconPath);
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);
expect(document.getElementById('favicon').getAttribute('href')).toEqual(faviconPath);
});
});
gl.utils.setFavicon(faviconPath);
describe('resetFavicon', () => {
beforeEach(() => {
const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon');
favicon.setAttribute('href', 'default/favicon');
document.body.appendChild(favicon);
});
afterEach(() => {
document.body.removeChild(document.getElementById('favicon'));
});
describe('gl.utils.resetFavicon', () => {
it('should reset page favicon to tanuki', () => {
const fakeLink = {
setAttribute() {},
};
commonUtils.resetFavicon();
expect(document.getElementById('favicon').getAttribute('href')).toEqual('default/favicon');
});
});
spyOn(window.document, 'getElementById').and.callFake(() => fakeLink);
spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => {
expect(attr).toEqual('href');
expect(val).toMatch(/favicon/);
describe('setCiStatusFavicon', () => {
const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1/status.json`;
beforeEach(() => {
const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon');
document.body.appendChild(favicon);
});
gl.utils.resetFavicon();
afterEach(() => {
document.body.removeChild(document.getElementById('favicon'));
});
it('should reset favicon in case of error', () => {
const favicon = document.getElementById('favicon');
spyOn($, 'ajax').and.callFake(function (options) {
options.error();
expect(favicon.getAttribute('href')).toEqual('null');
});
commonUtils.setCiStatusFavicon(BUILD_URL);
});
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();
const favicon = document.getElementById('favicon');
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();
expect(favicon.getAttribute('href')).toEqual(FAVICON_PATH);
});
gl.utils.setCiStatusFavicon(BUILD_URL);
commonUtils.setCiStatusFavicon(BUILD_URL);
});
});
describe('gl.utils.ajaxPost', () => {
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(() => {});
gl.utils.ajaxPost(requestURL, data);
commonUtils.ajaxPost(requestURL, data);
expect(ajaxSpy.calls.allArgs()[0][0].type).toEqual('POST');
});
});
});
})();
});
......@@ -78,8 +78,9 @@ import 'vendor/jquery.scrollTo';
});
describe('meta click', () => {
let metakeyEvent;
beforeEach(function () {
spyOn(gl.utils, 'isMetaClick').and.returnValue(true);
metakeyEvent = $.Event('click', { keyCode: 91, ctrlKey: true });
});
it('opens page when commits link is clicked', function () {
......@@ -89,7 +90,7 @@ import 'vendor/jquery.scrollTo';
});
this.class.bindEvents();
document.querySelector('.merge-request-tabs .commits-tab a').click();
$('.merge-request-tabs .commits-tab a').trigger(metakeyEvent);
});
it('opens page when commits badge is clicked', function () {
......@@ -99,7 +100,7 @@ import 'vendor/jquery.scrollTo';
});
this.class.bindEvents();
document.querySelector('.merge-request-tabs .commits-tab a .badge').click();
$('.merge-request-tabs .commits-tab a .badge').trigger(metakeyEvent);
});
});
......
......@@ -26,37 +26,30 @@ describe('Todos', () => {
describe('meta click', () => {
let visitUrlSpy;
let windowOpenSpy;
let metakeyEvent;
beforeEach(() => {
spyOn(gl.utils, 'isMetaClick').and.returnValue(true);
metakeyEvent = $.Event('click', { keyCode: 91, ctrlKey: true });
visitUrlSpy = spyOn(gl.utils, 'visitUrl').and.callFake(() => {});
windowOpenSpy = spyOn(window, 'open').and.callFake(() => {});
});
it('opens the todo url in another tab', (done) => {
it('opens the todo url in another tab', () => {
const todoLink = todoItem.dataset.url;
spyOn(window, 'open').and.callFake((url, target) => {
expect(todoLink).toEqual(url);
expect(target).toEqual('_blank');
done();
});
$('.todos-list .todo').trigger(metakeyEvent);
todoItem.click();
expect(visitUrlSpy).not.toHaveBeenCalled();
expect(windowOpenSpy).toHaveBeenCalledWith(todoLink, '_blank');
});
it('opens the avatar\'s url in another tab when the avatar is clicked', (done) => {
const avatarImage = todoItem.querySelector('img');
const avatarUrl = avatarImage.parentElement.getAttribute('href');
spyOn(window, 'open').and.callFake((url, target) => {
expect(avatarUrl).toEqual(url);
expect(target).toEqual('_blank');
done();
});
it('run native funcionality when avatar is clicked', () => {
$('.todos-list a').on('click', e => e.preventDefault());
$('.todos-list img').trigger(metakeyEvent);
avatarImage.click();
expect(visitUrlSpy).not.toHaveBeenCalled();
expect(windowOpenSpy).not.toHaveBeenCalled();
});
});
});
......
......@@ -224,29 +224,41 @@ describe('mrWidgetOptions', () => {
describe('handleMounted', () => {
it('should call required methods to do the initial kick-off', () => {
spyOn(vm, 'initDeploymentsPolling');
spyOn(vm, 'setFavicon');
spyOn(vm, 'setFaviconHelper');
vm.handleMounted();
expect(vm.setFavicon).toHaveBeenCalled();
expect(vm.setFaviconHelper).toHaveBeenCalled();
expect(vm.initDeploymentsPolling).toHaveBeenCalled();
});
});
describe('setFavicon', () => {
let faviconElement;
beforeEach(() => {
const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon');
document.body.appendChild(favicon);
faviconElement = document.getElementById('favicon');
});
afterEach(() => {
document.body.removeChild(document.getElementById('favicon'));
});
it('should call setFavicon method', () => {
spyOn(gl.utils, 'setFavicon');
vm.setFavicon();
vm.setFaviconHelper();
expect(gl.utils.setFavicon).toHaveBeenCalledWith(vm.mr.ciStatusFaviconPath);
expect(faviconElement.getAttribute('href')).toEqual(vm.mr.ciStatusFaviconPath);
});
it('should not call setFavicon when there is no ciStatusFaviconPath', () => {
spyOn(gl.utils, 'setFavicon');
vm.mr.ciStatusFaviconPath = null;
vm.setFavicon();
vm.setFaviconHelper();
expect(gl.utils.setFavicon).not.toHaveBeenCalled();
expect(faviconElement.getAttribute('href')).toEqual(null);
});
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment