summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/lib/utils
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/lib/utils')
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js17
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js74
-rw-r--r--app/assets/javascripts/lib/utils/keycodes.js1
-rw-r--r--app/assets/javascripts/lib/utils/poll_until_complete.js42
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js28
5 files changed, 148 insertions, 14 deletions
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index e4001e94478..a2591180039 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -2,12 +2,12 @@
* @module common-utils
*/
+import { GlBreakpointInstance as breakpointInstance } from '@gitlab/ui/dist/utils';
import $ from 'jquery';
import axios from './axios_utils';
import { getLocationHash } from './url_utility';
import { convertToCamelCase, convertToSnakeCase } from './text_utility';
import { isObject } from './type_utility';
-import breakpointInstance from '../../breakpoints';
export const getPagePath = (index = 0) => {
const page = $('body').attr('data-page') || '';
@@ -135,7 +135,9 @@ export const handleLocationHash = () => {
adjustment -= topPadding;
}
- window.scrollBy(0, adjustment);
+ setTimeout(() => {
+ window.scrollBy(0, adjustment);
+ });
};
// Check if element scrolled into viewport from above or below
@@ -247,6 +249,7 @@ export const scrollToElement = element => {
}
const { top } = $el.offset();
+ // eslint-disable-next-line no-jquery/no-animate
return $('body, html').animate(
{
scrollTop: top - contentTop(),
@@ -481,6 +484,16 @@ export const historyPushState = newUrl => {
};
/**
+ * Based on the current location and the string parameters provided
+ * overwrites the current entry in the history without reloading the page.
+ *
+ * @param {String} param
+ */
+export const historyReplaceState = newUrl => {
+ window.history.replaceState({}, document.title, newUrl);
+};
+
+/**
* Returns true for a String value of "true" and false otherwise.
* This is the opposite of Boolean(...).toString().
* `parseBoolean` is idempotent.
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 996692bacb3..fd9a13be18b 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -392,15 +392,21 @@ export const getTimeframeWindowFrom = (initialStartDate, length) => {
* @param {Date} date
* @param {Array} quarter
*/
-export const dayInQuarter = (date, quarter) =>
- quarter.reduce((acc, month) => {
- if (date.getMonth() > month.getMonth()) {
+export const dayInQuarter = (date, quarter) => {
+ const dateValues = {
+ date: date.getDate(),
+ month: date.getMonth(),
+ };
+
+ return quarter.reduce((acc, month) => {
+ if (dateValues.month > month.getMonth()) {
return acc + totalDaysInMonth(month);
- } else if (date.getMonth() === month.getMonth()) {
- return acc + date.getDate();
+ } else if (dateValues.month === month.getMonth()) {
+ return acc + dateValues.date;
}
return acc + 0;
}, 0);
+};
window.gl = window.gl || {};
window.gl.utils = {
@@ -464,7 +470,7 @@ export const pikadayToString = date => {
*/
export const parseSeconds = (
seconds,
- { daysPerWeek = 5, hoursPerDay = 8, limitToHours = false } = {},
+ { daysPerWeek = 5, hoursPerDay = 8, limitToHours = false, limitToDays = false } = {},
) => {
const DAYS_PER_WEEK = daysPerWeek;
const HOURS_PER_DAY = hoursPerDay;
@@ -480,8 +486,11 @@ export const parseSeconds = (
minutes: 1,
};
- if (limitToHours) {
+ if (limitToDays || limitToHours) {
timePeriodConstraints.weeks = 0;
+ }
+
+ if (limitToHours) {
timePeriodConstraints.days = 0;
}
@@ -546,6 +555,16 @@ export const calculateRemainingMilliseconds = endDate => {
export const getDateInPast = (date, daysInPast) =>
new Date(newDate(date).setDate(date.getDate() - daysInPast));
+/**
+ * Adds a given number of days to a given date and returns the new date.
+ *
+ * @param {Date} date the date that we will add days to
+ * @param {Number} daysInFuture number of days that are added to a given date
+ * @returns {Date} Date in future as Date object
+ */
+export const getDateInFuture = (date, daysInFuture) =>
+ new Date(newDate(date).setDate(date.getDate() + daysInFuture));
+
/*
* Appending T00:00:00 makes JS assume local time and prevents it from shifting the date
* to match the user's time zone. We want to display the date in server time for now, to
@@ -606,3 +625,44 @@ export const secondsToDays = seconds => Math.round(seconds / 86400);
* @return {Date} the date following the date provided
*/
export const dayAfter = date => new Date(newDate(date).setDate(date.getDate() + 1));
+
+/**
+ * Mimics the behaviour of the rails distance_of_time_in_words function
+ * https://api.rubyonrails.org/v6.0.1/classes/ActionView/Helpers/DateHelper.html#method-i-distance_of_time_in_words
+ * 0 < -> 29 secs => less than a minute
+ * 30 secs < -> 1 min, 29 secs => 1 minute
+ * 1 min, 30 secs < -> 44 mins, 29 secs => [2..44] minutes
+ * 44 mins, 30 secs < -> 89 mins, 29 secs => about 1 hour
+ * 89 mins, 30 secs < -> 23 hrs, 59 mins, 29 secs => about[2..24]hours
+ * 23 hrs, 59 mins, 30 secs < -> 41 hrs, 59 mins, 29 secs => 1 day
+ * 41 hrs, 59 mins, 30 secs => x days
+ *
+ * @param {Number} seconds
+ * @return {String} approximated time
+ */
+export const approximateDuration = (seconds = 0) => {
+ if (!_.isNumber(seconds) || seconds < 0) {
+ return '';
+ }
+
+ const ONE_MINUTE_LIMIT = 90; // 1 minute 30s
+ const MINUTES_LIMIT = 2670; // 44 minutes 30s
+ const ONE_HOUR_LIMIT = 5370; // 89 minutes 30s
+ const HOURS_LIMIT = 86370; // 23 hours 59 minutes 30s
+ const ONE_DAY_LIMIT = 151170; // 41 hours 59 minutes 30s
+
+ const { days = 0, hours = 0, minutes = 0 } = parseSeconds(seconds, {
+ daysPerWeek: 7,
+ hoursPerDay: 24,
+ limitToDays: true,
+ });
+
+ if (seconds < 30) {
+ return __('less than a minute');
+ } else if (seconds < MINUTES_LIMIT) {
+ return n__('1 minute', '%d minutes', seconds < ONE_MINUTE_LIMIT ? 1 : minutes);
+ } else if (seconds < HOURS_LIMIT) {
+ return n__('about 1 hour', 'about %d hours', seconds < ONE_HOUR_LIMIT ? 1 : hours);
+ }
+ return n__('1 day', '%d days', seconds < ONE_DAY_LIMIT ? 1 : days);
+};
diff --git a/app/assets/javascripts/lib/utils/keycodes.js b/app/assets/javascripts/lib/utils/keycodes.js
index 5e0f9b612a2..2270d329c24 100644
--- a/app/assets/javascripts/lib/utils/keycodes.js
+++ b/app/assets/javascripts/lib/utils/keycodes.js
@@ -2,3 +2,4 @@ export const UP_KEY_CODE = 38;
export const DOWN_KEY_CODE = 40;
export const ENTER_KEY_CODE = 13;
export const ESC_KEY_CODE = 27;
+export const BACKSPACE_KEY_CODE = 8;
diff --git a/app/assets/javascripts/lib/utils/poll_until_complete.js b/app/assets/javascripts/lib/utils/poll_until_complete.js
new file mode 100644
index 00000000000..199d0e6f0f7
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/poll_until_complete.js
@@ -0,0 +1,42 @@
+import axios from '~/lib/utils/axios_utils';
+import Poll from './poll';
+import httpStatusCodes from './http_status';
+
+/**
+ * Polls an endpoint until it returns either a 200 OK or a error status.
+ * The Poll-Interval header in the responses are used to determine how
+ * frequently to poll.
+ *
+ * Once a 200 OK is received, the promise resolves with that response. If an
+ * error status is received, the promise rejects with the error.
+ *
+ * @param {string} url - The URL to poll.
+ * @param {Object} [config] - The config to provide to axios.get().
+ * @returns {Promise}
+ */
+export default (url, config = {}) =>
+ new Promise((resolve, reject) => {
+ const eTagPoll = new Poll({
+ resource: {
+ axiosGet(data) {
+ return axios.get(data.url, {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ ...data.config,
+ });
+ },
+ },
+ data: { url, config },
+ method: 'axiosGet',
+ successCallback: response => {
+ if (response.status === httpStatusCodes.OK) {
+ resolve(response);
+ eTagPoll.stop();
+ }
+ },
+ errorCallback: reject,
+ });
+
+ eTagPoll.makeRequest();
+ });
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 6bbf118d7d1..a03fedcd7e7 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -21,12 +21,17 @@ export const addDelimiter = text =>
export const highCountTrim = count => (count > 99 ? '99+' : count);
/**
- * Converts first char to uppercase and replaces undercores with spaces
- * @param {String} string
+ * Converts first char to uppercase and replaces the given separator with spaces
+ * @param {String} string - The string to humanize
+ * @param {String} separator - The separator used to separate words (defaults to "_")
* @requires {String}
+ * @returns {String}
*/
-export const humanize = string =>
- string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
+export const humanize = (string, separator = '_') => {
+ const replaceRegex = new RegExp(separator, 'g');
+
+ return string.charAt(0).toUpperCase() + string.replace(replaceRegex, ' ').slice(1);
+};
/**
* Replaces underscores with dashes
@@ -45,7 +50,11 @@ export const slugify = (str, separator = '-') => {
const slug = str
.trim()
.toLowerCase()
- .replace(/[^a-zA-Z0-9_.-]+/g, separator);
+ .replace(/[^a-zA-Z0-9_.-]+/g, separator)
+ // Remove any duplicate separators or separator prefixes/suffixes
+ .split(separator)
+ .filter(Boolean)
+ .join(separator);
return slug === separator ? '' : slug;
};
@@ -160,6 +169,15 @@ export const convertToSentenceCase = string => {
};
/**
+ * Converts a sentence to title case
+ * e.g. Hello world => Hello World
+ *
+ * @param {String} string
+ * @returns {String}
+ */
+export const convertToTitleCase = string => string.replace(/\b[a-z]/g, s => s.toUpperCase());
+
+/**
* Splits camelCase or PascalCase words
* e.g. HelloWorld => Hello World
*