Commit a525fea8 authored by Martin Wortschack's avatar Martin Wortschack

Merge branch '223260-improve-and-limit-date-filters' into 'master'

Improve and limit audit event date filter

Closes #223260

See merge request gitlab-org/gitlab!42711
parents c26c620b 86498b5a
......@@ -743,3 +743,22 @@ export const differenceInMilliseconds = (startDate, endDate = Date.now()) => {
const endDateInMS = endDate instanceof Date ? endDate.getTime() : endDate;
return endDateInMS - startDateInMS;
};
/**
* A utility which returns a new date at the first day of the month for any given date.
*
* @param {Date} date
*
* @return {Date} the date at the first day of the month
*/
export const dateAtFirstDayOfMonth = date => new Date(newDate(date).setDate(1));
/**
* A utility function which checks if two dates match.
*
* @param {Date|Int} date1 Can be either a date object or a unix timestamp.
* @param {Date|Int} date2 Can be either a date object or a unix timestamp.
*
* @return {Boolean} true if the dates match
*/
export const datesMatch = (date1, date2) => differenceInMilliseconds(date1, date2) === 0;
......@@ -58,9 +58,9 @@ export default {
<audit-events-export-button v-if="hasExportUrl" :export-href="exportHref" />
</div>
</header>
<div class="row-content-block second-block pb-0">
<div class="d-flex justify-content-between audit-controls row">
<div class="col-lg-auto flex-fill form-group align-items-lg-center pr-lg-8">
<div class="row-content-block second-block gl-pb-0">
<div class="gl-display-flex gl-justify-content-space-between audit-controls gl-flex-wrap">
<div class="gl-mb-5 gl-w-full">
<audit-events-filter
:filter-token-options="filterTokenOptions"
:value="filterValue"
......@@ -68,9 +68,9 @@ export default {
@submit="searchForAuditEvents"
/>
</div>
<div class="d-flex col-lg-auto flex-wrap pl-lg-0">
<div class="gl-display-flex gl-flex-wrap gl-w-full">
<div
class="audit-controls d-flex align-items-lg-center flex-column flex-lg-row col-lg-auto px-0"
class="audit-controls gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row gl-justify-content-space-between gl-px-0 gl-w-full"
>
<date-range-field
:start-date="startDate"
......
<script>
import { GlButtonGroup, GlButton } from '@gitlab/ui';
import { n__, s__ } from '~/locale';
import { datesMatch, dateAtFirstDayOfMonth, getDateInPast } from '~/lib/utils/datetime_utility';
import { CURRENT_DATE } from '../constants';
const DATE_RANGE_OPTIONS = [
{
text: n__('Last %d day', 'Last %d days', 7),
startDate: getDateInPast(CURRENT_DATE, 7),
endDate: CURRENT_DATE,
},
{
text: n__('Last %d day', 'Last %d days', 14),
startDate: getDateInPast(CURRENT_DATE, 14),
endDate: CURRENT_DATE,
},
{
text: s__('AuditLogs|This month'),
startDate: dateAtFirstDayOfMonth(CURRENT_DATE),
endDate: CURRENT_DATE,
},
];
export default {
components: {
GlButton,
GlButtonGroup,
},
props: {
dateRange: {
type: Object,
required: true,
},
},
methods: {
onDateRangeClicked({ startDate, endDate }) {
this.$emit('input', { startDate, endDate });
},
isCurrentDateRange({ startDate, endDate }) {
const { dateRange } = this;
return datesMatch(startDate, dateRange.startDate) && datesMatch(endDate, dateRange.endDate);
},
},
DATE_RANGE_OPTIONS,
};
</script>
<template>
<gl-button-group>
<gl-button
v-for="(dateRangeOption, idx) in $options.DATE_RANGE_OPTIONS"
:key="idx"
:selected="isCurrentDateRange(dateRangeOption)"
@click="onDateRangeClicked(dateRangeOption)"
>{{ dateRangeOption.text }}</gl-button
>
</gl-button-group>
</template>
<script>
import { GlDaterangePicker } from '@gitlab/ui';
import { dateAtFirstDayOfMonth } from '~/lib/utils/datetime_utility';
import { CURRENT_DATE, MAX_DATE_RANGE } from '../constants';
import DateRangeButtons from './date_range_buttons.vue';
export default {
components: {
DateRangeButtons,
GlDaterangePicker,
},
props: {
......@@ -17,21 +21,43 @@ export default {
default: null,
},
},
computed: {
defaultStartDate() {
return this.startDate || dateAtFirstDayOfMonth(CURRENT_DATE);
},
defaultEndDate() {
return this.endDate || CURRENT_DATE;
},
defaultDateRange() {
return { startDate: this.defaultStartDate, endDate: this.defaultEndDate };
},
},
methods: {
onInput(dates) {
this.$emit('selected', dates);
},
},
CURRENT_DATE,
MAX_DATE_RANGE,
};
</script>
<template>
<gl-daterange-picker
class="d-flex flex-wrap flex-sm-nowrap"
:default-start-date="startDate"
:default-end-date="endDate"
start-picker-class="form-group align-items-lg-center mr-0 mr-sm-1 d-flex flex-column flex-lg-row"
end-picker-class="form-group align-items-lg-center mr-0 mr-sm-2 d-flex flex-column flex-lg-row"
@input="onInput"
/>
<div
class="gl-display-flex gl-align-items-flex-end gl-xs-align-items-baseline gl-xs-flex-direction-column"
>
<div class="gl-pr-5 gl-mb-5">
<date-range-buttons :date-range="defaultDateRange" @input="onInput" />
</div>
<gl-daterange-picker
class="gl-display-flex gl-pl-0 gl-w-full"
:default-start-date="defaultStartDate"
:default-end-date="defaultEndDate"
:default-max-date="$options.CURRENT_DATE"
:max-date-range="$options.MAX_DATE_RANGE"
start-picker-class="gl-mb-5 gl-pr-5 gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row gl-flex-fill-1"
end-picker-class="gl-mb-5 gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row gl-flex-fill-1"
@input="onInput"
/>
</div>
</template>
......@@ -47,7 +47,7 @@ export default {
<template>
<div>
<gl-dropdown :text="selectedOption.text" class="w-100 flex-column flex-lg-row form-group">
<gl-dropdown :text="selectedOption.text" class="w-100 flex-column flex-lg-row gl-mb-5">
<gl-dropdown-section-header> {{ $options.SORTING_TITLE }}</gl-dropdown-section-header>
<gl-dropdown-item
v-for="option in $options.SORTING_OPTIONS"
......
......@@ -56,3 +56,7 @@ export const AUDIT_FILTER_CONFIGS = [
];
export const AVAILABLE_TOKEN_TYPES = AUDIT_FILTER_CONFIGS.map(token => token.type);
export const MAX_DATE_RANGE = 31;
export const CURRENT_DATE = new Date();
.audit-controls .gl-dropdown-toggle {
border-color: $gray-100;
border-radius: 0.25rem;
box-shadow: inset 0 0 0 0.0625rem $gray-200;
padding: 0.5rem 0.75rem;
.audit-controls {
.gl-dropdown-toggle {
border-color: $gray-100;
border-radius: 0.25rem;
box-shadow: inset 0 0 0 0.0625rem $gray-200;
padding: 0.5rem 0.75rem;
}
.gl-daterange-picker > div {
@include media-breakpoint-up(lg) {
align-items: baseline;
}
}
}
---
title: Add quick select date options to audit events filter
merge_request: 42711
author:
type: added
......@@ -13,13 +13,13 @@ exports[`AuditEventsApp when initialized matches the snapshot 1`] = `
</header>
<div
class="row-content-block second-block pb-0"
class="row-content-block second-block gl-pb-0"
>
<div
class="d-flex justify-content-between audit-controls row"
class="gl-display-flex gl-justify-content-space-between audit-controls gl-flex-wrap"
>
<div
class="col-lg-auto flex-fill form-group align-items-lg-center pr-lg-8"
class="gl-mb-5 gl-w-full"
>
<div
class="input-group bg-white flex-grow-1"
......@@ -37,10 +37,10 @@ exports[`AuditEventsApp when initialized matches the snapshot 1`] = `
</div>
<div
class="d-flex col-lg-auto flex-wrap pl-lg-0"
class="gl-display-flex gl-flex-wrap gl-w-full"
>
<div
class="audit-controls d-flex align-items-lg-center flex-column flex-lg-row col-lg-auto px-0"
class="audit-controls gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row gl-justify-content-space-between gl-px-0 gl-w-full"
>
<date-range-field-stub
enddate="Sun Feb 02 2020 00:00:00 GMT+0000 (Greenwich Mean Time)"
......
import { shallowMount } from '@vue/test-utils';
import { GlButtonGroup, GlButton } from '@gitlab/ui';
import DateRangeButtons from 'ee/audit_events/components/date_range_buttons.vue';
import { CURRENT_DATE } from 'ee/audit_events/constants';
import { getDateInPast } from '~/lib/utils/datetime_utility';
describe('DateRangeButtons component', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = shallowMount(DateRangeButtons, {
propsData: { ...props },
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('shows the selected the option that matches the provided dateRange property', () => {
createComponent({
dateRange: { startDate: getDateInPast(CURRENT_DATE, 7), endDate: CURRENT_DATE },
});
expect(
wrapper
.find(GlButtonGroup)
.find('[selected="true"]')
.text(),
).toBe('Last 7 days');
});
it('shows no date range as selected when the dateRange property does not match any option', () => {
createComponent({
dateRange: {
startDate: getDateInPast(CURRENT_DATE, 5),
endDate: getDateInPast(CURRENT_DATE, 2),
},
});
expect(
wrapper
.find(GlButtonGroup)
.find('[selected="true"]')
.exists(),
).toBe(false);
});
it('emits an "input" event with the dateRange when a new date range is selected', async () => {
createComponent({
dateRange: { startDate: getDateInPast(CURRENT_DATE, 1), endDate: CURRENT_DATE },
});
wrapper
.find(GlButtonGroup)
.find(GlButton)
.vm.$emit('click');
await wrapper.vm.$nextTick();
expect(wrapper.emitted().input[0]).toEqual([
{
startDate: getDateInPast(CURRENT_DATE, 7),
endDate: CURRENT_DATE,
},
]);
});
});
import { shallowMount } from '@vue/test-utils';
import { GlDaterangePicker } from '@gitlab/ui';
import DateRangeButtons from 'ee/audit_events/components/date_range_buttons.vue';
import DateRangeField from 'ee/audit_events/components/date_range_field.vue';
import { parsePikadayDate } from '~/lib/utils/datetime_utility';
import { CURRENT_DATE, MAX_DATE_RANGE } from 'ee/audit_events/constants';
import { dateAtFirstDayOfMonth, parsePikadayDate } from '~/lib/utils/datetime_utility';
describe('DateRangeField component', () => {
let wrapper;
......@@ -10,6 +12,9 @@ describe('DateRangeField component', () => {
const startDate = parsePikadayDate('2020-03-13');
const endDate = parsePikadayDate('2020-03-14');
const findDatePicker = () => wrapper.find(GlDaterangePicker);
const findDateRangeButtons = () => wrapper.find(DateRangeButtons);
const createComponent = (props = {}) => {
wrapper = shallowMount(DateRangeField, {
propsData: { ...props },
......@@ -21,39 +26,74 @@ describe('DateRangeField component', () => {
wrapper = null;
});
it('passes the startDate to the date picker as defaultStartDate', () => {
createComponent({ startDate });
describe('default behaviour', () => {
it('sets the max date range on the date picker', () => {
createComponent();
expect(wrapper.find(GlDaterangePicker).props()).toMatchObject({
defaultStartDate: startDate,
defaultEndDate: null,
expect(findDatePicker().props('maxDateRange')).toBe(MAX_DATE_RANGE);
});
it("sets the max selectable date to today's date on the date picker", () => {
createComponent();
expect(
findDatePicker()
.props('defaultMaxDate')
.toDateString(),
).toBe(CURRENT_DATE.toDateString());
});
});
it('passes the endDate to the date picker as defaultEndDate', () => {
createComponent({ endDate });
it('sets the default start date to the start of the month', () => {
createComponent();
expect(wrapper.find(GlDaterangePicker).props()).toMatchObject({
defaultStartDate: null,
defaultEndDate: endDate,
expect(
findDatePicker()
.props('defaultStartDate')
.toDateString(),
).toBe(dateAtFirstDayOfMonth(CURRENT_DATE).toDateString());
});
it("sets the default end date to today's date", () => {
createComponent();
expect(
findDatePicker()
.props('defaultEndDate')
.toDateString(),
).toBe(CURRENT_DATE.toDateString());
});
it('passes both startDate and endDate to the date picker as default dates', () => {
createComponent({ startDate, endDate });
expect(findDatePicker().props()).toMatchObject({
defaultStartDate: startDate,
defaultEndDate: endDate,
});
});
});
it('passes both startDate and endDate to the date picker as default dates', () => {
createComponent({ startDate, endDate });
describe('when a new date range is picked', () => {
it('emits the "selected" event with the picked startDate and endDate', async () => {
createComponent();
findDatePicker().vm.$emit('input', { startDate, endDate });
expect(wrapper.find(GlDaterangePicker).props()).toMatchObject({
defaultStartDate: startDate,
defaultEndDate: endDate,
await wrapper.vm.$nextTick();
expect(wrapper.emitted().selected[0]).toEqual([
{
startDate,
endDate,
},
]);
});
});
it('should emit the "selected" event with startDate and endDate on input change', () => {
createComponent();
wrapper.find(GlDaterangePicker).vm.$emit('input', { startDate, endDate });
describe('when a date range button is pressed', () => {
it('emits the "selected" event with the picked startDate and endDate', async () => {
createComponent();
findDateRangeButtons().vm.$emit('input', { startDate, endDate });
return wrapper.vm.$nextTick(() => {
expect(wrapper.emitted().selected).toBeTruthy();
await wrapper.vm.$nextTick();
expect(wrapper.emitted().selected[0]).toEqual([
{
startDate,
......
......@@ -3666,6 +3666,9 @@ msgstr ""
msgid "AuditLogs|Target"
msgstr ""
msgid "AuditLogs|This month"
msgstr ""
msgid "AuditLogs|User Events"
msgstr ""
......
......@@ -667,3 +667,26 @@ describe('differenceInMilliseconds', () => {
expect(datetimeUtility.differenceInMilliseconds(startDate, endDate)).toBe(expected);
});
});
describe('dateAtFirstDayOfMonth', () => {
const date = new Date('2019-07-16T12:00:00.000Z');
it('returns the date at the first day of the month', () => {
const startDate = datetimeUtility.dateAtFirstDayOfMonth(date);
const expectedStartDate = new Date('2019-07-01T12:00:00.000Z');
expect(startDate).toStrictEqual(expectedStartDate);
});
});
describe('datesMatch', () => {
const date = new Date('2019-07-17T00:00:00.000Z');
it.each`
date1 | date2 | expected
${date} | ${new Date('2019-07-17T00:00:00.000Z')} | ${true}
${date} | ${new Date('2019-07-17T12:00:00.000Z')} | ${false}
`('returns $expected for $date1 matches $date2', ({ date1, date2, expected }) => {
expect(datetimeUtility.datesMatch(date1, date2)).toBe(expected);
});
});
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