diff options
Diffstat (limited to 'app/assets/javascripts/lib/utils')
-rw-r--r-- | app/assets/javascripts/lib/utils/common_utils.js | 17 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/datetime_utility.js | 74 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/keycodes.js | 1 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/poll_until_complete.js | 42 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/text_utility.js | 28 |
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 * |