Commit f47c768f authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 0f8c2334
...@@ -210,6 +210,9 @@ export default { ...@@ -210,6 +210,9 @@ export default {
:text="diffFile.file_path" :text="diffFile.file_path"
:gfm="gfmCopyText" :gfm="gfmCopyText"
css-class="btn-default btn-transparent btn-clipboard" css-class="btn-default btn-transparent btn-clipboard"
data-track-event="click_copy_file_button"
data-track-label="diff_copy_file_path_button"
data-track-property="diff_copy_file"
/> />
<small v-if="isModeChanged" ref="fileMode" class="mr-1"> <small v-if="isModeChanged" ref="fileMode" class="mr-1">
...@@ -233,6 +236,9 @@ export default { ...@@ -233,6 +236,9 @@ export default {
:class="{ active: diffHasExpandedDiscussions(diffFile) }" :class="{ active: diffHasExpandedDiscussions(diffFile) }"
class="js-btn-vue-toggle-comments btn" class="js-btn-vue-toggle-comments btn"
data-qa-selector="toggle_comments_button" data-qa-selector="toggle_comments_button"
data-track-event="click_toggle_comments_button"
data-track-label="diff_toggle_comments_button"
data-track-property="diff_toggle_comments"
type="button" type="button"
@click="toggleFileDiscussionWrappers(diffFile)" @click="toggleFileDiscussionWrappers(diffFile)"
> >
...@@ -245,6 +251,9 @@ export default { ...@@ -245,6 +251,9 @@ export default {
:can-current-user-fork="canCurrentUserFork" :can-current-user-fork="canCurrentUserFork"
:edit-path="diffFile.edit_path" :edit-path="diffFile.edit_path"
:can-modify-blob="diffFile.can_modify_blob" :can-modify-blob="diffFile.can_modify_blob"
data-track-event="click_toggle_edit_button"
data-track-label="diff_toggle_edit_button"
data-track-property="diff_toggle_edit"
@showForkMessage="showForkMessage" @showForkMessage="showForkMessage"
/> />
</template> </template>
...@@ -263,6 +272,9 @@ export default { ...@@ -263,6 +272,9 @@ export default {
v-gl-tooltip.hover v-gl-tooltip.hover
:title="expandDiffToFullFileTitle" :title="expandDiffToFullFileTitle"
class="expand-file" class="expand-file"
data-track-event="click_toggle_view_full_button"
data-track-label="diff_toggle_view_full_button"
data-track-property="diff_toggle_view_full"
@click="toggleFullDiff(diffFile.file_path)" @click="toggleFullDiff(diffFile.file_path)"
> >
<gl-loading-icon v-if="diffFile.isLoadingFullFile" color="dark" inline /> <gl-loading-icon v-if="diffFile.isLoadingFullFile" color="dark" inline />
...@@ -275,6 +287,9 @@ export default { ...@@ -275,6 +287,9 @@ export default {
:href="diffFile.view_path" :href="diffFile.view_path"
target="blank" target="blank"
class="view-file" class="view-file"
data-track-event="click_toggle_view_sha_button"
data-track-label="diff_toggle_view_sha_button"
data-track-property="diff_toggle_view_sha"
:title="viewFileButtonText" :title="viewFileButtonText"
> >
<icon name="doc-text" /> <icon name="doc-text" />
...@@ -288,6 +303,9 @@ export default { ...@@ -288,6 +303,9 @@ export default {
:title="`View on ${diffFile.formatted_external_url}`" :title="`View on ${diffFile.formatted_external_url}`"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
data-track-event="click_toggle_external_button"
data-track-label="diff_toggle_external_button"
data-track-property="diff_toggle_external"
class="btn btn-file-option" class="btn btn-file-option"
> >
<icon name="external-link" /> <icon name="external-link" />
......
...@@ -27,10 +27,12 @@ import GroupEmptyState from './group_empty_state.vue'; ...@@ -27,10 +27,12 @@ import GroupEmptyState from './group_empty_state.vue';
import DashboardsDropdown from './dashboards_dropdown.vue'; import DashboardsDropdown from './dashboards_dropdown.vue';
import TrackEventDirective from '~/vue_shared/directives/track_event'; import TrackEventDirective from '~/vue_shared/directives/track_event';
import { getTimeDiff, getAddMetricTrackingOptions } from '../utils'; import { getAddMetricTrackingOptions } from '../utils';
import { metricStates } from '../constants'; import { getTimeRange } from './date_time_picker/date_time_picker_lib';
const defaultTimeDiff = getTimeDiff(); import { datePickerTimeWindows, metricStates } from '../constants';
const defaultTimeDiff = getTimeRange();
export default { export default {
components: { components: {
...@@ -191,6 +193,7 @@ export default { ...@@ -191,6 +193,7 @@ export default {
startDate: getParameterValues('start')[0] || defaultTimeDiff.start, startDate: getParameterValues('start')[0] || defaultTimeDiff.start,
endDate: getParameterValues('end')[0] || defaultTimeDiff.end, endDate: getParameterValues('end')[0] || defaultTimeDiff.end,
hasValidDates: true, hasValidDates: true,
datePickerTimeWindows,
isRearrangingPanels: false, isRearrangingPanels: false,
}; };
}, },
...@@ -426,6 +429,7 @@ export default { ...@@ -426,6 +429,7 @@ export default {
<date-time-picker <date-time-picker
:start="startDate" :start="startDate"
:end="endDate" :end="endDate"
:time-windows="datePickerTimeWindows"
@apply="onDateTimePickerApply" @apply="onDateTimePickerApply"
@invalid="onDateTimePickerInvalid" @invalid="onDateTimePickerInvalid"
/> />
......
<script> <script>
import { GlButton, GlDropdown, GlDropdownItem, GlFormGroup } from '@gitlab/ui'; import { GlButton, GlDropdown, GlDropdownItem, GlFormGroup } from '@gitlab/ui';
import { s__, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import DateTimePickerInput from './date_time_picker_input.vue'; import DateTimePickerInput from './date_time_picker_input.vue';
import { import {
getTimeDiff, defaultTimeWindows,
isValidDate, isValidDate,
getTimeWindow, getTimeRange,
getTimeWindowKey,
stringToISODate, stringToISODate,
ISODateToString, ISODateToString,
truncateZerosInDateTime, truncateZerosInDateTime,
isDateTimePickerInputValid, isDateTimePickerInputValid,
} from '~/monitoring/utils'; } from './date_time_picker_lib';
import { timeWindows } from '~/monitoring/constants';
const events = { const events = {
apply: 'apply', apply: 'apply',
...@@ -41,7 +40,7 @@ export default { ...@@ -41,7 +40,7 @@ export default {
timeWindows: { timeWindows: {
type: Object, type: Object,
required: false, required: false,
default: () => timeWindows, default: () => defaultTimeWindows,
}, },
}, },
data() { data() {
...@@ -81,11 +80,11 @@ export default { ...@@ -81,11 +80,11 @@ export default {
}, },
timeWindowText() { timeWindowText() {
const timeWindow = getTimeWindow({ start: this.start, end: this.end }); const timeWindow = getTimeWindowKey({ start: this.start, end: this.end }, this.timeWindows);
if (timeWindow) { if (timeWindow) {
return this.timeWindows[timeWindow]; return this.timeWindows[timeWindow].label;
} else if (isValidDate(this.start) && isValidDate(this.end)) { } else if (isValidDate(this.start) && isValidDate(this.end)) {
return sprintf(s__('%{start} to %{end}'), { return sprintf(__('%{start} to %{end}'), {
start: this.formatDate(this.start), start: this.formatDate(this.start),
end: this.formatDate(this.end), end: this.formatDate(this.end),
}); });
...@@ -104,7 +103,7 @@ export default { ...@@ -104,7 +103,7 @@ export default {
return truncateZerosInDateTime(ISODateToString(date)); return truncateZerosInDateTime(ISODateToString(date));
}, },
setTimeWindow(key) { setTimeWindow(key) {
const { start, end } = getTimeDiff(key); const { start, end } = getTimeRange(key, this.timeWindows);
this.startDate = start; this.startDate = start;
this.endDate = end; this.endDate = end;
...@@ -161,18 +160,18 @@ export default { ...@@ -161,18 +160,18 @@ export default {
class="col-md-4 p-0 m-0" class="col-md-4 p-0 m-0"
> >
<gl-dropdown-item <gl-dropdown-item
v-for="(value, key) in timeWindows" v-for="(timeWindow, key) in timeWindows"
:key="key" :key="key"
:active="value === timeWindowText" :active="timeWindow.label === timeWindowText"
active-class="active" active-class="active"
@click="setTimeWindow(key)" @click="setTimeWindow(key)"
> >
<icon <icon
name="mobile-issue-close" name="mobile-issue-close"
class="align-bottom" class="align-bottom"
:class="{ invisible: value !== timeWindowText }" :class="{ invisible: timeWindow.label !== timeWindowText }"
/> />
{{ value }} {{ timeWindow.label }}
</gl-dropdown-item> </gl-dropdown-item>
</gl-form-group> </gl-form-group>
</div> </div>
......
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import { GlFormGroup, GlFormInput } from '@gitlab/ui'; import { GlFormGroup, GlFormInput } from '@gitlab/ui';
import { s__, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import { dateFormats } from '~/monitoring/constants'; import { dateFormats } from './date_time_picker_lib';
const inputGroupText = { const inputGroupText = {
invalidFeedback: sprintf(s__('Format: %{dateFormat}'), { invalidFeedback: sprintf(__('Format: %{dateFormat}'), {
dateFormat: dateFormats.dateTimePicker.format, dateFormat: dateFormats.stringDate,
}), }),
placeholder: dateFormats.dateTimePicker.format, placeholder: dateFormats.stringDate,
}; };
export default { export default {
......
import dateformat from 'dateformat';
import { __ } from '~/locale';
import { secondsToMilliseconds } from '~/lib/utils/datetime_utility';
/**
* Valid strings for this regex are
* 2019-10-01 and 2019-10-01 01:02:03
*/
const dateTimePickerRegex = /^(\d{4})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])(?: (0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]))?$/;
export const defaultTimeWindows = {
thirtyMinutes: {
label: __('30 minutes'),
seconds: 60 * 30,
},
threeHours: {
label: __('3 hours'),
seconds: 60 * 60 * 3,
},
eightHours: {
label: __('8 hours'),
seconds: 60 * 60 * 8,
default: true,
},
oneDay: {
label: __('1 day'),
seconds: 60 * 60 * 24 * 1,
},
threeDays: {
label: __('3 days'),
seconds: 60 * 60 * 24 * 3,
},
};
export const dateFormats = {
ISODate: "yyyy-mm-dd'T'HH:MM:ss'Z'",
stringDate: 'yyyy-mm-dd HH:MM:ss',
};
/**
* The URL params start and end need to be validated
* before passing them down to other components.
*
* @param {string} dateString
* @returns true if the string is a valid date, false otherwise
*/
export const isValidDate = dateString => {
try {
// dateformat throws error that can be caught.
// This is better than using `new Date()`
if (dateString && dateString.trim()) {
dateformat(dateString, 'isoDateTime');
return true;
}
return false;
} catch (e) {
return false;
}
};
export const getTimeRange = (timeWindowKey, timeWindows = defaultTimeWindows) => {
let difference;
if (timeWindows[timeWindowKey]) {
difference = timeWindows[timeWindowKey].seconds;
} else {
const [defaultEntry] = Object.entries(timeWindows).filter(
([, timeWindow]) => timeWindow.default,
);
// find default time window
difference = defaultEntry[1].seconds;
}
const end = Math.floor(Date.now() / 1000); // convert milliseconds to seconds
const start = end - difference;
return {
start: new Date(secondsToMilliseconds(start)).toISOString(),
end: new Date(secondsToMilliseconds(end)).toISOString(),
};
};
export const getTimeWindowKey = ({ start, end }, timeWindows = defaultTimeWindows) =>
Object.entries(timeWindows).reduce((acc, [timeWindowKey, timeWindow]) => {
if (new Date(end) - new Date(start) === secondsToMilliseconds(timeWindow.seconds)) {
return timeWindowKey;
}
return acc;
}, null);
/**
* Convert the input in Time picker component to ISO date.
*
* @param {string} val
* @returns {string}
*/
export const stringToISODate = val =>
dateformat(new Date(val.replace(/-/g, '/')), dateFormats.ISODate, true);
/**
* Convert the ISO date received from the URL to string
* for the Time picker component.
*
* @param {Date} date
* @returns {string}
*/
export const ISODateToString = date => dateformat(date, dateFormats.stringDate);
export const truncateZerosInDateTime = datetime => datetime.replace(' 00:00:00', '');
export const isDateTimePickerInputValid = val => dateTimePickerRegex.test(val);
export default {};
...@@ -3,7 +3,7 @@ import { mapActions, mapState, mapGetters } from 'vuex'; ...@@ -3,7 +3,7 @@ import { mapActions, mapState, mapGetters } from 'vuex';
import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue'; import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
import { getParameterValues, removeParams } from '~/lib/utils/url_utility'; import { getParameterValues, removeParams } from '~/lib/utils/url_utility';
import { sidebarAnimationDuration } from '../constants'; import { sidebarAnimationDuration } from '../constants';
import { getTimeDiff } from '../utils'; import { getTimeRange } from './date_time_picker/date_time_picker_lib';
let sidebarMutationObserver; let sidebarMutationObserver;
...@@ -18,7 +18,7 @@ export default { ...@@ -18,7 +18,7 @@ export default {
}, },
}, },
data() { data() {
const defaultRange = getTimeDiff(); const defaultRange = getTimeRange();
const start = getParameterValues('start', this.dashboardUrl)[0] || defaultRange.start; const start = getParameterValues('start', this.dashboardUrl)[0] || defaultRange.start;
const end = getParameterValues('end', this.dashboardUrl)[0] || defaultRange.end; const end = getParameterValues('end', this.dashboardUrl)[0] || defaultRange.end;
......
...@@ -50,11 +50,6 @@ export const metricStates = { ...@@ -50,11 +50,6 @@ export const metricStates = {
export const sidebarAnimationDuration = 300; // milliseconds. export const sidebarAnimationDuration = 300; // milliseconds.
export const chartHeight = 300; export const chartHeight = 300;
/**
* Valid strings for this regex are
* 2019-10-01 and 2019-10-01 01:02:03
*/
export const dateTimePickerRegex = /^(\d{4})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])(?: (0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]))?$/;
export const graphTypes = { export const graphTypes = {
deploymentData: 'scatter', deploymentData: 'scatter',
...@@ -83,38 +78,39 @@ export const lineWidths = { ...@@ -83,38 +78,39 @@ export const lineWidths = {
default: 2, default: 2,
}; };
export const timeWindows = {
thirtyMinutes: __('30 minutes'),
threeHours: __('3 hours'),
eightHours: __('8 hours'),
oneDay: __('1 day'),
threeDays: __('3 days'),
oneWeek: __('1 week'),
};
export const dateFormats = { export const dateFormats = {
timeOfDay: 'h:MM TT', timeOfDay: 'h:MM TT',
default: 'dd mmm yyyy, h:MMTT', default: 'dd mmm yyyy, h:MMTT',
dateTimePicker: {
format: 'yyyy-mm-dd hh:mm:ss',
ISODate: "yyyy-mm-dd'T'HH:MM:ss'Z'",
stringDate: 'yyyy-mm-dd HH:MM:ss',
},
}; };
export const secondsIn = { export const datePickerTimeWindows = {
thirtyMinutes: 60 * 30, thirtyMinutes: {
threeHours: 60 * 60 * 3, label: __('30 minutes'),
eightHours: 60 * 60 * 8, seconds: 60 * 30,
oneDay: 60 * 60 * 24 * 1, },
threeDays: 60 * 60 * 24 * 3, threeHours: {
oneWeek: 60 * 60 * 24 * 7 * 1, label: __('3 hours'),
seconds: 60 * 60 * 3,
},
eightHours: {
label: __('8 hours'),
seconds: 60 * 60 * 8,
default: true,
},
oneDay: {
label: __('1 day'),
seconds: 60 * 60 * 24 * 1,
},
threeDays: {
label: __('3 days'),
seconds: 60 * 60 * 24 * 3,
},
oneWeek: {
label: __('1 week'),
seconds: 60 * 60 * 24 * 7 * 1,
},
twoWeeks: {
label: __('2 weeks'),
seconds: 60 * 60 * 24 * 7 * 2,
},
}; };
export const timeWindowsKeyNames = Object.keys(secondsIn).reduce(
(otherTimeWindows, timeWindow) => ({
...otherTimeWindows,
[timeWindow]: timeWindow,
}),
{},
);
import dateformat from 'dateformat';
import { secondsIn, dateTimePickerRegex, dateFormats } from './constants';
import { secondsToMilliseconds } from '~/lib/utils/datetime_utility';
export const getTimeDiff = timeWindow => {
const end = Math.floor(Date.now() / 1000); // convert milliseconds to seconds
const difference = secondsIn[timeWindow] || secondsIn.eightHours;
const start = end - difference;
return {
start: new Date(secondsToMilliseconds(start)).toISOString(),
end: new Date(secondsToMilliseconds(end)).toISOString(),
};
};
export const getTimeWindow = ({ start, end }) =>
Object.entries(secondsIn).reduce((acc, [timeRange, value]) => {
if (new Date(end) - new Date(start) === secondsToMilliseconds(value)) {
return timeRange;
}
return acc;
}, null);
export const isDateTimePickerInputValid = val => dateTimePickerRegex.test(val);
export const truncateZerosInDateTime = datetime => datetime.replace(' 00:00:00', '');
/**
* The URL params start and end need to be validated
* before passing them down to other components.
*
* @param {string} dateString
*/
export const isValidDate = dateString => {
try {
// dateformat throws error that can be caught.
// This is better than using `new Date()`
if (dateString && dateString.trim()) {
dateformat(dateString, 'isoDateTime');
return true;
}
return false;
} catch (e) {
return false;
}
};
/**
* Convert the input in Time picker component to ISO date.
*
* @param {string} val
* @returns {string}
*/
export const stringToISODate = val =>
dateformat(new Date(val.replace(/-/g, '/')), dateFormats.dateTimePicker.ISODate, true);
/**
* Convert the ISO date received from the URL to string
* for the Time picker component.
*
* @param {Date} date
* @returns {string}
*/
export const ISODateToString = date => dateformat(date, dateFormats.dateTimePicker.stringDate);
/** /**
* This method is used to validate if the graph data format for a chart component * This method is used to validate if the graph data format for a chart component
* that needs a time series as a response from a prometheus query (query_range) is * that needs a time series as a response from a prometheus query (query_range) is
......
---
title: Track usage of merge request file header buttons
merge_request:
author: Oregand
type: other
...@@ -1342,8 +1342,7 @@ In its simplest form, the `environment` keyword can be defined like: ...@@ -1342,8 +1342,7 @@ In its simplest form, the `environment` keyword can be defined like:
deploy to production: deploy to production:
stage: deploy stage: deploy
script: git push production HEAD:master script: git push production HEAD:master
environment: environment: production
name: production
``` ```
In the above example, the `deploy to production` job will be marked as doing a In the above example, the `deploy to production` job will be marked as doing a
......
...@@ -6801,6 +6801,9 @@ msgstr "" ...@@ -6801,6 +6801,9 @@ msgstr ""
msgid "Email address" msgid "Email address"
msgstr "" msgstr ""
msgid "Email display name"
msgstr ""
msgid "Email domain is not editable in subgroups. Value inherited from top-level parent group." msgid "Email domain is not editable in subgroups. Value inherited from top-level parent group."
msgstr "" msgstr ""
...@@ -6846,6 +6849,9 @@ msgstr "" ...@@ -6846,6 +6849,9 @@ msgstr ""
msgid "Emails" msgid "Emails"
msgstr "" msgstr ""
msgid "Emails sent from Service Desk will have this name"
msgstr ""
msgid "Emails separated by comma" msgid "Emails separated by comma"
msgstr "" msgstr ""
...@@ -8918,6 +8924,9 @@ msgstr "" ...@@ -8918,6 +8924,9 @@ msgstr ""
msgid "GitLab Shared Runners execute code of different projects on the same Runner unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is on GitLab.com)." msgid "GitLab Shared Runners execute code of different projects on the same Runner unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is on GitLab.com)."
msgstr "" msgstr ""
msgid "GitLab Support Bot"
msgstr ""
msgid "GitLab User" msgid "GitLab User"
msgstr "" msgstr ""
......
...@@ -42,7 +42,7 @@ describe('dashboard time window', () => { ...@@ -42,7 +42,7 @@ describe('dashboard time window', () => {
mock.restore(); mock.restore();
}); });
it('shows an error message if invalid url parameters are passed', done => { it('shows an active quick range option', done => {
mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsDashboardPayload); mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsDashboardPayload);
createComponentWrapperMounted({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] }); createComponentWrapperMounted({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
...@@ -55,6 +55,7 @@ describe('dashboard time window', () => { ...@@ -55,6 +55,7 @@ describe('dashboard time window', () => {
const timeWindowDropdownItems = wrapper const timeWindowDropdownItems = wrapper
.find('.js-time-window-dropdown') .find('.js-time-window-dropdown')
.findAll(GlDropdownItem); .findAll(GlDropdownItem);
const activeItem = timeWindowDropdownItems.wrappers.filter(itemWrapper => const activeItem = timeWindowDropdownItems.wrappers.filter(itemWrapper =>
itemWrapper.find('.active').exists(), itemWrapper.find('.active').exists(),
); );
......
import * as dateTimePickerLib from '~/monitoring/components/date_time_picker/date_time_picker_lib';
describe('date time picker lib', () => {
describe('isValidDate', () => {
[
{
input: '2019-09-09T00:00:00.000Z',
output: true,
},
{
input: '2019-09-09T000:00.000Z',
output: false,
},
{
input: 'a2019-09-09T000:00.000Z',
output: false,
},
{
input: '2019-09-09T',
output: false,
},
{
input: '2019-09-09',
output: true,
},
{
input: '2019-9-9',
output: true,
},
{
input: '2019-9-',
output: true,
},
{
input: '2019--',
output: false,
},
{
input: '2019',
output: true,
},
{
input: '',
output: false,
},
{
input: null,
output: false,
},
].forEach(({ input, output }) => {
it(`isValidDate return ${output} for ${input}`, () => {
expect(dateTimePickerLib.isValidDate(input)).toBe(output);
});
});
});
describe('getTimeWindow', () => {
[
{
args: [
{
start: '2019-10-01T18:27:47.000Z',
end: '2019-10-01T21:27:47.000Z',
},
dateTimePickerLib.defaultTimeWindows,
],
expected: 'threeHours',
},
{
args: [
{
start: '2019-10-01T28:27:47.000Z',
end: '2019-10-01T21:27:47.000Z',
},
dateTimePickerLib.defaultTimeWindows,
],
expected: null,
},
{
args: [
{
start: '',
end: '',
},
dateTimePickerLib.defaultTimeWindows,
],
expected: null,
},
{
args: [
{
start: null,
end: null,
},
dateTimePickerLib.defaultTimeWindows,
],
expected: null,
},
{
args: [{}, dateTimePickerLib.defaultTimeWindows],
expected: null,
},
].forEach(({ args, expected }) => {
it(`returns "${expected}" with args=${JSON.stringify(args)}`, () => {
expect(dateTimePickerLib.getTimeWindowKey(...args)).toEqual(expected);
});
});
});
describe('getTimeRange', () => {
function secondsBetween({ start, end }) {
return (new Date(end) - new Date(start)) / 1000;
}
function minutesBetween(timeRange) {
return secondsBetween(timeRange) / 60;
}
function hoursBetween(timeRange) {
return minutesBetween(timeRange) / 60;
}
it('defaults to an 8 hour (28800s) difference', () => {
const params = dateTimePickerLib.getTimeRange();
expect(hoursBetween(params)).toEqual(8);
});
it('accepts time window as an argument', () => {
const params = dateTimePickerLib.getTimeRange('thirtyMinutes');
expect(minutesBetween(params)).toEqual(30);
});
it('returns a value for every defined time window', () => {
const nonDefaultWindows = Object.entries(dateTimePickerLib.defaultTimeWindows).filter(
([, timeWindow]) => !timeWindow.default,
);
nonDefaultWindows.forEach(timeWindow => {
const params = dateTimePickerLib.getTimeRange(timeWindow[0]);
// Ensure we're not returning the default
expect(hoursBetween(params)).not.toEqual(8);
});
});
});
describe('stringToISODate', () => {
['', 'null', undefined, 'abc'].forEach(input => {
it(`throws error for invalid input like ${input}`, done => {
try {
dateTimePickerLib.stringToISODate(input);
} catch (e) {
expect(e).toBeDefined();
done();
}
});
});
[
{
input: '2019-09-09 01:01:01',
output: '2019-09-09T01:01:01Z',
},
{
input: '2019-09-09 00:00:00',
output: '2019-09-09T00:00:00Z',
},
{
input: '2019-09-09 23:59:59',
output: '2019-09-09T23:59:59Z',
},
{
input: '2019-09-09',
output: '2019-09-09T00:00:00Z',
},
].forEach(({ input, output }) => {
it(`returns ${output} from ${input}`, () => {
expect(dateTimePickerLib.stringToISODate(input)).toBe(output);
});
});
});
describe('truncateZerosInDateTime', () => {
[
{
input: '',
output: '',
},
{
input: '2019-10-10',
output: '2019-10-10',
},
{
input: '2019-10-10 00:00:01',
output: '2019-10-10 00:00:01',
},
{
input: '2019-10-10 00:00:00',
output: '2019-10-10',
},
].forEach(({ input, output }) => {
it(`truncateZerosInDateTime return ${output} for ${input}`, () => {
expect(dateTimePickerLib.truncateZerosInDateTime(input)).toBe(output);
});
});
});
describe('isDateTimePickerInputValid', () => {
[
{
input: null,
output: false,
},
{
input: '',
output: false,
},
{
input: 'xxxx-xx-xx',
output: false,
},
{
input: '9999-99-19',
output: false,
},
{
input: '2019-19-23',
output: false,
},
{
input: '2019-09-23',
output: true,
},
{
input: '2019-09-23 x',
output: false,
},
{
input: '2019-09-29 0:0:0',
output: false,
},
{
input: '2019-09-29 00:00:00',
output: true,
},
{
input: '2019-09-29 24:24:24',
output: false,
},
{
input: '2019-09-29 23:24:24',
output: true,
},
{
input: '2019-09-29 23:24:24 ',
output: false,
},
].forEach(({ input, output }) => {
it(`returns ${output} for ${input}`, () => {
expect(dateTimePickerLib.isDateTimePickerInputValid(input)).toBe(output);
});
});
});
});
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import DateTimePicker from '~/monitoring/components/date_time_picker/date_time_picker.vue'; import DateTimePicker from '~/monitoring/components/date_time_picker/date_time_picker.vue';
import { timeWindows } from '~/monitoring/constants'; import { defaultTimeWindows } from '~/monitoring/components/date_time_picker/date_time_picker_lib';
const timeWindowsCount = Object.keys(timeWindows).length; const timeWindowsCount = Object.entries(defaultTimeWindows).length;
const start = '2019-10-10T07:00:00.000Z'; const start = '2019-10-10T07:00:00.000Z';
const end = '2019-10-13T07:00:00.000Z'; const end = '2019-10-13T07:00:00.000Z';
const selectedTimeWindowText = `3 days`; const selectedTimeWindowText = `3 days`;
...@@ -13,6 +13,7 @@ describe('DateTimePicker', () => { ...@@ -13,6 +13,7 @@ describe('DateTimePicker', () => {
const dropdownToggle = () => dateTimePicker.find('.dropdown-toggle'); const dropdownToggle = () => dateTimePicker.find('.dropdown-toggle');
const dropdownMenu = () => dateTimePicker.find('.dropdown-menu'); const dropdownMenu = () => dateTimePicker.find('.dropdown-menu');
const applyButtonElement = () => dateTimePicker.find('button.btn-success').element; const applyButtonElement = () => dateTimePicker.find('button.btn-success').element;
const findQuickRangeItems = () => dateTimePicker.findAll('.dropdown-item');
const cancelButtonElement = () => dateTimePicker.find('button.btn-secondary').element; const cancelButtonElement = () => dateTimePicker.find('button.btn-secondary').element;
const fillInputAndBlur = (input, val) => { const fillInputAndBlur = (input, val) => {
dateTimePicker.find(input).setValue(val); dateTimePicker.find(input).setValue(val);
...@@ -25,7 +26,6 @@ describe('DateTimePicker', () => { ...@@ -25,7 +26,6 @@ describe('DateTimePicker', () => {
const createComponent = props => { const createComponent = props => {
dateTimePicker = mount(DateTimePicker, { dateTimePicker = mount(DateTimePicker, {
propsData: { propsData: {
timeWindows,
start, start,
end, end,
...props, ...props,
...@@ -52,16 +52,6 @@ describe('DateTimePicker', () => { ...@@ -52,16 +52,6 @@ describe('DateTimePicker', () => {
}); });
}); });
it('renders dropdown without a selectedTimeWindow set', done => {
createComponent({
selectedTimeWindow: {},
});
dateTimePicker.vm.$nextTick(() => {
expect(dateTimePicker.findAll('input').length).toBe(2);
done();
});
});
it('renders inputs with h/m/s truncated if its all 0s', done => { it('renders inputs with h/m/s truncated if its all 0s', done => {
createComponent({ createComponent({
start: '2019-10-10T00:00:00.000Z', start: '2019-10-10T00:00:00.000Z',
...@@ -74,11 +64,11 @@ describe('DateTimePicker', () => { ...@@ -74,11 +64,11 @@ describe('DateTimePicker', () => {
}); });
}); });
it(`renders dropdown with ${timeWindowsCount} items in quick range`, done => { it(`renders dropdown with ${timeWindowsCount} (default) items in quick range`, done => {
createComponent(); createComponent();
dropdownToggle().trigger('click'); dropdownToggle().trigger('click');
dateTimePicker.vm.$nextTick(() => { dateTimePicker.vm.$nextTick(() => {
expect(dateTimePicker.findAll('.dropdown-item').length).toBe(timeWindowsCount); expect(findQuickRangeItems().length).toBe(timeWindowsCount);
done(); done();
}); });
}); });
...@@ -167,4 +157,77 @@ describe('DateTimePicker', () => { ...@@ -167,4 +157,77 @@ describe('DateTimePicker', () => {
}); });
}); });
}); });
describe('when using non-default time windows', () => {
const otherTimeWindows = {
oneMinute: {
label: '1 minute',
seconds: 60,
},
twoMinutes: {
label: '2 minutes',
seconds: 60 * 2,
default: true,
},
fiveMinutes: {
label: '5 minutes',
seconds: 60 * 5,
},
};
it('renders dropdown with a label in the quick range', done => {
createComponent({
// 2 minutes range
start: '2020-01-21T15:00:00.000Z',
end: '2020-01-21T15:02:00.000Z',
timeWindows: otherTimeWindows,
});
dropdownToggle().trigger('click');
dateTimePicker.vm.$nextTick(() => {
expect(dropdownToggle().text()).toBe('2 minutes');
done();
});
});
it('renders dropdown with quick range items', done => {
createComponent({
// 2 minutes range
start: '2020-01-21T15:00:00.000Z',
end: '2020-01-21T15:02:00.000Z',
timeWindows: otherTimeWindows,
});
dropdownToggle().trigger('click');
dateTimePicker.vm.$nextTick(() => {
const items = findQuickRangeItems();
expect(items.length).toBe(Object.keys(otherTimeWindows).length);
expect(items.at(0).text()).toBe('1 minute');
expect(items.at(0).is('.active')).toBe(false);
expect(items.at(1).text()).toBe('2 minutes');
expect(items.at(1).is('.active')).toBe(true);
expect(items.at(2).text()).toBe('5 minutes');
expect(items.at(2).is('.active')).toBe(false);
done();
});
});
it('renders dropdown with a label not in the quick range', done => {
createComponent({
// 10 minutes range
start: '2020-01-21T15:00:00.000Z',
end: '2020-01-21T15:10:00.000Z',
timeWindows: otherTimeWindows,
});
dropdownToggle().trigger('click');
dateTimePicker.vm.$nextTick(() => {
expect(dropdownToggle().text()).toBe('2020-01-21 15:00:00 to 2020-01-21 15:10:00');
done();
});
});
});
}); });
import * as monitoringUtils from '~/monitoring/utils'; import * as monitoringUtils from '~/monitoring/utils';
import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants';
import { import {
graphDataPrometheusQuery, graphDataPrometheusQuery,
graphDataPrometheusQueryRange, graphDataPrometheusQueryRange,
...@@ -58,92 +57,6 @@ describe('monitoring/utils', () => { ...@@ -58,92 +57,6 @@ describe('monitoring/utils', () => {
}); });
}); });
describe('getTimeDiff', () => {
function secondsBetween({ start, end }) {
return (new Date(end) - new Date(start)) / 1000;
}
function minutesBetween(timeRange) {
return secondsBetween(timeRange) / 60;
}
function hoursBetween(timeRange) {
return minutesBetween(timeRange) / 60;
}
it('defaults to an 8 hour (28800s) difference', () => {
const params = monitoringUtils.getTimeDiff();
expect(hoursBetween(params)).toEqual(8);
});
it('accepts time window as an argument', () => {
const params = monitoringUtils.getTimeDiff('thirtyMinutes');
expect(minutesBetween(params)).toEqual(30);
});
it('returns a value for every defined time window', () => {
const nonDefaultWindows = Object.keys(timeWindows).filter(window => window !== 'eightHours');
nonDefaultWindows.forEach(timeWindow => {
const params = monitoringUtils.getTimeDiff(timeWindow);
// Ensure we're not returning the default
expect(hoursBetween(params)).not.toEqual(8);
});
});
});
describe('getTimeWindow', () => {
[
{
args: [
{
start: '2019-10-01T18:27:47.000Z',
end: '2019-10-01T21:27:47.000Z',
},
],
expected: timeWindowsKeyNames.threeHours,
},
{
args: [
{
start: '2019-10-01T28:27:47.000Z',
end: '2019-10-01T21:27:47.000Z',
},
],
expected: null,
},
{
args: [
{
start: '',
end: '',
},
],
expected: null,
},
{
args: [
{
start: null,
end: null,
},
],
expected: null,
},
{
args: [{}],
expected: null,
},
].forEach(({ args, expected }) => {
it(`returns "${expected}" with args=${JSON.stringify(args)}`, () => {
expect(monitoringUtils.getTimeWindow(...args)).toEqual(expected);
});
});
});
describe('graphDataValidatorForValues', () => { describe('graphDataValidatorForValues', () => {
/* /*
* When dealing with a metric using the query format, e.g. * When dealing with a metric using the query format, e.g.
...@@ -174,193 +87,6 @@ describe('monitoring/utils', () => { ...@@ -174,193 +87,6 @@ describe('monitoring/utils', () => {
}); });
}); });
describe('stringToISODate', () => {
['', 'null', undefined, 'abc'].forEach(input => {
it(`throws error for invalid input like ${input}`, done => {
try {
monitoringUtils.stringToISODate(input);
} catch (e) {
expect(e).toBeDefined();
done();
}
});
});
[
{
input: '2019-09-09 01:01:01',
output: '2019-09-09T01:01:01Z',
},
{
input: '2019-09-09 00:00:00',
output: '2019-09-09T00:00:00Z',
},
{
input: '2019-09-09 23:59:59',
output: '2019-09-09T23:59:59Z',
},
{
input: '2019-09-09',
output: '2019-09-09T00:00:00Z',
},
].forEach(({ input, output }) => {
it(`returns ${output} from ${input}`, () => {
expect(monitoringUtils.stringToISODate(input)).toBe(output);
});
});
});
describe('ISODateToString', () => {
[
{
input: new Date('2019-09-09T00:00:00.000Z'),
output: '2019-09-09 00:00:00',
},
{
input: new Date('2019-09-09T07:00:00.000Z'),
output: '2019-09-09 07:00:00',
},
].forEach(({ input, output }) => {
it(`ISODateToString return ${output} for ${input}`, () => {
expect(monitoringUtils.ISODateToString(input)).toBe(output);
});
});
});
describe('truncateZerosInDateTime', () => {
[
{
input: '',
output: '',
},
{
input: '2019-10-10',
output: '2019-10-10',
},
{
input: '2019-10-10 00:00:01',
output: '2019-10-10 00:00:01',
},
{
input: '2019-10-10 00:00:00',
output: '2019-10-10',
},
].forEach(({ input, output }) => {
it(`truncateZerosInDateTime return ${output} for ${input}`, () => {
expect(monitoringUtils.truncateZerosInDateTime(input)).toBe(output);
});
});
});
describe('isValidDate', () => {
[
{
input: '2019-09-09T00:00:00.000Z',
output: true,
},
{
input: '2019-09-09T000:00.000Z',
output: false,
},
{
input: 'a2019-09-09T000:00.000Z',
output: false,
},
{
input: '2019-09-09T',
output: false,
},
{
input: '2019-09-09',
output: true,
},
{
input: '2019-9-9',
output: true,
},
{
input: '2019-9-',
output: true,
},
{
input: '2019--',
output: false,
},
{
input: '2019',
output: true,
},
{
input: '',
output: false,
},
{
input: null,
output: false,
},
].forEach(({ input, output }) => {
it(`isValidDate return ${output} for ${input}`, () => {
expect(monitoringUtils.isValidDate(input)).toBe(output);
});
});
});
describe('isDateTimePickerInputValid', () => {
[
{
input: null,
output: false,
},
{
input: '',
output: false,
},
{
input: 'xxxx-xx-xx',
output: false,
},
{
input: '9999-99-19',
output: false,
},
{
input: '2019-19-23',
output: false,
},
{
input: '2019-09-23',
output: true,
},
{
input: '2019-09-23 x',
output: false,
},
{
input: '2019-09-29 0:0:0',
output: false,
},
{
input: '2019-09-29 00:00:00',
output: true,
},
{
input: '2019-09-29 24:24:24',
output: false,
},
{
input: '2019-09-29 23:24:24',
output: true,
},
{
input: '2019-09-29 23:24:24 ',
output: false,
},
].forEach(({ input, output }) => {
it(`returns ${output} for ${input}`, () => {
expect(monitoringUtils.isDateTimePickerInputValid(input)).toBe(output);
});
});
});
describe('graphDataValidatorForAnomalyValues', () => { describe('graphDataValidatorForAnomalyValues', () => {
let oneMetric; let oneMetric;
let threeMetrics; let threeMetrics;
......
import Vue from 'vue'; import Vue from 'vue';
import { createStore } from 'ee_else_ce/mr_notes/stores'; import { createStore } from 'ee_else_ce/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { mockTracking, triggerEvent } from 'spec/helpers/tracking_helper';
import DiffFileComponent from '~/diffs/components/diff_file.vue'; import DiffFileComponent from '~/diffs/components/diff_file.vue';
import { diffViewerModes, diffViewerErrors } from '~/ide/constants'; import { diffViewerModes, diffViewerErrors } from '~/ide/constants';
import diffFileMockDataReadable from '../mock_data/diff_file'; import diffFileMockDataReadable from '../mock_data/diff_file';
...@@ -8,12 +9,14 @@ import diffFileMockDataUnreadable from '../mock_data/diff_file_unreadable'; ...@@ -8,12 +9,14 @@ import diffFileMockDataUnreadable from '../mock_data/diff_file_unreadable';
describe('DiffFile', () => { describe('DiffFile', () => {
let vm; let vm;
let trackingSpy;
beforeEach(() => { beforeEach(() => {
vm = createComponentWithStore(Vue.extend(DiffFileComponent), createStore(), { vm = createComponentWithStore(Vue.extend(DiffFileComponent), createStore(), {
file: JSON.parse(JSON.stringify(diffFileMockDataReadable)), file: JSON.parse(JSON.stringify(diffFileMockDataReadable)),
canCurrentUserFork: false, canCurrentUserFork: false,
}).$mount(); }).$mount();
trackingSpy = mockTracking('_category_', vm.$el, spyOn);
}); });
afterEach(() => { afterEach(() => {
...@@ -30,6 +33,7 @@ describe('DiffFile', () => { ...@@ -30,6 +33,7 @@ describe('DiffFile', () => {
expect(el.querySelectorAll('.diff-content.hidden').length).toEqual(0); expect(el.querySelectorAll('.diff-content.hidden').length).toEqual(0);
expect(el.querySelector('.js-file-title')).toBeDefined(); expect(el.querySelector('.js-file-title')).toBeDefined();
expect(el.querySelector('.btn-clipboard')).toBeDefined();
expect(el.querySelector('.file-title-name').innerText.indexOf(file_path)).toBeGreaterThan(-1); expect(el.querySelector('.file-title-name').innerText.indexOf(file_path)).toBeGreaterThan(-1);
expect(el.querySelector('.js-syntax-highlight')).toBeDefined(); expect(el.querySelector('.js-syntax-highlight')).toBeDefined();
...@@ -39,6 +43,25 @@ describe('DiffFile', () => { ...@@ -39,6 +43,25 @@ describe('DiffFile', () => {
.then(() => { .then(() => {
expect(el.querySelectorAll('.line_content').length).toBe(5); expect(el.querySelectorAll('.line_content').length).toBe(5);
expect(el.querySelectorAll('.js-line-expansion-content').length).toBe(1); expect(el.querySelectorAll('.js-line-expansion-content').length).toBe(1);
triggerEvent('.btn-clipboard');
})
.then(done)
.catch(done.fail);
});
it('should track a click event on copy to clip board button', done => {
const el = vm.$el;
expect(el.querySelector('.btn-clipboard')).toBeDefined();
vm.file.renderIt = true;
vm.$nextTick()
.then(() => {
triggerEvent('.btn-clipboard');
expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_copy_file_button', {
label: 'diff_copy_file_path_button',
property: 'diff_copy_file',
});
}) })
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
......
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