From bfd85dd3c9afc02b427c16fa0241db5be1b3a6c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20G=C3=B3mez=20Morales?= Date: Mon, 28 Apr 2025 11:11:36 +0200 Subject: [PATCH] feat(editor): Add time range selector to Insights (#14877) Co-authored-by: Guillaume Jacquart --- .../@n8n/api-types/src/frontend-settings.ts | 3 + packages/cli/src/services/frontend.service.ts | 1 + .../editor-ui/src/__tests__/defaults.ts | 9 ++ .../components/InsightsDashboard.test.ts | 44 ++++++ .../insights/components/InsightsDashboard.vue | 135 +++++++++++------- .../components/InsightsDateRangeSelect.vue | 56 ++++++++ .../components/InsightsSummary.test.ts | 2 + .../insights/components/InsightsSummary.vue | 14 +- .../components/InsightsUpgradeModal.vue | 70 +++++++++ .../charts/InsightsChartAverageRuntime.vue | 16 +-- .../components/charts/InsightsChartFailed.vue | 13 +- .../charts/InsightsChartFailureRate.vue | 15 +- .../charts/InsightsChartTimeSaved.vue | 15 +- .../components/charts/InsightsChartTotal.vue | 12 +- .../components/charts/insightChartProps.ts | 7 + .../src/features/insights/insights.api.ts | 15 +- .../features/insights/insights.constants.ts | 39 ++++- .../src/features/insights/insights.store.ts | 25 +++- packages/frontend/editor-ui/src/init.ts | 2 +- .../src/plugins/i18n/locales/en.json | 12 +- .../editor-ui/src/views/CredentialsView.vue | 57 ++++---- .../editor-ui/src/views/ExecutionsView.vue | 33 ++--- .../editor-ui/src/views/WorkflowsView.vue | 75 +++++----- 23 files changed, 481 insertions(+), 189 deletions(-) create mode 100644 packages/frontend/editor-ui/src/features/insights/components/InsightsDashboard.test.ts create mode 100644 packages/frontend/editor-ui/src/features/insights/components/InsightsDateRangeSelect.vue create mode 100644 packages/frontend/editor-ui/src/features/insights/components/InsightsUpgradeModal.vue create mode 100644 packages/frontend/editor-ui/src/features/insights/components/charts/insightChartProps.ts diff --git a/packages/@n8n/api-types/src/frontend-settings.ts b/packages/@n8n/api-types/src/frontend-settings.ts index 809dc68daa..c08fdce17a 100644 --- a/packages/@n8n/api-types/src/frontend-settings.ts +++ b/packages/@n8n/api-types/src/frontend-settings.ts @@ -1,5 +1,7 @@ import type { ExpressionEvaluatorType, LogLevel, WorkflowSettings } from 'n8n-workflow'; +import { type InsightsDateRange } from './schemas/insights.schema'; + export interface IVersionNotificationSettings { enabled: boolean; endpoint: string; @@ -193,5 +195,6 @@ export interface FrontendSettings { enabled: boolean; summary: boolean; dashboard: boolean; + dateRanges: InsightsDateRange[]; }; } diff --git a/packages/cli/src/services/frontend.service.ts b/packages/cli/src/services/frontend.service.ts index 7335cd4cc5..0387c0ca68 100644 --- a/packages/cli/src/services/frontend.service.ts +++ b/packages/cli/src/services/frontend.service.ts @@ -251,6 +251,7 @@ export class FrontendService { enabled: this.modulesConfig.modules.includes('insights'), summary: true, dashboard: false, + dateRanges: [], }, logsView: { enabled: false, diff --git a/packages/frontend/editor-ui/src/__tests__/defaults.ts b/packages/frontend/editor-ui/src/__tests__/defaults.ts index e45167710a..47fc2a6d7b 100644 --- a/packages/frontend/editor-ui/src/__tests__/defaults.ts +++ b/packages/frontend/editor-ui/src/__tests__/defaults.ts @@ -149,6 +149,15 @@ export const defaultSettings: FrontendSettings = { enabled: false, summary: true, dashboard: false, + dateRanges: [ + { key: 'day', licensed: true, granularity: 'hour' }, + { key: 'week', licensed: true, granularity: 'day' }, + { key: '2weeks', licensed: true, granularity: 'day' }, + { key: 'month', licensed: false, granularity: 'day' }, + { key: 'quarter', licensed: false, granularity: 'week' }, + { key: '6months', licensed: false, granularity: 'week' }, + { key: 'year', licensed: false, granularity: 'week' }, + ], }, logsView: { enabled: false, diff --git a/packages/frontend/editor-ui/src/features/insights/components/InsightsDashboard.test.ts b/packages/frontend/editor-ui/src/features/insights/components/InsightsDashboard.test.ts new file mode 100644 index 0000000000..855643d9eb --- /dev/null +++ b/packages/frontend/editor-ui/src/features/insights/components/InsightsDashboard.test.ts @@ -0,0 +1,44 @@ +import { createComponentRenderer } from '@/__tests__/render'; +import InsightsDashboard from './InsightsDashboard.vue'; +import { createTestingPinia } from '@pinia/testing'; +import { defaultSettings } from '@/__tests__/defaults'; +import { useInsightsStore } from '@/features/insights/insights.store'; +import { mockedStore } from '@/__tests__/utils'; +import { within } from '@testing-library/vue'; +import userEvent from '@testing-library/user-event'; + +const renderComponent = createComponentRenderer(InsightsDashboard, { + props: { + insightType: 'total', + }, +}); + +describe('InsightsDashboard', () => { + beforeEach(() => { + createTestingPinia({ initialState: { settings: { settings: defaultSettings } } }); + }); + + it('should render without error', () => { + mockedStore(useInsightsStore); + expect(() => renderComponent()).not.toThrow(); + }); + + it('should update the selected time range', async () => { + mockedStore(useInsightsStore); + + const { getByTestId } = renderComponent(); + + expect(getByTestId('range-select')).toBeVisible(); + const select = within(getByTestId('range-select')).getByRole('combobox'); + await userEvent.click(select); + + const controllingId = select.getAttribute('aria-controls'); + const actions = document.querySelector(`#${controllingId}`); + if (!actions) { + throw new Error('Actions menu not found'); + } + + await userEvent.click(actions.querySelectorAll('li')[0]); + expect((select as HTMLInputElement).value).toBe('Last 24 hours'); + }); +}); diff --git a/packages/frontend/editor-ui/src/features/insights/components/InsightsDashboard.vue b/packages/frontend/editor-ui/src/features/insights/components/InsightsDashboard.vue index f5b5b0d8b9..c457fa274f 100644 --- a/packages/frontend/editor-ui/src/features/insights/components/InsightsDashboard.vue +++ b/packages/frontend/editor-ui/src/features/insights/components/InsightsDashboard.vue @@ -1,9 +1,13 @@ + + diff --git a/packages/frontend/editor-ui/src/features/insights/components/InsightsSummary.test.ts b/packages/frontend/editor-ui/src/features/insights/components/InsightsSummary.test.ts index 507423eab3..c0ce4f03e9 100644 --- a/packages/frontend/editor-ui/src/features/insights/components/InsightsSummary.test.ts +++ b/packages/frontend/editor-ui/src/features/insights/components/InsightsSummary.test.ts @@ -27,6 +27,7 @@ describe('InsightsSummary', () => { renderComponent({ props: { summary: [], + timeRange: 'week', }, }), ).not.toThrow(); @@ -95,6 +96,7 @@ describe('InsightsSummary', () => { const { html } = renderComponent({ props: { summary, + timeRange: 'week', }, }); diff --git a/packages/frontend/editor-ui/src/features/insights/components/InsightsSummary.vue b/packages/frontend/editor-ui/src/features/insights/components/InsightsSummary.vue index ef222edee3..8c34429afc 100644 --- a/packages/frontend/editor-ui/src/features/insights/components/InsightsSummary.vue +++ b/packages/frontend/editor-ui/src/features/insights/components/InsightsSummary.vue @@ -5,15 +5,17 @@ import { VIEWS } from '@/constants'; import { INSIGHT_IMPACT_TYPES, INSIGHTS_UNIT_IMPACT_MAPPING, + TIME_RANGE_LABELS, } from '@/features/insights/insights.constants'; import type { InsightsSummaryDisplay } from '@/features/insights/insights.types'; -import type { InsightsSummary } from '@n8n/api-types'; +import type { InsightsDateRange, InsightsSummary } from '@n8n/api-types'; import { smartDecimal } from '@n8n/utils/number/smartDecimal'; -import { computed, ref, useCssModule } from 'vue'; +import { computed, useCssModule } from 'vue'; import { useRoute } from 'vue-router'; const props = defineProps<{ summary: InsightsSummaryDisplay; + timeRange: InsightsDateRange['key']; loading?: boolean; }>(); @@ -22,8 +24,6 @@ const route = useRoute(); const $style = useCssModule(); const telemetry = useTelemetry(); -const lastNDays = ref(7); - const summaryTitles = computed>(() => ({ total: i18n.baseText('insights.banner.title.total'), failed: i18n.baseText('insights.banner.title.failed'), @@ -84,9 +84,9 @@ const trackTabClick = (insightType: keyof InsightsSummary) => { {{ summaryTitles[id] }} - {{ - i18n.baseText('insights.lastNDays', { interpolate: { count: lastNDays } }) - }} + + {{ TIME_RANGE_LABELS[timeRange] }} +