@n8n/frontend-module-insights 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +88 -0
- package/README.md +64 -0
- package/biome.jsonc +4 -0
- package/eslint.config.mjs +33 -0
- package/package.json +72 -0
- package/src/__tests__/setup.ts +4 -0
- package/src/chartjs.utils.ts +208 -0
- package/src/components/InsightsDashboard.test.ts +891 -0
- package/src/components/InsightsDashboard.vue +426 -0
- package/src/components/InsightsDataRangePicker.vue +195 -0
- package/src/components/InsightsDateRangeAlert.test.ts +134 -0
- package/src/components/InsightsDateRangeAlert.vue +89 -0
- package/src/components/InsightsPaywall.vue +42 -0
- package/src/components/InsightsSummary.test.ts +206 -0
- package/src/components/InsightsSummary.vue +357 -0
- package/src/components/InsightsUpgradeModal.vue +74 -0
- package/src/components/__snapshots__/InsightsSummary.test.ts.snap +446 -0
- package/src/components/charts/InsightsChartAverageRuntime.vue +67 -0
- package/src/components/charts/InsightsChartFailed.vue +57 -0
- package/src/components/charts/InsightsChartFailureRate.vue +58 -0
- package/src/components/charts/InsightsChartTimeSaved.vue +74 -0
- package/src/components/charts/InsightsChartTotal.vue +58 -0
- package/src/components/charts/insightChartProps.ts +9 -0
- package/src/components/tables/InsightsTableWorkflows.test.ts +509 -0
- package/src/components/tables/InsightsTableWorkflows.vue +279 -0
- package/src/index.ts +4 -0
- package/src/insights.api.test.ts +435 -0
- package/src/insights.api.ts +71 -0
- package/src/insights.constants.ts +69 -0
- package/src/insights.module.test.ts +59 -0
- package/src/insights.module.ts +45 -0
- package/src/insights.store.test.ts +118 -0
- package/src/insights.store.ts +88 -0
- package/src/insights.types.ts +16 -0
- package/src/insights.utils.test.ts +339 -0
- package/src/insights.utils.ts +172 -0
- package/stylelint.config.mjs +9 -0
- package/vite.config.ts +29 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { CalendarDate } from '@internationalized/date';
|
|
2
|
+
import { createComponentRenderer, defaultSettings } from '@n8n/frontend-test-utils';
|
|
3
|
+
import { createTestingPinia } from '@pinia/testing';
|
|
4
|
+
|
|
5
|
+
import InsightsDateRangeAlert from './InsightsDateRangeAlert.vue';
|
|
6
|
+
|
|
7
|
+
const renderComponent = createComponentRenderer(InsightsDateRangeAlert, {
|
|
8
|
+
global: {
|
|
9
|
+
stubs: { N8nIcon: true },
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('InsightsDateRangeAlert', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
createTestingPinia({
|
|
16
|
+
initialState: { settings: { settings: defaultSettings } },
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('does not render when earliestDataDate is null', () => {
|
|
21
|
+
const { queryByTestId } = renderComponent({
|
|
22
|
+
props: {
|
|
23
|
+
earliestDataDate: null,
|
|
24
|
+
rangeStart: new CalendarDate(2026, 6, 1),
|
|
25
|
+
rangeEnd: new CalendarDate(2026, 6, 30),
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(queryByTestId('insights-date-range-alert')).not.toBeInTheDocument();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('does not render when rangeStart is on the earliest data date', () => {
|
|
33
|
+
const { queryByTestId } = renderComponent({
|
|
34
|
+
props: {
|
|
35
|
+
earliestDataDate: '2026-06-11T00:00:00.000Z',
|
|
36
|
+
rangeStart: new CalendarDate(2026, 6, 11),
|
|
37
|
+
rangeEnd: new CalendarDate(2026, 6, 30),
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(queryByTestId('insights-date-range-alert')).not.toBeInTheDocument();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('does not render when rangeStart is after the earliest data date', () => {
|
|
45
|
+
const { queryByTestId } = renderComponent({
|
|
46
|
+
props: {
|
|
47
|
+
earliestDataDate: '2026-06-11T00:00:00.000Z',
|
|
48
|
+
rangeStart: new CalendarDate(2026, 6, 15),
|
|
49
|
+
rangeEnd: new CalendarDate(2026, 6, 30),
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(queryByTestId('insights-date-range-alert')).not.toBeInTheDocument();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('renders the alert when rangeStart is before the earliest data date', () => {
|
|
57
|
+
const { getByTestId } = renderComponent({
|
|
58
|
+
props: {
|
|
59
|
+
earliestDataDate: '2026-06-11T00:00:00.000Z',
|
|
60
|
+
rangeStart: new CalendarDate(2026, 6, 9),
|
|
61
|
+
rangeEnd: new CalendarDate(2026, 6, 30),
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(getByTestId('insights-date-range-alert')).toBeInTheDocument();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('shows the correct title', () => {
|
|
69
|
+
const { getByText } = renderComponent({
|
|
70
|
+
props: {
|
|
71
|
+
earliestDataDate: '2026-06-11T00:00:00.000Z',
|
|
72
|
+
rangeStart: new CalendarDate(2026, 6, 9),
|
|
73
|
+
rangeEnd: new CalendarDate(2026, 6, 30),
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
expect(getByText('Your selected range goes back further than your data')).toBeInTheDocument();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('uses the singular form when exactly 1 day has no data', () => {
|
|
81
|
+
const { getByTestId } = renderComponent({
|
|
82
|
+
props: {
|
|
83
|
+
earliestDataDate: '2026-06-11T00:00:00.000Z',
|
|
84
|
+
rangeStart: new CalendarDate(2026, 6, 10),
|
|
85
|
+
rangeEnd: new CalendarDate(2026, 6, 30),
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(getByTestId('insights-date-range-alert').textContent).toContain('1 earlier day');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('uses the plural form when multiple days have no data', () => {
|
|
93
|
+
const { getByTestId } = renderComponent({
|
|
94
|
+
props: {
|
|
95
|
+
earliestDataDate: '2026-06-11T00:00:00.000Z',
|
|
96
|
+
rangeStart: new CalendarDate(2026, 6, 9),
|
|
97
|
+
rangeEnd: new CalendarDate(2026, 6, 30),
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(getByTestId('insights-date-range-alert').textContent).toContain('2 earlier days');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('shows no-data description when the entire range is before the earliest data date', () => {
|
|
105
|
+
const { getByTestId } = renderComponent({
|
|
106
|
+
props: {
|
|
107
|
+
earliestDataDate: '2026-06-25T00:00:00.000Z',
|
|
108
|
+
rangeStart: new CalendarDate(2026, 6, 1),
|
|
109
|
+
rangeEnd: new CalendarDate(2026, 6, 10),
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(getByTestId('insights-date-range-alert').textContent).toContain(
|
|
114
|
+
'Your selected range does not have any data available.',
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('hides the alert and emits dismiss when the dismiss button is clicked', async () => {
|
|
119
|
+
const { getByTestId, queryByTestId, emitted } = renderComponent({
|
|
120
|
+
props: {
|
|
121
|
+
earliestDataDate: '2026-06-11T00:00:00.000Z',
|
|
122
|
+
rangeStart: new CalendarDate(2026, 6, 9),
|
|
123
|
+
rangeEnd: new CalendarDate(2026, 6, 30),
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(getByTestId('insights-date-range-alert')).toBeInTheDocument();
|
|
128
|
+
|
|
129
|
+
await getByTestId('insights-date-range-alert-dismiss').click();
|
|
130
|
+
|
|
131
|
+
expect(queryByTestId('insights-date-range-alert')).not.toBeInTheDocument();
|
|
132
|
+
expect(emitted('dismiss')).toHaveLength(1);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { DateValue } from '@internationalized/date';
|
|
3
|
+
import { getLocalTimeZone, parseDate } from '@internationalized/date';
|
|
4
|
+
import { N8nAlert, N8nButton, N8nIcon, N8nText } from '@n8n/design-system';
|
|
5
|
+
import { useI18n } from '@n8n/i18n';
|
|
6
|
+
import { computed, ref } from 'vue';
|
|
7
|
+
|
|
8
|
+
const props = defineProps<{
|
|
9
|
+
earliestDataDate: string | null;
|
|
10
|
+
rangeStart: DateValue;
|
|
11
|
+
rangeEnd: DateValue;
|
|
12
|
+
}>();
|
|
13
|
+
|
|
14
|
+
const emit = defineEmits<{ dismiss: [] }>();
|
|
15
|
+
|
|
16
|
+
const i18n = useI18n();
|
|
17
|
+
|
|
18
|
+
const isDismissed = ref(false);
|
|
19
|
+
|
|
20
|
+
const alertInfo = computed(() => {
|
|
21
|
+
if (!props.earliestDataDate) return null;
|
|
22
|
+
|
|
23
|
+
const earliestCalendarDate = parseDate(props.earliestDataDate.substring(0, 10));
|
|
24
|
+
if (props.rangeStart.compare(earliestCalendarDate) >= 0) return null;
|
|
25
|
+
|
|
26
|
+
if (props.rangeEnd.compare(earliestCalendarDate) < 0) {
|
|
27
|
+
return { earliestCalendarDate, daysWithoutData: 0, noData: true as const };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const daysWithoutData = earliestCalendarDate.compare(props.rangeStart);
|
|
31
|
+
return { earliestCalendarDate, daysWithoutData, noData: false as const };
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const formattedDate = computed(() => {
|
|
35
|
+
if (!alertInfo.value) return '';
|
|
36
|
+
const d = alertInfo.value.earliestCalendarDate.toDate(getLocalTimeZone());
|
|
37
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
38
|
+
day: 'numeric',
|
|
39
|
+
month: 'short',
|
|
40
|
+
year: 'numeric',
|
|
41
|
+
}).format(d);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function dismiss() {
|
|
45
|
+
isDismissed.value = true;
|
|
46
|
+
emit('dismiss');
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<template>
|
|
51
|
+
<N8nAlert
|
|
52
|
+
v-if="alertInfo && !isDismissed"
|
|
53
|
+
type="info"
|
|
54
|
+
:show-icon="false"
|
|
55
|
+
data-test-id="insights-date-range-alert"
|
|
56
|
+
>
|
|
57
|
+
<template #icon>
|
|
58
|
+
<N8nText color="text-dark">
|
|
59
|
+
<N8nIcon icon="info" />
|
|
60
|
+
</N8nText>
|
|
61
|
+
</template>
|
|
62
|
+
<template #title>
|
|
63
|
+
<N8nText color="text-dark" bold>
|
|
64
|
+
{{ i18n.baseText('insights.dashboard.dataRangeAlert.title') }}
|
|
65
|
+
</N8nText>
|
|
66
|
+
</template>
|
|
67
|
+
<N8nText color="text-dark">
|
|
68
|
+
{{
|
|
69
|
+
alertInfo.noData
|
|
70
|
+
? i18n.baseText('insights.dashboard.dataRangeAlert.descriptionNoData', {
|
|
71
|
+
interpolate: { date: formattedDate },
|
|
72
|
+
})
|
|
73
|
+
: i18n.baseText('insights.dashboard.dataRangeAlert.description', {
|
|
74
|
+
interpolate: { date: formattedDate, days: alertInfo.daysWithoutData },
|
|
75
|
+
adjustToNumber: alertInfo.daysWithoutData,
|
|
76
|
+
})
|
|
77
|
+
}}
|
|
78
|
+
</N8nText>
|
|
79
|
+
<template #aside>
|
|
80
|
+
<N8nButton
|
|
81
|
+
size="small"
|
|
82
|
+
variant="subtle"
|
|
83
|
+
:label="i18n.baseText('insights.dashboard.dataRangeAlert.dismiss')"
|
|
84
|
+
data-test-id="insights-date-range-alert-dismiss"
|
|
85
|
+
@click="dismiss"
|
|
86
|
+
/>
|
|
87
|
+
</template>
|
|
88
|
+
</N8nAlert>
|
|
89
|
+
</template>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { N8nButton, N8nIcon, N8nText } from '@n8n/design-system';
|
|
3
|
+
import { useI18n } from '@n8n/i18n';
|
|
4
|
+
import { useBasePageRedirectionHelper } from '@n8n/stores/composables/useBasePageRedirectionHelper';
|
|
5
|
+
|
|
6
|
+
const pageRedirectionHelper = useBasePageRedirectionHelper();
|
|
7
|
+
const i18n = useI18n();
|
|
8
|
+
|
|
9
|
+
const goToUpgrade = async () => {
|
|
10
|
+
await pageRedirectionHelper.goToUpgrade('insights', 'upgrade-insights');
|
|
11
|
+
};
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<div :class="$style.callout">
|
|
16
|
+
<N8nIcon icon="lock" size="xlarge"></N8nIcon>
|
|
17
|
+
<N8nText bold tag="h4" size="large">
|
|
18
|
+
{{ i18n.baseText('insights.dashboard.paywall.title') }}
|
|
19
|
+
</N8nText>
|
|
20
|
+
<N8nText>
|
|
21
|
+
{{ i18n.baseText('insights.dashboard.paywall.description') }}
|
|
22
|
+
</N8nText>
|
|
23
|
+
<N8nButton variant="solid" native-type="button" size="large" @click="goToUpgrade">
|
|
24
|
+
{{ i18n.baseText('generic.upgrade') }}
|
|
25
|
+
</N8nButton>
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<style lang="scss" module>
|
|
30
|
+
.callout {
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: column;
|
|
33
|
+
height: 100%;
|
|
34
|
+
padding: 6rem 0;
|
|
35
|
+
align-items: center;
|
|
36
|
+
max-width: 360px;
|
|
37
|
+
margin: 0 auto;
|
|
38
|
+
text-align: center;
|
|
39
|
+
gap: 10px;
|
|
40
|
+
justify-content: center;
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { CalendarDate, getLocalTimeZone, today } from '@internationalized/date';
|
|
2
|
+
import { createComponentRenderer, defaultSettings } from '@n8n/frontend-test-utils';
|
|
3
|
+
import { createTestingPinia } from '@pinia/testing';
|
|
4
|
+
import { reactive } from 'vue';
|
|
5
|
+
|
|
6
|
+
import InsightsSummary from './InsightsSummary.vue';
|
|
7
|
+
import type { InsightsSummaryDisplay } from '../insights.types';
|
|
8
|
+
|
|
9
|
+
vi.mock('vue-router', () => ({
|
|
10
|
+
useRouter: () => ({}),
|
|
11
|
+
useRoute: () => reactive({}),
|
|
12
|
+
RouterLink: {
|
|
13
|
+
template: '<a><slot /></a>',
|
|
14
|
+
},
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const renderComponent = createComponentRenderer(InsightsSummary, {
|
|
18
|
+
global: {
|
|
19
|
+
stubs: {
|
|
20
|
+
RouterLink: {
|
|
21
|
+
template: '<a><slot /></a>',
|
|
22
|
+
},
|
|
23
|
+
N8nIcon: true,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('InsightsSummary', () => {
|
|
29
|
+
const endDate = today(getLocalTimeZone());
|
|
30
|
+
const startDate = endDate.subtract({ days: 7 });
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
createTestingPinia({
|
|
34
|
+
initialState: { settings: { settings: defaultSettings } },
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should render without error', () => {
|
|
39
|
+
expect(() =>
|
|
40
|
+
renderComponent({
|
|
41
|
+
props: {
|
|
42
|
+
summary: [],
|
|
43
|
+
startDate,
|
|
44
|
+
endDate,
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
).not.toThrow();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test.each<InsightsSummaryDisplay[]>([
|
|
51
|
+
[[]],
|
|
52
|
+
[
|
|
53
|
+
[
|
|
54
|
+
{ id: 'total', value: 525, deviation: 85, unit: '', deviationUnit: '%' },
|
|
55
|
+
{ id: 'failed', value: 14, deviation: 3, unit: '', deviationUnit: '%' },
|
|
56
|
+
{ id: 'failureRate', value: 1.9, deviation: -0.8, unit: '%', deviationUnit: 'pp' },
|
|
57
|
+
{
|
|
58
|
+
id: 'timeSaved',
|
|
59
|
+
value: 55.55555555555556,
|
|
60
|
+
deviation: -5.164722222222222,
|
|
61
|
+
unit: 'h',
|
|
62
|
+
deviationUnit: 'h',
|
|
63
|
+
},
|
|
64
|
+
{ id: 'averageRunTime', value: 2.5, deviation: -0.5, unit: 's', deviationUnit: 's' },
|
|
65
|
+
],
|
|
66
|
+
],
|
|
67
|
+
[
|
|
68
|
+
[
|
|
69
|
+
{ id: 'total', value: 525, deviation: 85, unit: '', deviationUnit: '%' },
|
|
70
|
+
{ id: 'failed', value: 14, deviation: 3, unit: '', deviationUnit: '%' },
|
|
71
|
+
{ id: 'failureRate', value: 1.9, deviation: -0.8, unit: '%', deviationUnit: 'pp' },
|
|
72
|
+
{ id: 'timeSaved', value: 0, deviation: 0, unit: 'h', deviationUnit: 'h' },
|
|
73
|
+
{ id: 'averageRunTime', value: 2.5, deviation: -0.5, unit: 's', deviationUnit: 's' },
|
|
74
|
+
],
|
|
75
|
+
],
|
|
76
|
+
[
|
|
77
|
+
[
|
|
78
|
+
{ id: 'total', value: 525, deviation: -2, unit: '', deviationUnit: '%' },
|
|
79
|
+
{ id: 'failed', value: 14, deviation: -3, unit: '', deviationUnit: '%' },
|
|
80
|
+
{ id: 'failureRate', value: 1.9, deviation: 0.8, unit: '%', deviationUnit: 'pp' },
|
|
81
|
+
{ id: 'timeSaved', value: 55.55555555555556, deviation: 0, unit: 'h', deviationUnit: 'h' },
|
|
82
|
+
{ id: 'averageRunTime', value: 2.5, deviation: 0.5, unit: 's', deviationUnit: 's' },
|
|
83
|
+
],
|
|
84
|
+
],
|
|
85
|
+
[
|
|
86
|
+
[
|
|
87
|
+
{ id: 'total', value: 525, deviation: null, unit: '', deviationUnit: '%' },
|
|
88
|
+
{ id: 'failed', value: 14, deviation: null, unit: '', deviationUnit: '%' },
|
|
89
|
+
{ id: 'failureRate', value: 1.9, deviation: null, unit: '%', deviationUnit: 'pp' },
|
|
90
|
+
{
|
|
91
|
+
id: 'timeSaved',
|
|
92
|
+
value: 55.55555555555556,
|
|
93
|
+
deviation: null,
|
|
94
|
+
unit: 'h',
|
|
95
|
+
deviationUnit: 'h',
|
|
96
|
+
},
|
|
97
|
+
{ id: 'averageRunTime', value: 2.5, deviation: null, unit: 's', deviationUnit: 's' },
|
|
98
|
+
],
|
|
99
|
+
],
|
|
100
|
+
[
|
|
101
|
+
[
|
|
102
|
+
{ id: 'total', value: 0, deviation: 0, unit: '', deviationUnit: '%' },
|
|
103
|
+
{ id: 'failed', value: 0, deviation: 0, unit: '', deviationUnit: '%' },
|
|
104
|
+
{ id: 'failureRate', value: 0, deviation: 0, unit: '%', deviationUnit: 'pp' },
|
|
105
|
+
{ id: 'timeSaved', value: 0, deviation: 0, unit: 'h', deviationUnit: 'h' },
|
|
106
|
+
{ id: 'averageRunTime', value: 0, deviation: 0, unit: 's', deviationUnit: 's' },
|
|
107
|
+
],
|
|
108
|
+
],
|
|
109
|
+
])('should render the summary correctly', (summary) => {
|
|
110
|
+
const { html } = renderComponent({
|
|
111
|
+
props: {
|
|
112
|
+
summary,
|
|
113
|
+
startDate,
|
|
114
|
+
endDate,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(html()).toMatchSnapshot();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('with different date ranges', () => {
|
|
122
|
+
const testSummary: InsightsSummaryDisplay = [
|
|
123
|
+
{ id: 'total', value: 525, deviation: 85, unit: '', deviationUnit: '%' },
|
|
124
|
+
{ id: 'failed', value: 14, deviation: 3, unit: '', deviationUnit: '%' },
|
|
125
|
+
{ id: 'failureRate', value: 1.9, deviation: -0.8, unit: '%', deviationUnit: 'pp' },
|
|
126
|
+
{ id: 'timeSaved', value: 55.55, deviation: -5.16, unit: 'h', deviationUnit: 'h' },
|
|
127
|
+
{ id: 'averageRunTime', value: 2.5, deviation: -0.5, unit: 's', deviationUnit: 's' },
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
test.each<{
|
|
131
|
+
description: string;
|
|
132
|
+
getDates: () => { start: ReturnType<typeof today>; end?: ReturnType<typeof today> };
|
|
133
|
+
}>([
|
|
134
|
+
{
|
|
135
|
+
description: 'last 24 hours (day preset)',
|
|
136
|
+
getDates: () => {
|
|
137
|
+
const end = today(getLocalTimeZone());
|
|
138
|
+
return { start: end.subtract({ days: 1 }), end };
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
description: 'last 7 days (week preset)',
|
|
143
|
+
getDates: () => {
|
|
144
|
+
const end = today(getLocalTimeZone());
|
|
145
|
+
return { start: end.subtract({ days: 7 }), end };
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
description: 'last 14 days (2weeks preset)',
|
|
150
|
+
getDates: () => {
|
|
151
|
+
const end = today(getLocalTimeZone());
|
|
152
|
+
return { start: end.subtract({ days: 14 }), end };
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
description: 'last 30 days (month preset)',
|
|
157
|
+
getDates: () => {
|
|
158
|
+
const end = today(getLocalTimeZone());
|
|
159
|
+
return { start: end.subtract({ days: 30 }), end };
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
description: 'last 90 days (quarter preset)',
|
|
164
|
+
getDates: () => {
|
|
165
|
+
const end = today(getLocalTimeZone());
|
|
166
|
+
return { start: end.subtract({ days: 90 }), end };
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
description: 'last 180 days (6months preset)',
|
|
171
|
+
getDates: () => {
|
|
172
|
+
const end = today(getLocalTimeZone());
|
|
173
|
+
return { start: end.subtract({ days: 180 }), end };
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
description: 'last 365 days (year preset)',
|
|
178
|
+
getDates: () => {
|
|
179
|
+
const end = today(getLocalTimeZone());
|
|
180
|
+
return { start: end.subtract({ days: 365 }), end };
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
description: 'custom range',
|
|
185
|
+
getDates: () => {
|
|
186
|
+
return {
|
|
187
|
+
start: new CalendarDate(2025, 9, 6),
|
|
188
|
+
end: new CalendarDate(2025, 9, 13),
|
|
189
|
+
};
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
])('should render with $description', ({ getDates }) => {
|
|
193
|
+
const { start, end } = getDates();
|
|
194
|
+
|
|
195
|
+
const { html } = renderComponent({
|
|
196
|
+
props: {
|
|
197
|
+
summary: testSummary,
|
|
198
|
+
startDate: start,
|
|
199
|
+
endDate: end,
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
expect(html()).toMatchSnapshot();
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|