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