Skip to content

Commit df474f3

Browse files
feat(editor): Insights summary banner (#13424)
Co-authored-by: Guillaume Jacquart <jacquart.guillaume@gmail.com>
1 parent 6992c36 commit df474f3

File tree

22 files changed

+559
-34
lines changed

22 files changed

+559
-34
lines changed

packages/@n8n/permissions/src/constants.ee.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ export const RESOURCES = {
2323
workersView: ['manage'] as const,
2424
workflow: ['share', 'execute', 'move', ...DEFAULT_OPERATIONS] as const,
2525
folder: [...DEFAULT_OPERATIONS] as const,
26+
insights: ['list'] as const,
2627
} as const;

packages/frontend/@n8n/design-system/src/components/N8nLoading/Loading.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ interface LoadingProps {
1919
animated?: boolean;
2020
loading?: boolean;
2121
rows?: number;
22+
cols?: number;
2223
shrinkLast?: boolean;
2324
variant?: (typeof VARIANT)[number];
2425
}
@@ -27,6 +28,7 @@ withDefaults(defineProps<LoadingProps>(), {
2728
animated: true,
2829
loading: true,
2930
rows: 1,
31+
cols: 0,
3032
shrinkLast: true,
3133
variant: 'p',
3234
});
@@ -38,7 +40,10 @@ withDefaults(defineProps<LoadingProps>(), {
3840
:animated="animated"
3941
:class="['n8n-loading', `n8n-loading-${variant}`]"
4042
>
41-
<template #template>
43+
<template v-if="cols" #template>
44+
<ElSkeletonItem v-for="i in cols" :key="i" />
45+
</template>
46+
<template v-else #template>
4247
<div v-if="variant === 'h1'">
4348
<div
4449
v-for="(item, index) in rows"

packages/frontend/editor-ui/src/components/Projects/ProjectHeader.test.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,6 @@ describe('ProjectHeader', () => {
6161
vi.clearAllMocks();
6262
});
6363

64-
it('should render the correct icon', async () => {
65-
const { container, rerender } = renderComponent();
66-
67-
expect(container.querySelector('.fa-home')).toBeVisible();
68-
69-
projectsStore.currentProject = { type: ProjectTypes.Personal } as Project;
70-
await rerender({});
71-
expect(container.querySelector('.fa-user')).toBeVisible();
72-
73-
const projectName = 'My Project';
74-
projectsStore.currentProject = { name: projectName } as Project;
75-
await rerender({});
76-
expect(container.querySelector('.fa-layer-group')).toBeVisible();
77-
});
78-
7964
it('should render the correct title and subtitle', async () => {
8065
const { getByText, queryByText, rerender } = renderComponent();
8166
const subtitle = 'All the workflows, credentials and executions you have access to';

packages/frontend/editor-ui/src/components/Projects/ProjectHeader.vue

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useRoute, useRouter } from 'vue-router';
44
import type { UserAction } from '@n8n/design-system';
55
import { N8nButton, N8nTooltip } from '@n8n/design-system';
66
import { useI18n } from '@/composables/useI18n';
7-
import { type ProjectIcon, ProjectTypes } from '@/types/projects.types';
7+
import { ProjectTypes } from '@/types/projects.types';
88
import { useProjectsStore } from '@/stores/projects.store';
99
import ProjectTabs from '@/components/Projects/ProjectTabs.vue';
1010
import { getResourcePermissions } from '@/permissions';
@@ -24,16 +24,6 @@ const emit = defineEmits<{
2424
createFolder: [];
2525
}>();
2626
27-
const headerIcon = computed((): ProjectIcon => {
28-
if (projectsStore.currentProject?.type === ProjectTypes.Personal) {
29-
return { type: 'icon', value: 'user' };
30-
} else if (projectsStore.currentProject?.name) {
31-
return projectsStore.currentProject.icon ?? { type: 'icon', value: 'layer-group' };
32-
} else {
33-
return { type: 'icon', value: 'home' };
34-
}
35-
});
36-
3727
const projectName = computed(() => {
3828
if (!projectsStore.currentProject) {
3929
return i18n.baseText('projects.menu.overview');
@@ -136,7 +126,6 @@ const onSelect = (action: string) => {
136126
<div>
137127
<div :class="$style.projectHeader">
138128
<div :class="$style.projectDetails">
139-
<ProjectIcon :icon="headerIcon" :border-less="true" size="medium" />
140129
<div :class="$style.headerActions">
141130
<N8nHeading bold tag="h2" size="xlarge">{{ projectName }}</N8nHeading>
142131
<N8nText color="text-light">
@@ -168,6 +157,7 @@ const onSelect = (action: string) => {
168157
</N8nTooltip>
169158
</div>
170159
</div>
160+
<slot></slot>
171161
<div :class="$style.actions">
172162
<ProjectTabs :show-settings="showSettings" />
173163
</div>

packages/frontend/editor-ui/src/components/executions/global/GlobalExecutionsList.vue

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import ConcurrentExecutionsHeader from '@/components/executions/ConcurrentExecutionsHeader.vue';
33
import ExecutionsFilter from '@/components/executions/ExecutionsFilter.vue';
44
import GlobalExecutionsListItem from '@/components/executions/global/GlobalExecutionsListItem.vue';
5-
import ProjectHeader from '@/components/Projects/ProjectHeader.vue';
65
import { useI18n } from '@/composables/useI18n';
76
import { useMessage } from '@/composables/useMessage';
87
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
@@ -334,7 +333,7 @@ const goToUpgrade = () => {
334333

335334
<template>
336335
<div :class="$style.execListWrapper">
337-
<ProjectHeader />
336+
<slot />
338337
<div :class="$style.execListHeaderControls">
339338
<ExecutionsFilter
340339
:workflows="workflows"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { computed, reactive } from 'vue';
2+
import { useRoute } from 'vue-router';
3+
import { VIEWS } from '@/constants';
4+
5+
export const useOverview = () => {
6+
const route = useRoute();
7+
8+
const isOverviewSubPage = computed(
9+
() =>
10+
route.name === VIEWS.WORKFLOWS ||
11+
route.name === VIEWS.HOMEPAGE ||
12+
route.name === VIEWS.CREDENTIALS ||
13+
route.name === VIEWS.EXECUTIONS ||
14+
route.name === VIEWS.FOLDERS,
15+
);
16+
17+
return reactive({
18+
isOverviewSubPage,
19+
});
20+
};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import InsightsSummary from '@/features/insights/components/InsightsSummary.vue';
2+
import { createComponentRenderer } from '@/__tests__/render';
3+
import type { InsightsSummaryDisplay } from '@/features/insights/insights.types';
4+
5+
const renderComponent = createComponentRenderer(InsightsSummary);
6+
7+
describe('InsightsSummary', () => {
8+
it('should render without error', () => {
9+
expect(() =>
10+
renderComponent({
11+
props: {
12+
summary: [],
13+
},
14+
}),
15+
).not.toThrow();
16+
});
17+
18+
test.each<InsightsSummaryDisplay[]>([
19+
[[]],
20+
[
21+
[
22+
{ id: 'total', value: 525, deviation: 85, unit: '' },
23+
{ id: 'failed', value: 14, deviation: 3, unit: '' },
24+
{ id: 'failureRate', value: 1.9, deviation: -0.8, unit: '%' },
25+
{ id: 'timeSaved', value: 55.55555555555556, deviation: -5.164722222222222, unit: 'h' },
26+
{ id: 'averageRunTime', value: 2.5, deviation: -0.5, unit: 's' },
27+
],
28+
],
29+
[
30+
[
31+
{ id: 'total', value: 525, deviation: 85, unit: '' },
32+
{ id: 'failed', value: 14, deviation: 3, unit: '' },
33+
{ id: 'failureRate', value: 1.9, deviation: -0.8, unit: '%' },
34+
{ id: 'timeSaved', value: 0, deviation: 0, unit: 'h' },
35+
{ id: 'averageRunTime', value: 2.5, deviation: -0.5, unit: 's' },
36+
],
37+
],
38+
[
39+
[
40+
{ id: 'total', value: 525, deviation: -2, unit: '' },
41+
{ id: 'failed', value: 14, deviation: -3, unit: '' },
42+
{ id: 'failureRate', value: 1.9, deviation: 0.8, unit: '%' },
43+
{ id: 'timeSaved', value: 55.55555555555556, deviation: 0, unit: 'h' },
44+
{ id: 'averageRunTime', value: 2.5, deviation: 0.5, unit: 's' },
45+
],
46+
],
47+
])('should render the summary correctly', (summary) => {
48+
const { html } = renderComponent({
49+
props: {
50+
summary,
51+
},
52+
});
53+
54+
expect(html()).toMatchSnapshot();
55+
});
56+
});

0 commit comments

Comments
 (0)