Commit c78607fa authored by Kushal Pandya's avatar Kushal Pandya Committed by Filipa Lacerda

EE Port: Combine all datetime library functions into `datetime_utility.js`

parent b8275cdc
...@@ -3,8 +3,7 @@ import Pikaday from 'pikaday'; ...@@ -3,8 +3,7 @@ import Pikaday from 'pikaday';
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { __ } from '~/locale'; import { __ } from '~/locale';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import { timeFor } from './lib/utils/datetime_utility'; import { timeFor, parsePikadayDate, pikadayToString } from './lib/utils/datetime_utility';
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
import boardsStore from './boards/stores/boards_store'; import boardsStore from './boards/stores/boards_store';
class DueDateSelect { class DueDateSelect {
......
/* eslint-disable no-new, no-unused-vars, consistent-return, no-else-return */
/* global GitLab */
import $ from 'jquery'; import $ from 'jquery';
import Pikaday from 'pikaday'; import Pikaday from 'pikaday';
import Autosave from './autosave'; import Autosave from './autosave';
...@@ -8,7 +5,7 @@ import UsersSelect from './users_select'; ...@@ -8,7 +5,7 @@ import UsersSelect from './users_select';
import GfmAutoComplete from './gfm_auto_complete'; import GfmAutoComplete from './gfm_auto_complete';
import ZenMode from './zen_mode'; import ZenMode from './zen_mode';
import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select'; import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select';
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix'; import { parsePikadayDate, pikadayToString } from './lib/utils/datetime_utility';
import groupsSelect from './groups_select'; import groupsSelect from './groups_select';
export default class IssuableForm { export default class IssuableForm {
...@@ -20,10 +17,12 @@ export default class IssuableForm { ...@@ -20,10 +17,12 @@ export default class IssuableForm {
this.handleSubmit = this.handleSubmit.bind(this); this.handleSubmit = this.handleSubmit.bind(this);
this.wipRegex = /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i; this.wipRegex = /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i;
new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources).setup(); this.gfmAutoComplete = new GfmAutoComplete(
new UsersSelect(); gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources,
).setup();
this.usersSelect = new UsersSelect();
groupsSelect(); groupsSelect();
new ZenMode(); this.zenMode = new ZenMode();
this.titleField = this.form.find('input[name*="[title]"]'); this.titleField = this.form.find('input[name*="[title]"]');
this.descriptionField = this.form.find('textarea[name*="[description]"]'); this.descriptionField = this.form.find('textarea[name*="[description]"]');
...@@ -59,8 +58,16 @@ export default class IssuableForm { ...@@ -59,8 +58,16 @@ export default class IssuableForm {
} }
initAutosave() { initAutosave() {
new Autosave(this.titleField, [document.location.pathname, document.location.search, 'title']); this.autosave = new Autosave(this.titleField, [
return new Autosave(this.descriptionField, [document.location.pathname, document.location.search, 'description']); document.location.pathname,
document.location.search,
'title',
]);
return new Autosave(this.descriptionField, [
document.location.pathname,
document.location.search,
'description',
]);
} }
handleSubmit() { handleSubmit() {
...@@ -76,7 +83,7 @@ export default class IssuableForm { ...@@ -76,7 +83,7 @@ export default class IssuableForm {
this.$wipExplanation = this.form.find('.js-wip-explanation'); this.$wipExplanation = this.form.find('.js-wip-explanation');
this.$noWipExplanation = this.form.find('.js-no-wip-explanation'); this.$noWipExplanation = this.form.find('.js-no-wip-explanation');
if (!(this.$wipExplanation.length && this.$noWipExplanation.length)) { if (!(this.$wipExplanation.length && this.$noWipExplanation.length)) {
return; return undefined;
} }
this.form.on('click', '.js-toggle-wip', this.toggleWip); this.form.on('click', '.js-toggle-wip', this.toggleWip);
this.titleField.on('keyup blur', this.renderWipExplanation); this.titleField.on('keyup blur', this.renderWipExplanation);
...@@ -91,10 +98,9 @@ export default class IssuableForm { ...@@ -91,10 +98,9 @@ export default class IssuableForm {
if (this.workInProgress()) { if (this.workInProgress()) {
this.$wipExplanation.show(); this.$wipExplanation.show();
return this.$noWipExplanation.hide(); return this.$noWipExplanation.hide();
} else {
this.$wipExplanation.hide();
return this.$noWipExplanation.show();
} }
this.$wipExplanation.hide();
return this.$noWipExplanation.show();
} }
toggleWip(event) { toggleWip(event) {
...@@ -112,7 +118,7 @@ export default class IssuableForm { ...@@ -112,7 +118,7 @@ export default class IssuableForm {
} }
addWip() { addWip() {
this.titleField.val(`WIP: ${(this.titleField.val())}`); this.titleField.val(`WIP: ${this.titleField.val()}`);
} }
initTargetBranchDropdown() { initTargetBranchDropdown() {
......
export const pad = (val, len = 2) => `0${val}`.slice(-len);
/**
* Formats dates in Pickaday
* @param {String} dateString Date in yyyy-mm-dd format
* @return {Date} UTC format
*/
export const parsePikadayDate = dateString => {
const parts = dateString.split('-');
const year = parseInt(parts[0], 10);
const month = parseInt(parts[1] - 1, 10);
const day = parseInt(parts[2], 10);
return new Date(year, month, day);
};
/**
* Used `onSelect` method in pickaday
* @param {Date} date UTC format
* @return {String} Date formated in yyyy-mm-dd
*/
export const pikadayToString = date => {
const day = pad(date.getDate());
const month = pad(date.getMonth() + 1);
const year = date.getFullYear();
return `${year}-${month}-${day}`;
};
import $ from 'jquery'; import $ from 'jquery';
import _ from 'underscore';
import timeago from 'timeago.js'; import timeago from 'timeago.js';
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { pluralize } from './text_utility'; import { pluralize } from './text_utility';
...@@ -46,6 +47,8 @@ const getMonthNames = abbreviated => { ...@@ -46,6 +47,8 @@ const getMonthNames = abbreviated => {
]; ];
}; };
export const pad = (val, len = 2) => `0${val}`.slice(-len);
/** /**
* Given a date object returns the day of the week in English * Given a date object returns the day of the week in English
* @param {date} date * @param {date} date
...@@ -74,10 +77,10 @@ let timeagoInstance; ...@@ -74,10 +77,10 @@ let timeagoInstance;
/** /**
* Sets a timeago Instance * Sets a timeago Instance
*/ */
export function getTimeago() { export const getTimeago = () => {
if (!timeagoInstance) { if (!timeagoInstance) {
const localeRemaining = function getLocaleRemaining(number, index) { const localeRemaining = (number, index) =>
return [ [
[s__('Timeago|just now'), s__('Timeago|right now')], [s__('Timeago|just now'), s__('Timeago|right now')],
[s__('Timeago|%s seconds ago'), s__('Timeago|%s seconds remaining')], [s__('Timeago|%s seconds ago'), s__('Timeago|%s seconds remaining')],
[s__('Timeago|1 minute ago'), s__('Timeago|1 minute remaining')], [s__('Timeago|1 minute ago'), s__('Timeago|1 minute remaining')],
...@@ -93,9 +96,9 @@ export function getTimeago() { ...@@ -93,9 +96,9 @@ export function getTimeago() {
[s__('Timeago|1 year ago'), s__('Timeago|1 year remaining')], [s__('Timeago|1 year ago'), s__('Timeago|1 year remaining')],
[s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')], [s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')],
][index]; ][index];
};
const locale = function getLocale(number, index) { const locale = (number, index) =>
return [ [
[s__('Timeago|just now'), s__('Timeago|right now')], [s__('Timeago|just now'), s__('Timeago|right now')],
[s__('Timeago|%s seconds ago'), s__('Timeago|in %s seconds')], [s__('Timeago|%s seconds ago'), s__('Timeago|in %s seconds')],
[s__('Timeago|1 minute ago'), s__('Timeago|in 1 minute')], [s__('Timeago|1 minute ago'), s__('Timeago|in 1 minute')],
...@@ -111,7 +114,6 @@ export function getTimeago() { ...@@ -111,7 +114,6 @@ export function getTimeago() {
[s__('Timeago|1 year ago'), s__('Timeago|in 1 year')], [s__('Timeago|1 year ago'), s__('Timeago|in 1 year')],
[s__('Timeago|%s years ago'), s__('Timeago|in %s years')], [s__('Timeago|%s years ago'), s__('Timeago|in %s years')],
][index]; ][index];
};
timeago.register(timeagoLanguageCode, locale); timeago.register(timeagoLanguageCode, locale);
timeago.register(`${timeagoLanguageCode}-remaining`, localeRemaining); timeago.register(`${timeagoLanguageCode}-remaining`, localeRemaining);
...@@ -119,7 +121,7 @@ export function getTimeago() { ...@@ -119,7 +121,7 @@ export function getTimeago() {
} }
return timeagoInstance; return timeagoInstance;
} };
/** /**
* For the given element, renders a timeago instance. * For the given element, renders a timeago instance.
...@@ -184,7 +186,7 @@ export const getDayDifference = (a, b) => { ...@@ -184,7 +186,7 @@ export const getDayDifference = (a, b) => {
* @param {Number} seconds * @param {Number} seconds
* @return {String} * @return {String}
*/ */
export function timeIntervalInWords(intervalInSeconds) { export const timeIntervalInWords = intervalInSeconds => {
const secondsInteger = parseInt(intervalInSeconds, 10); const secondsInteger = parseInt(intervalInSeconds, 10);
const minutes = Math.floor(secondsInteger / 60); const minutes = Math.floor(secondsInteger / 60);
const seconds = secondsInteger - minutes * 60; const seconds = secondsInteger - minutes * 60;
...@@ -196,9 +198,9 @@ export function timeIntervalInWords(intervalInSeconds) { ...@@ -196,9 +198,9 @@ export function timeIntervalInWords(intervalInSeconds) {
text = `${seconds} ${pluralize('second', seconds)}`; text = `${seconds} ${pluralize('second', seconds)}`;
} }
return text; return text;
} };
export function dateInWords(date, abbreviated = false, hideYear = false) { export const dateInWords = (date, abbreviated = false, hideYear = false) => {
if (!date) return date; if (!date) return date;
const month = date.getMonth(); const month = date.getMonth();
...@@ -240,7 +242,7 @@ export function dateInWords(date, abbreviated = false, hideYear = false) { ...@@ -240,7 +242,7 @@ export function dateInWords(date, abbreviated = false, hideYear = false) {
} }
return `${monthName} ${date.getDate()}, ${year}`; return `${monthName} ${date.getDate()}, ${year}`;
} };
/** /**
* Returns month name based on provided date. * Returns month name based on provided date.
...@@ -391,3 +393,83 @@ export const formatTime = milliseconds => { ...@@ -391,3 +393,83 @@ export const formatTime = milliseconds => {
formattedTime += remainingSeconds; formattedTime += remainingSeconds;
return formattedTime; return formattedTime;
}; };
/**
* Formats dates in Pickaday
* @param {String} dateString Date in yyyy-mm-dd format
* @return {Date} UTC format
*/
export const parsePikadayDate = dateString => {
const parts = dateString.split('-');
const year = parseInt(parts[0], 10);
const month = parseInt(parts[1] - 1, 10);
const day = parseInt(parts[2], 10);
return new Date(year, month, day);
};
/**
* Used `onSelect` method in pickaday
* @param {Date} date UTC format
* @return {String} Date formated in yyyy-mm-dd
*/
export const pikadayToString = date => {
const day = pad(date.getDate());
const month = pad(date.getMonth() + 1);
const year = date.getFullYear();
return `${year}-${month}-${day}`;
};
/**
* 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 const parseSeconds = (seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {}) => {
const DAYS_PER_WEEK = daysPerWeek;
const HOURS_PER_DAY = hoursPerDay;
const MINUTES_PER_HOUR = 60;
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,
days: MINUTES_PER_DAY,
hours: MINUTES_PER_HOUR,
minutes: 1,
};
let unorderedMinutes = Math.abs(seconds / MINUTES_PER_HOUR);
return _.mapObject(timePeriodConstraints, minutesPerPeriod => {
const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod);
unorderedMinutes -= periodCount * minutesPerPeriod;
return periodCount;
});
};
/**
* 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.
*/
export const stringifyTime = timeObject => {
const reducedTime = _.reduce(
timeObject,
(memo, unitValue, unitName) => {
const isNonZero = !!unitValue;
return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo;
},
'',
).trim();
return reducedTime.length ? reducedTime : '0m';
};
/**
* 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.
*/
export const abbreviateTime = timeStr =>
timeStr.split(' ').filter(unitStr => unitStr.charAt(0) !== '0')[0];
import _ from 'underscore';
/*
* TODO: Make these methods more configurable (e.g. stringifyTime condensed or
* non-condensed, abbreviateTimelengths)
* */
/*
* 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;
const HOURS_PER_DAY = hoursPerDay;
const MINUTES_PER_HOUR = 60;
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,
days: MINUTES_PER_DAY,
hours: MINUTES_PER_HOUR,
minutes: 1,
};
let unorderedMinutes = Math.abs(seconds / MINUTES_PER_HOUR);
return _.mapObject(timePeriodConstraints, minutesPerPeriod => {
const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod);
unorderedMinutes -= periodCount * minutesPerPeriod;
return periodCount;
});
}
/*
* 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.
*/
export function stringifyTime(timeObject) {
const reducedTime = _.reduce(
timeObject,
(memo, unitValue, unitName) => {
const isNonZero = !!unitValue;
return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo;
},
'',
).trim();
return reducedTime.length ? reducedTime : '0m';
}
/*
* 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.
*/
export function abbreviateTime(timeStr) {
return timeStr.split(' ').filter(unitStr => unitStr.charAt(0) !== '0')[0];
}
import $ from 'jquery'; import $ from 'jquery';
import Pikaday from 'pikaday'; import Pikaday from 'pikaday';
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix'; import { parsePikadayDate, pikadayToString } from './lib/utils/datetime_utility';
// Add datepickers to all `js-access-expiration-date` elements. If those elements are // Add datepickers to all `js-access-expiration-date` elements. If those elements are
// children of an element with the `clearable-input` class, and have a sibling // children of an element with the `clearable-input` class, and have a sibling
......
<script> <script>
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import { abbreviateTime } from '~/lib/utils/pretty_time'; import { abbreviateTime } from '~/lib/utils/datetime_utility';
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
export default { export default {
name: 'TimeTrackingCollapsedState', name: 'TimeTrackingCollapsedState',
components: { components: {
icon, icon,
},
directives: {
tooltip,
},
props: {
showComparisonState: {
type: Boolean,
required: true,
}, },
directives: { showSpentOnlyState: {
tooltip, type: Boolean,
required: true,
}, },
props: { showEstimateOnlyState: {
showComparisonState: { type: Boolean,
type: Boolean, required: true,
required: true,
},
showSpentOnlyState: {
type: Boolean,
required: true,
},
showEstimateOnlyState: {
type: Boolean,
required: true,
},
showNoTimeTrackingState: {
type: Boolean,
required: true,
},
timeSpentHumanReadable: {
type: String,
required: false,
default: '',
},
timeEstimateHumanReadable: {
type: String,
required: false,
default: '',
},
}, },
computed: { showNoTimeTrackingState: {
timeSpent() { type: Boolean,
return this.abbreviateTime(this.timeSpentHumanReadable); required: true,
}, },
timeEstimate() { timeSpentHumanReadable: {
return this.abbreviateTime(this.timeEstimateHumanReadable); type: String,
}, required: false,
divClass() { default: '',
if (this.showComparisonState) { },
return 'compare'; timeEstimateHumanReadable: {
} else if (this.showEstimateOnlyState) { type: String,
return 'estimate-only'; required: false,
} else if (this.showSpentOnlyState) { default: '',
return 'spend-only'; },
} else if (this.showNoTimeTrackingState) { },
return 'no-tracking'; computed: {
} timeSpent() {
return this.abbreviateTime(this.timeSpentHumanReadable);
},
timeEstimate() {
return this.abbreviateTime(this.timeEstimateHumanReadable);
},
divClass() {
if (this.showComparisonState) {
return 'compare';
} else if (this.showEstimateOnlyState) {
return 'estimate-only';
} else if (this.showSpentOnlyState) {
return 'spend-only';
} else if (this.showNoTimeTrackingState) {
return 'no-tracking';
}
return '';
},
spanClass() {
if (this.showComparisonState) {
return ''; return '';
}, } else if (this.showEstimateOnlyState || this.showSpentOnlyState) {
spanClass() { return 'bold';
if (this.showComparisonState) { } else if (this.showNoTimeTrackingState) {
return ''; return 'no-value';
} else if (this.showEstimateOnlyState || this.showSpentOnlyState) { }
return 'bold';
} else if (this.showNoTimeTrackingState) {
return 'no-value';
}
return ''; return '';
}, },
text() { text() {
if (this.showComparisonState) { if (this.showComparisonState) {
return `${this.timeSpent} / ${this.timeEstimate}`; return `${this.timeSpent} / ${this.timeEstimate}`;
} else if (this.showEstimateOnlyState) { } else if (this.showEstimateOnlyState) {
return `-- / ${this.timeEstimate}`; return `-- / ${this.timeEstimate}`;
} else if (this.showSpentOnlyState) { } else if (this.showSpentOnlyState) {
return `${this.timeSpent} / --`; return `${this.timeSpent} / --`;
} else if (this.showNoTimeTrackingState) { } else if (this.showNoTimeTrackingState) {
return 'None'; return 'None';
} }
return ''; return '';
}, },
timeTrackedTooltipText() { timeTrackedTooltipText() {
let title; let title;
if (this.showComparisonState) { if (this.showComparisonState) {
title = __('Time remaining'); title = __('Time remaining');
} else if (this.showEstimateOnlyState) { } else if (this.showEstimateOnlyState) {
title = __('Estimated'); title = __('Estimated');
} else if (this.showSpentOnlyState) { } else if (this.showSpentOnlyState) {
title = __('Time spent'); title = __('Time spent');
} }
return sprintf('%{title}: %{text}', ({ title, text: this.text })); return sprintf('%{title}: %{text}', { title, text: this.text });
}, },
tooltipText() { tooltipText() {
return this.showNoTimeTrackingState ? __('Time tracking') : this.timeTrackedTooltipText; return this.showNoTimeTrackingState ? __('Time tracking') : this.timeTrackedTooltipText;
},
}, },
methods: { },
abbreviateTime(timeStr) { methods: {
return abbreviateTime(timeStr); abbreviateTime(timeStr) {
}, return abbreviateTime(timeStr);
}, },
}; },
};
</script> </script>
<template> <template>
......
<script> <script>
import { parseSeconds, stringifyTime } from '../../../lib/utils/pretty_time'; import { parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility';
import tooltip from '../../../vue_shared/directives/tooltip'; import tooltip from '../../../vue_shared/directives/tooltip';
export default { export default {
......
<script> <script>
import Pikaday from 'pikaday'; import Pikaday from 'pikaday';
import { parsePikadayDate, pikadayToString } from '../../lib/utils/datefix'; import { parsePikadayDate, pikadayToString } from '~/lib/utils/datetime_utility';
export default { export default {
name: 'DatePicker', name: 'DatePicker',
......
import { parsePikadayDate } from '~/lib/utils/datefix'; import { parsePikadayDate } from '~/lib/utils/datetime_utility';
export default class SidebarStore { export default class SidebarStore {
constructor({ constructor({
......
<script> <script>
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import { parseSeconds, stringifyTime } from '~/lib/utils/pretty_time'; import { parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility';
import { VALUE_TYPE, CUSTOM_TYPE } from '../../constants'; import { VALUE_TYPE, CUSTOM_TYPE } from '../../constants';
import DetailsSectionMixin from '../../mixins/details_section_mixin'; import DetailsSectionMixin from '../../mixins/details_section_mixin';
import GeoNodeDetailItem from '../geo_node_detail_item.vue'; import GeoNodeDetailItem from '../geo_node_detail_item.vue';
import SectionRevealButton from './section_reveal_button.vue'; import SectionRevealButton from './section_reveal_button.vue';
export default { export default {
components: { components: {
SectionRevealButton, SectionRevealButton,
GeoNodeDetailItem, GeoNodeDetailItem,
},
mixins: [DetailsSectionMixin],
props: {
nodeDetails: {
type: Object,
required: true,
}, },
mixins: [ },
DetailsSectionMixin, data() {
], return {
props: { showSectionItems: false,
nodeDetails: { nodeDetailItems: [
type: Object, {
required: true, itemTitle: s__('GeoNodes|Sync settings'),
}, itemValue: this.syncSettings(),
}, itemValueType: VALUE_TYPE.CUSTOM,
data() { customType: CUSTOM_TYPE.SYNC,
},
{
itemTitle: s__('GeoNodes|Repositories'),
itemValue: this.nodeDetails.repositories,
itemValueType: VALUE_TYPE.GRAPH,
},
{
itemTitle: s__('GeoNodes|Wikis'),
itemValue: this.nodeDetails.wikis,
itemValueType: VALUE_TYPE.GRAPH,
},
{
itemTitle: s__('GeoNodes|Local LFS objects'),
itemValue: this.nodeDetails.lfs,
itemValueType: VALUE_TYPE.GRAPH,
},
{
itemTitle: s__('GeoNodes|Local attachments'),
itemValue: this.nodeDetails.attachments,
itemValueType: VALUE_TYPE.GRAPH,
},
{
itemTitle: s__('GeoNodes|Local job artifacts'),
itemValue: this.nodeDetails.jobArtifacts,
itemValueType: VALUE_TYPE.GRAPH,
},
{
itemTitle: s__('GeoNodes|Data replication lag'),
itemValue: this.dbReplicationLag(),
itemValueType: VALUE_TYPE.PLAIN,
},
{
itemTitle: s__('GeoNodes|Last event ID seen from primary'),
itemValue: this.lastEventStatus(),
itemValueType: VALUE_TYPE.CUSTOM,
customType: CUSTOM_TYPE.EVENT,
},
{
itemTitle: s__('GeoNodes|Last event ID processed by cursor'),
itemValue: this.cursorLastEventStatus(),
itemValueType: VALUE_TYPE.CUSTOM,
customType: CUSTOM_TYPE.EVENT,
eventTypeLogStatus: true,
},
],
};
},
methods: {
syncSettings() {
return { return {
showSectionItems: false, syncStatusUnavailable: this.nodeDetails.syncStatusUnavailable,
nodeDetailItems: [ selectiveSyncType: this.nodeDetails.selectiveSyncType,
{ lastEvent: this.nodeDetails.lastEvent,
itemTitle: s__('GeoNodes|Sync settings'), cursorLastEvent: this.nodeDetails.cursorLastEvent,
itemValue: this.syncSettings(),
itemValueType: VALUE_TYPE.CUSTOM,
customType: CUSTOM_TYPE.SYNC,
},
{
itemTitle: s__('GeoNodes|Repositories'),
itemValue: this.nodeDetails.repositories,
itemValueType: VALUE_TYPE.GRAPH,
},
{
itemTitle: s__('GeoNodes|Wikis'),
itemValue: this.nodeDetails.wikis,
itemValueType: VALUE_TYPE.GRAPH,
},
{
itemTitle: s__('GeoNodes|Local LFS objects'),
itemValue: this.nodeDetails.lfs,
itemValueType: VALUE_TYPE.GRAPH,
},
{
itemTitle: s__('GeoNodes|Local attachments'),
itemValue: this.nodeDetails.attachments,
itemValueType: VALUE_TYPE.GRAPH,
},
{
itemTitle: s__('GeoNodes|Local job artifacts'),
itemValue: this.nodeDetails.jobArtifacts,
itemValueType: VALUE_TYPE.GRAPH,
},
{
itemTitle: s__('GeoNodes|Data replication lag'),
itemValue: this.dbReplicationLag(),
itemValueType: VALUE_TYPE.PLAIN,
},
{
itemTitle: s__('GeoNodes|Last event ID seen from primary'),
itemValue: this.lastEventStatus(),
itemValueType: VALUE_TYPE.CUSTOM,
customType: CUSTOM_TYPE.EVENT,
},
{
itemTitle: s__('GeoNodes|Last event ID processed by cursor'),
itemValue: this.cursorLastEventStatus(),
itemValueType: VALUE_TYPE.CUSTOM,
customType: CUSTOM_TYPE.EVENT,
eventTypeLogStatus: true,
},
],
}; };
}, },
methods: { dbReplicationLag() {
syncSettings() { // Replication lag can be nil if the secondary isn't actually streaming
return { if (this.nodeDetails.dbReplicationLag !== null && this.nodeDetails.dbReplicationLag >= 0) {
syncStatusUnavailable: this.nodeDetails.syncStatusUnavailable, const parsedTime = parseSeconds(this.nodeDetails.dbReplicationLag, {
selectiveSyncType: this.nodeDetails.selectiveSyncType, hoursPerDay: 24,
lastEvent: this.nodeDetails.lastEvent, daysPerWeek: 7,
cursorLastEvent: this.nodeDetails.cursorLastEvent, });
};
},
dbReplicationLag() {
// Replication lag can be nil if the secondary isn't actually streaming
if (this.nodeDetails.dbReplicationLag !== null &&
this.nodeDetails.dbReplicationLag >= 0) {
const parsedTime = parseSeconds(this.nodeDetails.dbReplicationLag, {
hoursPerDay: 24,
daysPerWeek: 7,
});
return stringifyTime(parsedTime); return stringifyTime(parsedTime);
} }
return __('Unknown'); return __('Unknown');
}, },
lastEventStatus() { lastEventStatus() {
return { return {
eventId: this.nodeDetails.lastEvent.id, eventId: this.nodeDetails.lastEvent.id,
eventTimeStamp: this.nodeDetails.lastEvent.timeStamp, eventTimeStamp: this.nodeDetails.lastEvent.timeStamp,
}; };
}, },
cursorLastEventStatus() { cursorLastEventStatus() {
return { return {
eventId: this.nodeDetails.cursorLastEvent.id, eventId: this.nodeDetails.cursorLastEvent.id,
eventTimeStamp: this.nodeDetails.cursorLastEvent.timeStamp, eventTimeStamp: this.nodeDetails.cursorLastEvent.timeStamp,
}; };
}, },
handleSectionToggle(toggleState) { handleSectionToggle(toggleState) {
this.showSectionItems = toggleState; this.showSectionItems = toggleState;
},
}, },
}; },
};
</script> </script>
<template> <template>
......
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { parsePikadayDate } from '~/lib/utils/datefix'; import { parsePikadayDate } from '~/lib/utils/datetime_utility';
import { PRESET_TYPES } from '../constants'; import { PRESET_TYPES } from '../constants';
......
...@@ -192,3 +192,163 @@ describe('formatTime', () => { ...@@ -192,3 +192,163 @@ describe('formatTime', () => {
}); });
}); });
}); });
describe('datefix', () => {
describe('pad', () => {
it('should add a 0 when length is smaller than 2', () => {
expect(datetimeUtility.pad(2)).toEqual('02');
});
it('should not add a zero when lenght matches the default', () => {
expect(datetimeUtility.pad(12)).toEqual('12');
});
it('should add a 0 when lenght is smaller than the provided', () => {
expect(datetimeUtility.pad(12, 3)).toEqual('012');
});
});
describe('parsePikadayDate', () => {
// removed because of https://gitlab.com/gitlab-org/gitlab-ce/issues/39834
});
describe('pikadayToString', () => {
it('should format a UTC date into yyyy-mm-dd format', () => {
expect(datetimeUtility.pikadayToString(new Date('2020-01-29:00:00'))).toEqual('2020-01-29');
});
});
});
describe('prettyTime methods', () => {
const assertTimeUnits = (obj, minutes, hours, days, weeks) => {
expect(obj.minutes).toBe(minutes);
expect(obj.hours).toBe(hours);
expect(obj.days).toBe(days);
expect(obj.weeks).toBe(weeks);
};
describe('parseSeconds', () => {
it('should correctly parse a negative value', () => {
const zeroSeconds = datetimeUtility.parseSeconds(-1000);
assertTimeUnits(zeroSeconds, 16, 0, 0, 0);
});
it('should correctly parse a zero value', () => {
const zeroSeconds = datetimeUtility.parseSeconds(0);
assertTimeUnits(zeroSeconds, 0, 0, 0, 0);
});
it('should correctly parse a small non-zero second values', () => {
const subOneMinute = datetimeUtility.parseSeconds(10);
const aboveOneMinute = datetimeUtility.parseSeconds(100);
const manyMinutes = datetimeUtility.parseSeconds(1000);
assertTimeUnits(subOneMinute, 0, 0, 0, 0);
assertTimeUnits(aboveOneMinute, 1, 0, 0, 0);
assertTimeUnits(manyMinutes, 16, 0, 0, 0);
});
it('should correctly parse large second values', () => {
const aboveOneHour = datetimeUtility.parseSeconds(4800);
const aboveOneDay = datetimeUtility.parseSeconds(110000);
const aboveOneWeek = datetimeUtility.parseSeconds(25000000);
assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
assertTimeUnits(aboveOneDay, 33, 6, 3, 0);
assertTimeUnits(aboveOneWeek, 26, 0, 3, 173);
});
it('should correctly accept a custom param for hoursPerDay', () => {
const config = { hoursPerDay: 24 };
const aboveOneHour = datetimeUtility.parseSeconds(4800, config);
const aboveOneDay = datetimeUtility.parseSeconds(110000, config);
const aboveOneWeek = datetimeUtility.parseSeconds(25000000, config);
assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
assertTimeUnits(aboveOneDay, 33, 6, 1, 0);
assertTimeUnits(aboveOneWeek, 26, 8, 4, 57);
});
it('should correctly accept a custom param for daysPerWeek', () => {
const config = { daysPerWeek: 7 };
const aboveOneHour = datetimeUtility.parseSeconds(4800, config);
const aboveOneDay = datetimeUtility.parseSeconds(110000, config);
const aboveOneWeek = datetimeUtility.parseSeconds(25000000, config);
assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
assertTimeUnits(aboveOneDay, 33, 6, 3, 0);
assertTimeUnits(aboveOneWeek, 26, 0, 0, 124);
});
it('should correctly accept custom params for daysPerWeek and hoursPerDay', () => {
const config = { daysPerWeek: 55, hoursPerDay: 14 };
const aboveOneHour = datetimeUtility.parseSeconds(4800, config);
const aboveOneDay = datetimeUtility.parseSeconds(110000, config);
const aboveOneWeek = datetimeUtility.parseSeconds(25000000, config);
assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
assertTimeUnits(aboveOneDay, 33, 2, 2, 0);
assertTimeUnits(aboveOneWeek, 26, 0, 1, 9);
});
});
describe('stringifyTime', () => {
it('should stringify values with all non-zero units', () => {
const timeObject = {
weeks: 1,
days: 4,
hours: 7,
minutes: 20,
};
const timeString = datetimeUtility.stringifyTime(timeObject);
expect(timeString).toBe('1w 4d 7h 20m');
});
it('should stringify values with some non-zero units', () => {
const timeObject = {
weeks: 0,
days: 4,
hours: 0,
minutes: 20,
};
const timeString = datetimeUtility.stringifyTime(timeObject);
expect(timeString).toBe('4d 20m');
});
it('should stringify values with no non-zero units', () => {
const timeObject = {
weeks: 0,
days: 0,
hours: 0,
minutes: 0,
};
const timeString = datetimeUtility.stringifyTime(timeObject);
expect(timeString).toBe('0m');
});
});
describe('abbreviateTime', () => {
it('should abbreviate stringified times for weeks', () => {
const fullTimeString = '1w 3d 4h 5m';
expect(datetimeUtility.abbreviateTime(fullTimeString)).toBe('1w');
});
it('should abbreviate stringified times for non-weeks', () => {
const fullTimeString = '0w 3d 4h 5m';
expect(datetimeUtility.abbreviateTime(fullTimeString)).toBe('3d');
});
});
});
import { pad, pikadayToString } from '~/lib/utils/datefix';
describe('datefix', () => {
describe('pad', () => {
it('should add a 0 when length is smaller than 2', () => {
expect(pad(2)).toEqual('02');
});
it('should not add a zero when lenght matches the default', () => {
expect(pad(12)).toEqual('12');
});
it('should add a 0 when lenght is smaller than the provided', () => {
expect(pad(12, 3)).toEqual('012');
});
});
describe('parsePikadayDate', () => {
// removed because of https://gitlab.com/gitlab-org/gitlab-ce/issues/39834
});
describe('pikadayToString', () => {
it('should format a UTC date into yyyy-mm-dd format', () => {
expect(pikadayToString(new Date('2020-01-29:00:00'))).toEqual('2020-01-29');
});
});
});
import { parseSeconds, abbreviateTime, stringifyTime } from '~/lib/utils/pretty_time';
function assertTimeUnits(obj, minutes, hours, days, weeks) {
expect(obj.minutes).toBe(minutes);
expect(obj.hours).toBe(hours);
expect(obj.days).toBe(days);
expect(obj.weeks).toBe(weeks);
}
describe('prettyTime methods', () => {
describe('parseSeconds', () => {
it('should correctly parse a negative value', () => {
const zeroSeconds = parseSeconds(-1000);
assertTimeUnits(zeroSeconds, 16, 0, 0, 0);
});
it('should correctly parse a zero value', () => {
const zeroSeconds = parseSeconds(0);
assertTimeUnits(zeroSeconds, 0, 0, 0, 0);
});
it('should correctly parse a small non-zero second values', () => {
const subOneMinute = parseSeconds(10);
const aboveOneMinute = parseSeconds(100);
const manyMinutes = parseSeconds(1000);
assertTimeUnits(subOneMinute, 0, 0, 0, 0);
assertTimeUnits(aboveOneMinute, 1, 0, 0, 0);
assertTimeUnits(manyMinutes, 16, 0, 0, 0);
});
it('should correctly parse large second values', () => {
const aboveOneHour = parseSeconds(4800);
const aboveOneDay = parseSeconds(110000);
const aboveOneWeek = parseSeconds(25000000);
assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
assertTimeUnits(aboveOneDay, 33, 6, 3, 0);
assertTimeUnits(aboveOneWeek, 26, 0, 3, 173);
});
it('should correctly accept a custom param for hoursPerDay', () => {
const config = { hoursPerDay: 24 };
const aboveOneHour = parseSeconds(4800, config);
const aboveOneDay = parseSeconds(110000, config);
const aboveOneWeek = parseSeconds(25000000, config);
assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
assertTimeUnits(aboveOneDay, 33, 6, 1, 0);
assertTimeUnits(aboveOneWeek, 26, 8, 4, 57);
});
it('should correctly accept a custom param for daysPerWeek', () => {
const config = { daysPerWeek: 7 };
const aboveOneHour = parseSeconds(4800, config);
const aboveOneDay = parseSeconds(110000, config);
const aboveOneWeek = parseSeconds(25000000, config);
assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
assertTimeUnits(aboveOneDay, 33, 6, 3, 0);
assertTimeUnits(aboveOneWeek, 26, 0, 0, 124);
});
it('should correctly accept custom params for daysPerWeek and hoursPerDay', () => {
const config = { daysPerWeek: 55, hoursPerDay: 14 };
const aboveOneHour = parseSeconds(4800, config);
const aboveOneDay = parseSeconds(110000, config);
const aboveOneWeek = parseSeconds(25000000, config);
assertTimeUnits(aboveOneHour, 20, 1, 0, 0);
assertTimeUnits(aboveOneDay, 33, 2, 2, 0);
assertTimeUnits(aboveOneWeek, 26, 0, 1, 9);
});
});
describe('stringifyTime', () => {
it('should stringify values with all non-zero units', () => {
const timeObject = {
weeks: 1,
days: 4,
hours: 7,
minutes: 20,
};
const timeString = stringifyTime(timeObject);
expect(timeString).toBe('1w 4d 7h 20m');
});
it('should stringify values with some non-zero units', () => {
const timeObject = {
weeks: 0,
days: 4,
hours: 0,
minutes: 20,
};
const timeString = stringifyTime(timeObject);
expect(timeString).toBe('4d 20m');
});
it('should stringify values with no non-zero units', () => {
const timeObject = {
weeks: 0,
days: 0,
hours: 0,
minutes: 0,
};
const timeString = stringifyTime(timeObject);
expect(timeString).toBe('0m');
});
});
describe('abbreviateTime', () => {
it('should abbreviate stringified times for weeks', () => {
const fullTimeString = '1w 3d 4h 5m';
expect(abbreviateTime(fullTimeString)).toBe('1w');
});
it('should abbreviate stringified times for non-weeks', () => {
const fullTimeString = '0w 3d 4h 5m';
expect(abbreviateTime(fullTimeString)).toBe('3d');
});
});
});
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