Commit ff64ae2d authored by Jiaan Louw's avatar Jiaan Louw Committed by Enrique Alcántara

Update audit events date range filter

Clarifies the date range constraints on audit events

Changelog: changed
EE: true
parent eeda9e91
......@@ -63,29 +63,19 @@ export default {
<audit-events-export-button v-if="hasExportUrl" :export-href="exportHref" />
</div>
</header>
<div class="row-content-block second-block gl-pb-0">
<div class="gl-display-flex gl-justify-content-space-between gl-flex-wrap">
<div v-if="showFilter" class="gl-mb-5 gl-w-full">
<audit-events-filter
:filter-token-options="filterTokenOptions"
:value="filterValue"
@selected="setFilterValue"
@submit="searchForAuditEvents"
/>
</div>
<div class="gl-display-flex gl-flex-wrap gl-w-full">
<div
class="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"
:end-date="endDate"
@selected="setDateRange"
/>
<sorting-field :sort-by="sortBy" @selected="setSortBy" />
</div>
</div>
<div class="audit-log-filter row-content-block second-block gl-pb-0">
<div class="gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row!">
<audit-events-filter
v-if="showFilter"
:filter-token-options="filterTokenOptions"
:value="filterValue"
class="gl-mr-5 gl-mb-5"
@selected="setFilterValue"
@submit="searchForAuditEvents"
/>
<sorting-field :sort-by="sortBy" @selected="setSortBy" />
</div>
<date-range-field :start-date="startDate" :end-date="endDate" @selected="setDateRange" />
</div>
<audit-events-table :events="events" :is-last-page="isLastPage" />
</div>
......
......@@ -3,17 +3,17 @@ import { GlButtonGroup, GlButton } from '@gitlab/ui';
import { datesMatch, dateAtFirstDayOfMonth, getDateInPast } from '~/lib/utils/datetime_utility';
import { convertToSnakeCase } from '~/lib/utils/text_utility';
import { n__, s__ } from '~/locale';
import { CURRENT_DATE } from '../constants';
import { CURRENT_DATE, SAME_DAY_OFFSET } from '../constants';
const DATE_RANGE_OPTIONS = [
{
text: n__('Last %d day', 'Last %d days', 7),
startDate: getDateInPast(CURRENT_DATE, 7),
startDate: getDateInPast(CURRENT_DATE, 7 - SAME_DAY_OFFSET),
endDate: CURRENT_DATE,
},
{
text: n__('Last %d day', 'Last %d days', 14),
startDate: getDateInPast(CURRENT_DATE, 14),
startDate: getDateInPast(CURRENT_DATE, 14 - SAME_DAY_OFFSET),
endDate: CURRENT_DATE,
},
{
......@@ -55,6 +55,7 @@ export default {
<gl-button
v-for="(dateRangeOption, idx) in $options.DATE_RANGE_OPTIONS"
:key="idx"
:data-testid="trackingLabel(dateRangeOption)"
:selected="isCurrentDateRange(dateRangeOption)"
data-track-action="click_date_range_button"
:data-track-label="trackingLabel(dateRangeOption)"
......
<script>
import { GlDaterangePicker } from '@gitlab/ui';
import { dateAtFirstDayOfMonth, getDateInPast } from '~/lib/utils/datetime_utility';
import { __, n__, sprintf } from '~/locale';
import { CURRENT_DATE, MAX_DATE_RANGE } from '../constants';
import DateRangeButtons from './date_range_buttons.vue';
......@@ -40,29 +41,45 @@ export default {
this.$emit('selected', { startDate, endDate });
}
},
daysSelectedMessage(daysSelected) {
return n__('1 day selected', '%d days selected', daysSelected);
},
},
CURRENT_DATE,
MAX_DATE_RANGE,
i18n: {
dateRangeTooltip: sprintf(__('Date range limited to %{number} days'), {
number: MAX_DATE_RANGE,
}),
},
};
</script>
<template>
<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>
<div class="gl-display-flex gl-lg-flex-direction-row gl-flex-direction-column">
<date-range-buttons
:date-range="defaultDateRange"
class="gl-lg-pr-5 gl-mb-5"
@input="onInput"
/>
<gl-daterange-picker
class="gl-display-flex gl-pl-0 gl-w-full"
class="daterange-picker gl-display-flex gl-pl-0"
:default-start-date="defaultStartDate"
:default-end-date="defaultEndDate"
:default-max-date="$options.CURRENT_DATE"
:max-date-range="$options.MAX_DATE_RANGE"
:same-day-selection="true"
start-picker-class="gl-mb-5 gl-pr-5 gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row gl-flex-grow-1 gl-lg-align-items-flex-end"
end-picker-class="gl-mb-5 gl-lg-pr-5 gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row gl-flex-grow-1 gl-lg-align-items-flex-end"
:tooltip="$options.i18n.dateRangeTooltip"
start-picker-class="gl-mb-5 gl-pr-3 gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row gl-lg-align-items-flex-end"
end-picker-class="gl-mb-5 gl-lg-mr-3 gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row gl-lg-align-items-flex-end"
date-range-indicator-class="gl-mb-5 gl-white-space-nowrap"
@input="onInput"
/>
>
<template #default="{ daysSelected }">
<template v-if="daysSelected > 0">
{{ daysSelectedMessage(daysSelected) }}
</template>
</template>
</gl-daterange-picker>
</div>
</template>
......@@ -46,18 +46,16 @@ export default {
</script>
<template>
<div>
<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"
:key="option.key"
:is-check-item="true"
:is-checked="isChecked(option.key)"
@click="onItemClick(option.key)"
>
{{ option.text }}
</gl-dropdown-item>
</gl-dropdown>
</div>
<gl-dropdown :text="selectedOption.text" class="gl-display-flex gl-mb-5">
<gl-dropdown-section-header> {{ $options.SORTING_TITLE }}</gl-dropdown-section-header>
<gl-dropdown-item
v-for="option in $options.SORTING_OPTIONS"
:key="option.key"
:is-check-item="true"
:is-checked="isChecked(option.key)"
@click="onItemClick(option.key)"
>
{{ option.text }}
</gl-dropdown-item>
</gl-dropdown>
</template>
......@@ -59,5 +59,7 @@ export const AVAILABLE_TOKEN_TYPES = AUDIT_FILTER_CONFIGS.map((token) => token.t
export const MAX_DATE_RANGE = 31;
export const SAME_DAY_OFFSET = 1;
// This creates a date with zero time, making it simpler to match to the query date values
export const CURRENT_DATE = new Date(new Date().toDateString());
.audit-log-table .gl-table th {
@include gl-line-height-normal;
}
.audit-log-filter {
@include media-breakpoint-down(md) {
.daterange-picker {
display: grid;
grid-template-columns: 1fr 1fr;
}
}
}
......@@ -13,46 +13,34 @@ exports[`AuditEventsApp when initialized matches the snapshot 1`] = `
</header>
<div
class="row-content-block second-block gl-pb-0"
class="audit-log-filter row-content-block second-block gl-pb-0"
>
<div
class="gl-display-flex gl-justify-content-space-between gl-flex-wrap"
class="gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row!"
>
<div
class="gl-mb-5 gl-w-full"
class="input-group bg-white flex-grow-1 gl-mr-5 gl-mb-5"
data-testid="audit-events-filter"
>
<div
class="input-group bg-white flex-grow-1"
data-testid="audit-events-filter"
>
<gl-filtered-search-stub
availabletokens="[object Object],[object Object],[object Object],[object Object]"
class="gl-h-32 w-100"
clearbuttontitle="Clear"
close-button-title="Close"
placeholder="Search"
value="[object Object]"
/>
</div>
<gl-filtered-search-stub
availabletokens="[object Object],[object Object],[object Object],[object Object]"
class="gl-h-32 w-100"
clearbuttontitle="Clear"
close-button-title="Close"
placeholder="Search"
value="[object Object]"
/>
</div>
<div
class="gl-display-flex gl-flex-wrap gl-w-full"
>
<div
class="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)"
startdate="Wed Jan 01 2020 00:00:00 GMT+0000 (Greenwich Mean Time)"
/>
<sorting-field-stub
sortby="created_asc"
/>
</div>
</div>
<sorting-field-stub
sortby="created_asc"
/>
</div>
<date-range-field-stub
enddate="Sun Feb 02 2020 00:00:00 GMT+0000 (Greenwich Mean Time)"
startdate="Wed Jan 01 2020 00:00:00 GMT+0000 (Greenwich Mean Time)"
/>
</div>
<audit-events-table-stub
......
import { GlButtonGroup, GlButton } from '@gitlab/ui';
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
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';
import { CURRENT_DATE, SAME_DAY_OFFSET } from 'ee/audit_events/constants';
import { getDateInPast, dateAtFirstDayOfMonth } from '~/lib/utils/datetime_utility';
describe('DateRangeButtons component', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = shallowMount(DateRangeButtons, {
propsData: { ...props },
});
wrapper = extendedWrapper(
shallowMount(DateRangeButtons, {
propsData: { ...props },
}),
);
};
const findButtonGroup = () => wrapper.findComponent(GlButtonGroup);
const findButtons = (f) => findButtonGroup().findAllComponents(GlButton).filter(f);
const findButtons = (f) => wrapper.findAllComponents(GlButton).filter(f);
const findSelectedButtons = () => findButtons((b) => b.props('selected'));
const findUnSelectedButtons = () => findButtons((b) => !b.props('selected'));
const findOneWeekAgoButton = () => wrapper.findByTestId('date_range_button_last_7_days');
const findTwoWeeksAgoButton = () => wrapper.findByTestId('date_range_button_last_14_days');
const findThisMonthButton = () => wrapper.findByTestId('date_range_button_this_month');
afterEach(() => {
wrapper.destroy();
});
it('sets the tracking data on the button', () => {
createComponent({
dateRange: { startDate: getDateInPast(CURRENT_DATE, 7), endDate: CURRENT_DATE },
describe('when the last 7 days is selected', () => {
beforeEach(() => {
createComponent({
dateRange: {
startDate: getDateInPast(CURRENT_DATE, 7 - SAME_DAY_OFFSET),
endDate: CURRENT_DATE,
},
});
});
expect(findSelectedButtons().at(0).attributes()).toMatchObject({
'data-track-action': 'click_date_range_button',
'data-track-label': 'date_range_button_last_7_days',
});
});
describe.each`
button | selected | text | trackingLabel | startDate
${findOneWeekAgoButton} | ${true} | ${'Last 7 days'} | ${'date_range_button_last_7_days'} | ${getDateInPast(CURRENT_DATE, 7 - SAME_DAY_OFFSET)}
${findTwoWeeksAgoButton} | ${false} | ${'Last 14 days'} | ${'date_range_button_last_14_days'} | ${getDateInPast(CURRENT_DATE, 14 - SAME_DAY_OFFSET)}
${findThisMonthButton} | ${false} | ${'This month'} | ${'date_range_button_this_month'} | ${dateAtFirstDayOfMonth(CURRENT_DATE)}
`('for the "$text" button', ({ button, selected, text, trackingLabel, startDate }) => {
it(`the button is ${selected ? 'selected' : 'not selected'}`, () => {
expect(button().props('selected')).toBe(selected);
});
it('shows the selected the option that matches the provided dateRange property', () => {
createComponent({
dateRange: { startDate: getDateInPast(CURRENT_DATE, 7), endDate: CURRENT_DATE },
});
it('shows the correct text', () => {
expect(button().text()).toBe(text);
});
expect(findSelectedButtons().at(0).text()).toBe('Last 7 days');
});
it('sets the correct tracking data', () => {
expect(button().attributes()).toMatchObject({
'data-track-action': 'click_date_range_button',
'data-track-label': trackingLabel,
});
});
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),
},
});
it('emits an "input" event with the dateRange when a new date range is selected', () => {
button().vm.$emit('click');
expect(findSelectedButtons()).toHaveLength(0);
expect(wrapper.emitted('input')).toEqual([[{ startDate, endDate: CURRENT_DATE }]]);
});
});
});
it('emits an "input" event with the dateRange when a new date range is selected', async () => {
createComponent({
dateRange: { startDate: getDateInPast(CURRENT_DATE, 7), endDate: CURRENT_DATE },
describe('when no predefined date range is selected', () => {
beforeEach(() => {
createComponent({
dateRange: {
startDate: getDateInPast(CURRENT_DATE, 5),
endDate: getDateInPast(CURRENT_DATE, 2),
},
});
});
findUnSelectedButtons().at(0).vm.$emit('click');
await wrapper.vm.$nextTick();
expect(wrapper.emitted().input[0]).toEqual([
{
startDate: getDateInPast(CURRENT_DATE, 14),
endDate: CURRENT_DATE,
},
]);
it('shows that no button is selected', () => {
expect(findSelectedButtons()).toHaveLength(0);
});
});
});
......@@ -19,9 +19,10 @@ describe('DateRangeField component', () => {
const findDatePicker = () => wrapper.find(GlDaterangePicker);
const findDateRangeButtons = () => wrapper.find(DateRangeButtons);
const createComponent = (props = {}) => {
const createComponent = (props = {}, stubs = {}) => {
wrapper = shallowMount(DateRangeField, {
propsData: { ...props },
stubs: { ...stubs },
});
};
......@@ -69,6 +70,20 @@ describe('DateRangeField component', () => {
defaultEndDate: endDate,
});
});
it('sets the tooltip on the date picker with the max date range', () => {
createComponent();
expect(findDatePicker().props('tooltip')).toBe(
`Date range limited to ${MAX_DATE_RANGE} days`,
);
});
it('does not set the default min date on the date picker', () => {
createComponent();
expect(findDatePicker().props('defaultMinDate')).toBe(null);
});
});
describe('when a only a endDate is picked', () => {
......@@ -115,4 +130,18 @@ describe('DateRangeField component', () => {
]);
});
});
describe('number of days selected', () => {
it('renders the number of days selected when there is a date range', () => {
createComponent({ startDate, endDate }, { GlDaterangePicker });
expect(findDatePicker().text()).toContain('2 days selected');
});
it('does not render the number of days selected when the date range is less than one', () => {
createComponent({ startDate, endDate: startDate }, { GlDaterangePicker });
expect(findDatePicker().text()).not.toContain('days selected');
});
});
});
......@@ -10789,6 +10789,9 @@ msgstr ""
msgid "Date range"
msgstr ""
msgid "Date range limited to %{number} days"
msgstr ""
msgid "Date range must be shorter than %{max_range} days."
msgstr ""
......
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