summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2018-02-07 16:52:10 +0000
committerFilipa Lacerda <filipa@gitlab.com>2018-02-07 16:52:10 +0000
commitbb6895ec6cb60b8db6a615b12628685a088d1f1d (patch)
tree9ea78b03e1b04a7b8ebe5ecb19e862da83c4cb7d
parent206a15d70e50e9873db316f83421a50538e7849e (diff)
parent3976368808d3e105100291ac00f2f6bc5835dd4e (diff)
downloadgitlab-ce-bb6895ec6cb60b8db6a615b12628685a088d1f1d.tar.gz
Merge branch '3559-ce-port-group-roadmap' into 'master'
CE Port for Group Roadmap changes See merge request gitlab-org/gitlab-ce!16908
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js21
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js123
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js7
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/views/layouts/_page.html.haml2
-rw-r--r--spec/javascripts/datetime_utility_spec.js64
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js29
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js6
8 files changed, 250 insertions, 3 deletions
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 5811d059e0b..7d2cf4b634f 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -1,5 +1,6 @@
import axios from './axios_utils';
import { getLocationHash } from './url_utility';
+import { convertToCamelCase } from './text_utility';
export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index];
@@ -395,6 +396,26 @@ export const spriteIcon = (icon, className = '') => {
return `<svg ${classAttribute}><use xlink:href="${gon.sprite_icons}#${icon}" /></svg>`;
};
+/**
+ * This method takes in object with snake_case property names
+ * and returns new object with camelCase property names
+ *
+ * Reasoning for this method is to ensure consistent property
+ * naming conventions across JS code.
+ */
+export const convertObjectPropsToCamelCase = (obj = {}) => {
+ if (obj === null) {
+ return {};
+ }
+
+ return Object.keys(obj).reduce((acc, prop) => {
+ const result = acc;
+
+ result[convertToCamelCase(prop)] = obj[prop];
+ return acc;
+ }, {});
+};
+
export const imagePath = imgUrl => `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/${imgUrl}`;
window.gl = window.gl || {};
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 1fa6715180e..d6cccbef42b 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -10,6 +10,20 @@ window.timeago = timeago;
window.dateFormat = dateFormat;
/**
+ * Returns i18n month names array.
+ * If `abbreviated` is provided, returns abbreviated
+ * name.
+ *
+ * @param {Boolean} abbreviated
+ */
+const getMonthNames = (abbreviated) => {
+ if (abbreviated) {
+ return [s__('Jan'), s__('Feb'), s__('Mar'), s__('Apr'), s__('May'), s__('Jun'), s__('Jul'), s__('Aug'), s__('Sep'), s__('Oct'), s__('Nov'), s__('Dec')];
+ }
+ return [s__('January'), s__('February'), s__('March'), s__('April'), s__('May'), s__('June'), s__('July'), s__('August'), s__('September'), s__('October'), s__('November'), s__('December')];
+};
+
+/**
* Given a date object returns the day of the week in English
* @param {date} date
* @returns {String}
@@ -143,7 +157,6 @@ export const getDayDifference = (a, b) => {
* @param {Number} seconds
* @return {String}
*/
-// eslint-disable-next-line import/prefer-default-export
export function timeIntervalInWords(intervalInSeconds) {
const secondsInteger = parseInt(intervalInSeconds, 10);
const minutes = Math.floor(secondsInteger / 60);
@@ -158,7 +171,7 @@ export function timeIntervalInWords(intervalInSeconds) {
return text;
}
-export function dateInWords(date, abbreviated = false) {
+export function dateInWords(date, abbreviated = false, hideYear = false) {
if (!date) return date;
const month = date.getMonth();
@@ -169,9 +182,115 @@ export function dateInWords(date, abbreviated = false) {
const monthName = abbreviated ? monthNamesAbbr[month] : monthNames[month];
+ if (hideYear) {
+ return `${monthName} ${date.getDate()}`;
+ }
+
return `${monthName} ${date.getDate()}, ${year}`;
}
+/**
+ * Returns month name based on provided date.
+ *
+ * @param {Date} date
+ * @param {Boolean} abbreviated
+ */
+export const monthInWords = (date, abbreviated = false) => {
+ if (!date) {
+ return '';
+ }
+
+ return getMonthNames(abbreviated)[date.getMonth()];
+};
+
+/**
+ * Returns number of days in a month for provided date.
+ * courtesy: https://stacko(verflow.com/a/1185804/414749
+ *
+ * @param {Date} date
+ */
+export const totalDaysInMonth = (date) => {
+ if (!date) {
+ return 0;
+ }
+ return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
+};
+
+/**
+ * Returns list of Dates referring to Sundays of the month
+ * based on provided date
+ *
+ * @param {Date} date
+ */
+export const getSundays = (date) => {
+ if (!date) {
+ return [];
+ }
+
+ const daysToSunday = ['Saturday', 'Friday', 'Thursday', 'Wednesday', 'Tuesday', 'Monday', 'Sunday'];
+
+ const month = date.getMonth();
+ const year = date.getFullYear();
+ const sundays = [];
+ const dateOfMonth = new Date(year, month, 1);
+
+ while (dateOfMonth.getMonth() === month) {
+ const dayName = getDayName(dateOfMonth);
+ if (dayName === 'Sunday') {
+ sundays.push(new Date(dateOfMonth.getTime()));
+ }
+
+ const daysUntilNextSunday = daysToSunday.indexOf(dayName) + 1;
+ dateOfMonth.setDate(dateOfMonth.getDate() + daysUntilNextSunday);
+ }
+
+ return sundays;
+};
+
+/**
+ * Returns list of Dates representing a timeframe of Months from month of provided date (inclusive)
+ * up to provided length
+ *
+ * For eg;
+ * If current month is January 2018 and `length` provided is `6`
+ * Then this method will return list of Date objects as follows;
+ *
+ * [ October 2017, November 2017, December 2017, January 2018, February 2018, March 2018 ]
+ *
+ * If current month is March 2018 and `length` provided is `3`
+ * Then this method will return list of Date objects as follows;
+ *
+ * [ February 2018, March 2018, April 2018 ]
+ *
+ * @param {Number} length
+ * @param {Date} date
+ */
+export const getTimeframeWindow = (length, date) => {
+ if (!length) {
+ return [];
+ }
+
+ const currentDate = date instanceof Date ? date : new Date();
+ const currentMonthIndex = Math.floor(length / 2);
+ const timeframe = [];
+
+ // Move date object backward to the first month of timeframe
+ currentDate.setDate(1);
+ currentDate.setMonth(currentDate.getMonth() - currentMonthIndex);
+
+ // Iterate and update date for the size of length
+ // and push date reference to timeframe list
+ for (let i = 0; i < length; i += 1) {
+ timeframe.push(new Date(currentDate.getTime()));
+ currentDate.setMonth(currentDate.getMonth() + 1);
+ }
+
+ // Change date of last timeframe item to last date of the month
+ timeframe[length - 1].setDate(totalDaysInMonth(timeframe[length - 1]));
+
+ return timeframe;
+};
+
window.gl = window.gl || {};
window.gl.utils = {
...(window.gl.utils || {}),
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 62d80c4a649..94d03621bff 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -73,3 +73,10 @@ export function capitalizeFirstCharacter(text) {
* @returns {String}
*/
export const stripHtml = (string, replace = '') => string.replace(/<[^>]*>/g, replace);
+
+/**
+ * Converts snake_case string to camelCase
+ *
+ * @param {*} string
+ */
+export const convertToCamelCase = string => string.replace(/(_\w)/g, s => s[1].toUpperCase());
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 0d21a9f5f77..25ee081ea9c 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -214,6 +214,7 @@ $tooltip-font-size: 12px;
* Padding
*/
$gl-padding: 16px;
+$gl-padding-8: 8px;
$gl-col-padding: 15px;
$gl-btn-padding: 10px;
$gl-input-padding: 10px;
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index eba9cd253bb..f0963cf9da8 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,7 +1,7 @@
.layout-page{ class: page_with_sidebar_class }
- if defined?(nav) && nav
= render "layouts/nav/sidebar/#{nav}"
- .content-wrapper
+ .content-wrapper{ class: "#{@content_wrapper_class}" }
= render 'shared/outdated_browser'
.mobile-overlay
.alert-wrapper
diff --git a/spec/javascripts/datetime_utility_spec.js b/spec/javascripts/datetime_utility_spec.js
index 2e5b65f5610..a8d09202154 100644
--- a/spec/javascripts/datetime_utility_spec.js
+++ b/spec/javascripts/datetime_utility_spec.js
@@ -105,4 +105,68 @@ describe('dateInWords', () => {
it('should return abbreviated month name', () => {
expect(datetimeUtility.dateInWords(date, true)).toEqual('Jul 1, 2016');
});
+
+ it('should return date in words without year', () => {
+ expect(datetimeUtility.dateInWords(date, true, true)).toEqual('Jul 1');
+ });
+});
+
+describe('monthInWords', () => {
+ const date = new Date('2017-01-20');
+
+ it('returns month name from provided date', () => {
+ expect(datetimeUtility.monthInWords(date)).toBe('January');
+ });
+
+ it('returns abbreviated month name from provided date', () => {
+ expect(datetimeUtility.monthInWords(date, true)).toBe('Jan');
+ });
+});
+
+describe('totalDaysInMonth', () => {
+ it('returns number of days in a month for given date', () => {
+ // 1st Feb, 2016 (leap year)
+ expect(datetimeUtility.totalDaysInMonth(new Date(2016, 1, 1))).toBe(29);
+
+ // 1st Feb, 2017
+ expect(datetimeUtility.totalDaysInMonth(new Date(2017, 1, 1))).toBe(28);
+
+ // 1st Jan, 2017
+ expect(datetimeUtility.totalDaysInMonth(new Date(2017, 0, 1))).toBe(31);
+ });
+});
+
+describe('getSundays', () => {
+ it('returns array of dates representing all Sundays of the month', () => {
+ // December, 2017 (it has 5 Sundays)
+ const dateOfSundays = [3, 10, 17, 24, 31];
+ const sundays = datetimeUtility.getSundays(new Date(2017, 11, 1));
+
+ expect(sundays.length).toBe(5);
+ sundays.forEach((sunday, index) => {
+ expect(sunday.getDate()).toBe(dateOfSundays[index]);
+ });
+ });
+});
+
+describe('getTimeframeWindow', () => {
+ it('returns array of dates representing a timeframe based on provided length and date', () => {
+ const date = new Date(2018, 0, 1);
+ const mockTimeframe = [
+ new Date(2017, 9, 1),
+ new Date(2017, 10, 1),
+ new Date(2017, 11, 1),
+ new Date(2018, 0, 1),
+ new Date(2018, 1, 1),
+ new Date(2018, 2, 31),
+ ];
+ const timeframe = datetimeUtility.getTimeframeWindow(6, date);
+
+ expect(timeframe.length).toBe(6);
+ timeframe.forEach((timeframeItem, index) => {
+ expect(timeframeItem.getFullYear() === mockTimeframe[index].getFullYear()).toBeTruthy();
+ expect(timeframeItem.getMonth() === mockTimeframe[index].getMonth()).toBeTruthy();
+ expect(timeframeItem.getDate() === mockTimeframe[index].getDate()).toBeTruthy();
+ });
+ });
});
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 80430011aed..49799c31995 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -480,4 +480,33 @@ describe('common_utils', () => {
expect(commonUtils.spriteIcon('test', 'fa fa-test')).toEqual('<svg class="fa fa-test"><use xlink:href="icons.svg#test" /></svg>');
});
});
+
+ describe('convertObjectPropsToCamelCase', () => {
+ it('returns new object with camelCase property names by converting object with snake_case names', () => {
+ const snakeRegEx = /(_\w)/g;
+ const mockObj = {
+ id: 1,
+ group_name: 'GitLab.org',
+ absolute_web_url: 'https://gitlab.com/gitlab-org/',
+ };
+ const mappings = {
+ id: 'id',
+ groupName: 'group_name',
+ absoluteWebUrl: 'absolute_web_url',
+ };
+
+ const convertedObj = commonUtils.convertObjectPropsToCamelCase(mockObj);
+
+ Object.keys(convertedObj).forEach((prop) => {
+ expect(snakeRegEx.test(prop)).toBeFalsy();
+ expect(convertedObj[prop]).toBe(mockObj[mappings[prop]]);
+ });
+ });
+
+ it('return empty object if method is called with null or undefined', () => {
+ expect(Object.keys(commonUtils.convertObjectPropsToCamelCase(null)).length).toBe(0);
+ expect(Object.keys(commonUtils.convertObjectPropsToCamelCase()).length).toBe(0);
+ expect(Object.keys(commonUtils.convertObjectPropsToCamelCase({})).length).toBe(0);
+ });
+ });
});
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js
index 69a23d7b2f3..e57a55fa71a 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js
+++ b/spec/javascripts/lib/utils/text_utility_spec.js
@@ -72,4 +72,10 @@ describe('text_utility', () => {
expect(textUtils.stripHtml('This is a text with <p>html</p>.', ' ')).toEqual('This is a text with html .');
});
});
+
+ describe('convertToCamelCase', () => {
+ it('converts snake_case string to camelCase string', () => {
+ expect(textUtils.convertToCamelCase('snake_case')).toBe('snakeCase');
+ });
+ });
});