Commit 17204649 authored by Fatih Acet's avatar Fatih Acet

Merge branch '37298-the-prettytime-utils-module-could-use-some-light-maintenance' into 'master'

Resolve some tech debt in the prettyTime module

Closes #37298

See merge request gitlab-org/gitlab-ce!14058
parents 8cf2f51c 03b7df7b
import _ from 'underscore'; import _ from 'underscore';
(() => { /*
/* * TODO: Make these methods more configurable (e.g. stringifyTime condensed or
* TODO: Make these methods more configurable (e.g. stringifyTime condensed or * non-condensed, abbreviateTimelengths)
* non-condensed, abbreviateTimelengths) * */
* */
/*
const utils = window.gl.utils = gl.utils || {}; * Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # }
const prettyTime = utils.prettyTime = { * Seconds can be negative or positive, zero or non-zero. Can be configured for any day
/* * or week length.
* Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # } */
* Seconds can be negative or positive, zero or non-zero. Can be configured for any day
* or week length. export function parseSeconds(seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {}) {
*/ const DAYS_PER_WEEK = daysPerWeek;
parseSeconds(seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {}) { const HOURS_PER_DAY = hoursPerDay;
const DAYS_PER_WEEK = daysPerWeek; const MINUTES_PER_HOUR = 60;
const HOURS_PER_DAY = hoursPerDay; const MINUTES_PER_WEEK = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR;
const MINUTES_PER_HOUR = 60; const MINUTES_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR;
const MINUTES_PER_WEEK = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR;
const MINUTES_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR; const timePeriodConstraints = {
weeks: MINUTES_PER_WEEK,
const timePeriodConstraints = { days: MINUTES_PER_DAY,
weeks: MINUTES_PER_WEEK, hours: MINUTES_PER_HOUR,
days: MINUTES_PER_DAY, minutes: 1,
hours: MINUTES_PER_HOUR, };
minutes: 1,
};
let unorderedMinutes = prettyTime.secondsToMinutes(seconds); let unorderedMinutes = Math.abs(seconds / MINUTES_PER_HOUR);
return _.mapObject(timePeriodConstraints, (minutesPerPeriod) => { return _.mapObject(timePeriodConstraints, (minutesPerPeriod) => {
const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod); const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod);
unorderedMinutes -= (periodCount * minutesPerPeriod); unorderedMinutes -= (periodCount * minutesPerPeriod);
return periodCount; return periodCount;
}); });
}, }
/* /*
* Accepts a timeObject and returns a condensed string representation of it * Accepts a timeObject (see parseSeconds) and returns a condensed string representation of it
* (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included. * (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included.
*/ */
stringifyTime(timeObject) { export function stringifyTime(timeObject) {
const reducedTime = _.reduce(timeObject, (memo, unitValue, unitName) => { const reducedTime = _.reduce(timeObject, (memo, unitValue, unitName) => {
const isNonZero = !!unitValue; const isNonZero = !!unitValue;
return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo; return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo;
}, '').trim(); }, '').trim();
return reducedTime.length ? reducedTime : '0m'; return reducedTime.length ? reducedTime : '0m';
}, }
/* /*
* Accepts a time string of any size (e.g. '1w 2d 3h 5m' or '1w 2d') and returns * Accepts a time string of any size (e.g. '1w 2d 3h 5m' or '1w 2d') and returns
* the first non-zero unit/value pair. * the first non-zero unit/value pair.
*/ */
abbreviateTime(timeStr) { export function abbreviateTime(timeStr) {
return timeStr.split(' ') return timeStr.split(' ')
.filter(unitStr => unitStr.charAt(0) !== '0')[0]; .filter(unitStr => unitStr.charAt(0) !== '0')[0];
}, }
secondsToMinutes(seconds) {
return Math.abs(seconds / 60);
},
};
})(window.gl || (window.gl = {}));
import stopwatchSvg from 'icons/_icon_stopwatch.svg'; import stopwatchSvg from 'icons/_icon_stopwatch.svg';
import { abbreviateTime } from '../../../lib/utils/pretty_time';
import '../../../lib/utils/pretty_time';
export default { export default {
name: 'time-tracking-collapsed-state', name: 'time-tracking-collapsed-state',
...@@ -79,7 +78,7 @@ export default { ...@@ -79,7 +78,7 @@ export default {
}, },
methods: { methods: {
abbreviateTime(timeStr) { abbreviateTime(timeStr) {
return gl.utils.prettyTime.abbreviateTime(timeStr); return abbreviateTime(timeStr);
}, },
}, },
template: ` template: `
......
import '../../../lib/utils/pretty_time'; import { parseSeconds, stringifyTime } from '../../../lib/utils/pretty_time';
const prettyTime = gl.utils.prettyTime;
export default { export default {
name: 'time-tracking-comparison-pane', name: 'time-tracking-comparison-pane',
...@@ -23,12 +21,12 @@ export default { ...@@ -23,12 +21,12 @@ export default {
}, },
}, },
computed: { computed: {
parsedRemaining() { parsedTimeRemaining() {
const diffSeconds = this.timeEstimate - this.timeSpent; const diffSeconds = this.timeEstimate - this.timeSpent;
return prettyTime.parseSeconds(diffSeconds); return parseSeconds(diffSeconds);
}, },
timeRemainingHumanReadable() { timeRemainingHumanReadable() {
return prettyTime.stringifyTime(this.parsedRemaining); return stringifyTime(this.parsedTimeRemaining);
}, },
timeRemainingTooltip() { timeRemainingTooltip() {
const prefix = this.timeRemainingMinutes < 0 ? 'Over by' : 'Time remaining:'; const prefix = this.timeRemainingMinutes < 0 ? 'Over by' : 'Time remaining:';
...@@ -44,13 +42,6 @@ export default { ...@@ -44,13 +42,6 @@ export default {
timeRemainingStatusClass() { timeRemainingStatusClass() {
return this.timeEstimate >= this.timeSpent ? 'within_estimate' : 'over_estimate'; return this.timeEstimate >= this.timeSpent ? 'within_estimate' : 'over_estimate';
}, },
/* Parsed time values */
parsedEstimate() {
return prettyTime.parseSeconds(this.timeEstimate);
},
parsedSpent() {
return prettyTime.parseSeconds(this.timeSpent);
},
}, },
template: ` template: `
<div class="time-tracking-comparison-pane"> <div class="time-tracking-comparison-pane">
......
import '~/lib/utils/pretty_time'; import { parseSeconds, abbreviateTime, stringifyTime } from '~/lib/utils/pretty_time';
(() => { function assertTimeUnits(obj, minutes, hours, days, weeks) {
const prettyTime = gl.utils.prettyTime; expect(obj.minutes).toBe(minutes);
expect(obj.hours).toBe(hours);
expect(obj.days).toBe(days);
expect(obj.weeks).toBe(weeks);
}
describe('prettyTime methods', function () { describe('prettyTime methods', () => {
describe('parseSeconds', function () { describe('parseSeconds', () => {
it('should correctly parse a negative value', function () { it('should correctly parse a negative value', () => {
const parser = prettyTime.parseSeconds; const zeroSeconds = parseSeconds(-1000);
const zeroSeconds = parser(-1000); assertTimeUnits(zeroSeconds, 16, 0, 0, 0);
});
expect(zeroSeconds.minutes).toBe(16);
expect(zeroSeconds.hours).toBe(0);
expect(zeroSeconds.days).toBe(0);
expect(zeroSeconds.weeks).toBe(0);
});
it('should correctly parse a zero value', function () {
const parser = prettyTime.parseSeconds;
const zeroSeconds = parser(0);
expect(zeroSeconds.minutes).toBe(0);
expect(zeroSeconds.hours).toBe(0);
expect(zeroSeconds.days).toBe(0);
expect(zeroSeconds.weeks).toBe(0);
});
it('should correctly parse a small non-zero second values', function () {
const parser = prettyTime.parseSeconds;
const subOneMinute = parser(10);
expect(subOneMinute.minutes).toBe(0);
expect(subOneMinute.hours).toBe(0);
expect(subOneMinute.days).toBe(0);
expect(subOneMinute.weeks).toBe(0);
const aboveOneMinute = parser(100);
expect(aboveOneMinute.minutes).toBe(1);
expect(aboveOneMinute.hours).toBe(0);
expect(aboveOneMinute.days).toBe(0);
expect(aboveOneMinute.weeks).toBe(0);
const manyMinutes = parser(1000);
expect(manyMinutes.minutes).toBe(16);
expect(manyMinutes.hours).toBe(0);
expect(manyMinutes.days).toBe(0);
expect(manyMinutes.weeks).toBe(0);
});
it('should correctly parse large second values', function () {
const parser = prettyTime.parseSeconds;
const aboveOneHour = parser(4800);
expect(aboveOneHour.minutes).toBe(20);
expect(aboveOneHour.hours).toBe(1);
expect(aboveOneHour.days).toBe(0);
expect(aboveOneHour.weeks).toBe(0);
const aboveOneDay = parser(110000);
expect(aboveOneDay.minutes).toBe(33);
expect(aboveOneDay.hours).toBe(6);
expect(aboveOneDay.days).toBe(3);
expect(aboveOneDay.weeks).toBe(0);
const aboveOneWeek = parser(25000000);
expect(aboveOneWeek.minutes).toBe(26);
expect(aboveOneWeek.hours).toBe(0);
expect(aboveOneWeek.days).toBe(3);
expect(aboveOneWeek.weeks).toBe(173);
});
it('should correctly accept a custom param for hoursPerDay', function () { it('should correctly parse a zero value', () => {
const parser = prettyTime.parseSeconds; const zeroSeconds = parseSeconds(0);
const config = { hoursPerDay: 24 };
const aboveOneHour = parser(4800, config); assertTimeUnits(zeroSeconds, 0, 0, 0, 0);
});
expect(aboveOneHour.minutes).toBe(20); it('should correctly parse a small non-zero second values', () => {
expect(aboveOneHour.hours).toBe(1); const subOneMinute = parseSeconds(10);
expect(aboveOneHour.days).toBe(0); const aboveOneMinute = parseSeconds(100);
expect(aboveOneHour.weeks).toBe(0); const manyMinutes = parseSeconds(1000);
const aboveOneDay = parser(110000, config); assertTimeUnits(subOneMinute, 0, 0, 0, 0);
assertTimeUnits(aboveOneMinute, 1, 0, 0, 0);
assertTimeUnits(manyMinutes, 16, 0, 0, 0);
});
expect(aboveOneDay.minutes).toBe(33); it('should correctly parse large second values', () => {
expect(aboveOneDay.hours).toBe(6); const aboveOneHour = parseSeconds(4800);
expect(aboveOneDay.days).toBe(1); const aboveOneDay = parseSeconds(110000);
expect(aboveOneDay.weeks).toBe(0); const aboveOneWeek = parseSeconds(25000000);
const aboveOneWeek = parser(25000000, config); assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
assertTimeUnits(aboveOneDay, 33, 6, 3, 0);
assertTimeUnits(aboveOneWeek, 26, 0, 3, 173);
});
expect(aboveOneWeek.minutes).toBe(26); it('should correctly accept a custom param for hoursPerDay', () => {
expect(aboveOneWeek.hours).toBe(8); const config = { hoursPerDay: 24 };
expect(aboveOneWeek.days).toBe(4);
expect(aboveOneWeek.weeks).toBe(57); const aboveOneHour = parseSeconds(4800, config);
}); const aboveOneDay = parseSeconds(110000, config);
const aboveOneWeek = parseSeconds(25000000, config);
it('should correctly accept a custom param for daysPerWeek', function () { assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
const parser = prettyTime.parseSeconds; assertTimeUnits(aboveOneDay, 33, 6, 1, 0);
const config = { daysPerWeek: 7 }; assertTimeUnits(aboveOneWeek, 26, 8, 4, 57);
});
const aboveOneHour = parser(4800, config); it('should correctly accept a custom param for daysPerWeek', () => {
const config = { daysPerWeek: 7 };
expect(aboveOneHour.minutes).toBe(20); const aboveOneHour = parseSeconds(4800, config);
expect(aboveOneHour.hours).toBe(1); const aboveOneDay = parseSeconds(110000, config);
expect(aboveOneHour.days).toBe(0); const aboveOneWeek = parseSeconds(25000000, config);
expect(aboveOneHour.weeks).toBe(0);
const aboveOneDay = parser(110000, config); assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
assertTimeUnits(aboveOneDay, 33, 6, 3, 0);
assertTimeUnits(aboveOneWeek, 26, 0, 0, 124);
});
expect(aboveOneDay.minutes).toBe(33); it('should correctly accept custom params for daysPerWeek and hoursPerDay', () => {
expect(aboveOneDay.hours).toBe(6); const config = { daysPerWeek: 55, hoursPerDay: 14 };
expect(aboveOneDay.days).toBe(3);
expect(aboveOneDay.weeks).toBe(0);
const aboveOneWeek = parser(25000000, config); const aboveOneHour = parseSeconds(4800, config);
const aboveOneDay = parseSeconds(110000, config);
const aboveOneWeek = parseSeconds(25000000, config);
expect(aboveOneWeek.minutes).toBe(26); assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
expect(aboveOneWeek.hours).toBe(0); assertTimeUnits(aboveOneDay, 33, 2, 2, 0);
expect(aboveOneWeek.days).toBe(0); assertTimeUnits(aboveOneWeek, 26, 0, 1, 9);
});
});
expect(aboveOneWeek.weeks).toBe(124); describe('stringifyTime', () => {
}); it('should stringify values with all non-zero units', () => {
const timeObject = {
weeks: 1,
days: 4,
hours: 7,
minutes: 20,
};
it('should correctly accept custom params for daysPerWeek and hoursPerDay', function () { const timeString = stringifyTime(timeObject);
const parser = prettyTime.parseSeconds;
const config = { daysPerWeek: 55, hoursPerDay: 14 };
const aboveOneHour = parser(4800, config); expect(timeString).toBe('1w 4d 7h 20m');
});
expect(aboveOneHour.minutes).toBe(20); it('should stringify values with some non-zero units', () => {
expect(aboveOneHour.hours).toBe(1); const timeObject = {
expect(aboveOneHour.days).toBe(0); weeks: 0,
expect(aboveOneHour.weeks).toBe(0); days: 4,
hours: 0,
minutes: 20,
};
const aboveOneDay = parser(110000, config); const timeString = stringifyTime(timeObject);
expect(aboveOneDay.minutes).toBe(33); expect(timeString).toBe('4d 20m');
expect(aboveOneDay.hours).toBe(2); });
expect(aboveOneDay.days).toBe(2);
expect(aboveOneDay.weeks).toBe(0);
const aboveOneWeek = parser(25000000, config); it('should stringify values with no non-zero units', () => {
const timeObject = {
weeks: 0,
days: 0,
hours: 0,
minutes: 0,
};
expect(aboveOneWeek.minutes).toBe(26); const timeString = stringifyTime(timeObject);
expect(aboveOneWeek.hours).toBe(0);
expect(aboveOneWeek.days).toBe(1);
expect(aboveOneWeek.weeks).toBe(9); expect(timeString).toBe('0m');
});
}); });
});
describe('stringifyTime', function () { describe('abbreviateTime', () => {
it('should stringify values with all non-zero units', function () { it('should abbreviate stringified times for weeks', () => {
const timeObject = { const fullTimeString = '1w 3d 4h 5m';
weeks: 1, expect(abbreviateTime(fullTimeString)).toBe('1w');
days: 4,
hours: 7,
minutes: 20,
};
const timeString = prettyTime.stringifyTime(timeObject);
expect(timeString).toBe('1w 4d 7h 20m');
});
it('should stringify values with some non-zero units', function () {
const timeObject = {
weeks: 0,
days: 4,
hours: 0,
minutes: 20,
};
const timeString = prettyTime.stringifyTime(timeObject);
expect(timeString).toBe('4d 20m');
});
it('should stringify values with no non-zero units', function () {
const timeObject = {
weeks: 0,
days: 0,
hours: 0,
minutes: 0,
};
const timeString = prettyTime.stringifyTime(timeObject);
expect(timeString).toBe('0m');
});
}); });
describe('abbreviateTime', function () { it('should abbreviate stringified times for non-weeks', () => {
it('should abbreviate stringified times for weeks', function () { const fullTimeString = '0w 3d 4h 5m';
const fullTimeString = '1w 3d 4h 5m'; expect(abbreviateTime(fullTimeString)).toBe('3d');
expect(prettyTime.abbreviateTime(fullTimeString)).toBe('1w');
});
it('should abbreviate stringified times for non-weeks', function () {
const fullTimeString = '0w 3d 4h 5m';
expect(prettyTime.abbreviateTime(fullTimeString)).toBe('3d');
});
}); });
}); });
})(window.gl || (window.gl = {})); });
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