Skip to content

Commit bdbd1b7

Browse files
Merge pull request #197 from pneumaticapp/frontend/workflows/45762__Add_the_ability_to_export_csv
45762 frontend [ workflows ] Add the ability to export csv
2 parents 5c6a591 + f4fe19a commit bdbd1b7

12 files changed

Lines changed: 376 additions & 111 deletions

File tree

frontend/src/public/components/Workflows/WorkflowsTablePage/WorkflowsTable/WorkflowsTable.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@
219219

220220
.container__actions--wide-table {
221221
position: sticky;
222+
z-index: 5;
222223
left: 0;
223224
margin-bottom: 1.6rem;
224225
padding: 0 2.4rem 0 3.2rem;

frontend/src/public/components/Workflows/WorkflowsTablePage/WorkflowsTable/WorkflowsTableActions.tsx

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
import React, { useMemo } from 'react';
1+
import React, { useCallback, useMemo } from 'react';
22
import { useSelector, useDispatch } from 'react-redux';
33
import { useIntl } from 'react-intl';
44

55
import { openTuneViewModal } from '../../../../redux/workflows/slice';
66
import { EWorkflowsLoadingStatus } from '../../../../types/workflow';
77
import { EPageTitle } from '../../../../constants/defaultValues';
8-
import { Button, Tooltip } from '../../../UI';
8+
import { Button, Dropdown, Tooltip } from '../../../UI';
99
import { TuneViewIcon, DownloadIcon } from '../../../icons';
1010
import { PageTitle } from '../../../PageTitle';
1111
import { getWorkflowTemplatesIdsFilter } from '../../../../redux/selectors/workflows';
1212
import { useWorkflowsExport } from '../../../../hooks/useWorkflowsExport';
13+
import { TWorkflowExportFormat } from '../../../../utils/workflows/exportWorkflowsToExcel';
1314
import { WorkflowsTableActionsProps } from './types';
1415

1516
import styles from './WorkflowsTable.css';
@@ -35,9 +36,24 @@ export function WorkflowsTableActions({
3536
[workflowsLoadingStatus, isExporting],
3637
);
3738

38-
const handleTuneViewClick = () => {
39+
const handleTuneViewClick = useCallback(() => {
3940
dispatch(openTuneViewModal());
40-
};
41+
}, [dispatch]);
42+
43+
const exportDropdownOptions = useMemo(
44+
() =>
45+
(['xlsx', 'csv'] as const).map((exportFormat: TWorkflowExportFormat) => ({
46+
mapKey: exportFormat,
47+
label: formatMessage({
48+
id: exportFormat === 'csv' ? 'workflows.export-as-csv' : 'workflows.export-as-excel',
49+
}),
50+
onClick: (setClosed: () => void) => {
51+
setClosed();
52+
handleExportClick(exportFormat);
53+
},
54+
})),
55+
[formatMessage, handleExportClick],
56+
);
4157

4258
const tuneViewButton = (
4359
<Button
@@ -52,22 +68,42 @@ export function WorkflowsTableActions({
5268
/>
5369
);
5470

55-
const exportExcelButton = (
56-
<Button
57-
className={styles['tune-view-button']}
58-
buttonStyle="transparent-black"
59-
label={
60-
isMobile
61-
? ''
62-
: formatMessage({ id: isExporting ? 'workflows.export-excel-loading' : 'workflows.export-excel' })
63-
}
64-
size="sm"
65-
disabled={isExportDisabled}
66-
icon={DownloadIcon}
67-
onClick={handleExportClick}
71+
const exportButtonLabel = isMobile
72+
? ''
73+
: formatMessage({ id: isExporting ? 'workflows.export-loading' : 'workflows.export-menu' });
74+
75+
const exportControl = (
76+
<Dropdown
77+
isDisabled={isExportDisabled}
78+
direction="right"
79+
className={styles['export-dropdown']}
80+
toggleProps={{
81+
...(isMobile
82+
? {
83+
'aria-label': formatMessage({
84+
id: isExporting ? 'workflows.export-loading' : 'workflows.export-menu-aria',
85+
}),
86+
}
87+
: {}),
88+
}}
89+
options={exportDropdownOptions}
90+
renderToggle={() => (
91+
<Button
92+
className={styles['tune-view-button']}
93+
buttonStyle="transparent-black"
94+
size="sm"
95+
icon={DownloadIcon}
96+
label={exportButtonLabel}
97+
disabled={isExportDisabled}
98+
/>
99+
)}
68100
/>
69101
);
70102

103+
const exportTooltipContentId = isExporting
104+
? 'workflows.export-loading'
105+
: 'workflows.export-tooltip';
106+
71107
return (
72108
<div
73109
className={isWideTable ? styles['container__actions--wide-table'] : styles['container__actions--narrow-table']}
@@ -88,19 +124,21 @@ export function WorkflowsTableActions({
88124
)}
89125
{isExportDisabled ? (
90126
<Tooltip
91-
content={formatMessage({
92-
id: isExporting ? 'workflows.export-excel-loading' : 'workflows.export-excel-tooltip',
93-
})}
127+
content={formatMessage({ id: exportTooltipContentId })}
128+
appendTo={() => document.body}
129+
zIndex={1040}
94130
contentClassName={styles['workflow-tune-view-tooltip']}
95131
>
96-
<div>{exportExcelButton}</div>
132+
<div>{exportControl}</div>
97133
</Tooltip>
98134
) : (
99135
<Tooltip
100-
content={formatMessage({ id: 'workflows.export-excel-tooltip-all' })}
136+
content={formatMessage({ id: 'workflows.export-tooltip-all' })}
137+
appendTo={() => document.body}
138+
zIndex={1040}
101139
contentClassName={styles['workflow-tune-view-tooltip']}
102140
>
103-
<div>{exportExcelButton}</div>
141+
<div>{exportControl}</div>
104142
</Tooltip>
105143
)}
106144
</div>

frontend/src/public/hooks/useWorkflowsExport.ts

Lines changed: 68 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import {
2121
buildWorkflowsExportRows,
2222
buildWorkflowsXlsxBuffer,
2323
downloadWorkflowsExcel,
24+
TWorkflowExportFormat,
2425
} from '../utils/workflows/exportWorkflowsToExcel';
26+
import { downloadWorkflowsCsv } from '../utils/workflows/exportWorkflowsToCsv';
2527
import { NotificationManager } from '../components/UI/Notifications';
2628
import { ALL_SYSTEM_FIELD_NAMES } from '../components/Workflows/WorkflowsTablePage/WorkflowsTable/constants';
2729

@@ -62,68 +64,75 @@ export function useWorkflowsExport() {
6264
const groups = useSelector(getGroupsList);
6365
const timezone = useSelector(getTimezone);
6466

65-
const handleExportClick = useCallback(async () => {
66-
setIsExporting(true);
67-
try {
68-
const fields = selectedFields.length ? selectedFields : ALL_SYSTEM_FIELD_NAMES;
69-
const items = await fetchAllWorkflowsForExport({
70-
statusFilter,
71-
sorting,
72-
templatesIdsFilter,
73-
tasksApiNamesFilter,
74-
performersIdsFilter,
75-
performersGroupIdsFilter,
76-
workflowStartersIdsFilter,
77-
searchText,
78-
fields,
79-
});
67+
const handleExportClick = useCallback(
68+
async (exportFormat: TWorkflowExportFormat) => {
69+
setIsExporting(true);
70+
try {
71+
const fields = selectedFields.length ? selectedFields : ALL_SYSTEM_FIELD_NAMES;
72+
const items = await fetchAllWorkflowsForExport({
73+
statusFilter,
74+
sorting,
75+
templatesIdsFilter,
76+
tasksApiNamesFilter,
77+
performersIdsFilter,
78+
performersGroupIdsFilter,
79+
workflowStartersIdsFilter,
80+
searchText,
81+
fields,
82+
});
83+
84+
if (items.length === 0) {
85+
NotificationManager.warning({
86+
title: formatMessage({ id: 'workflows.export-excel-empty-title' }),
87+
message: formatMessage({ id: 'workflows.export-excel-empty-message' }),
88+
});
89+
return;
90+
}
8091

81-
if (items.length === 0) {
82-
NotificationManager.warning({
83-
title: formatMessage({ id: 'workflows.export-excel-empty-title' }),
84-
message: formatMessage({ id: 'workflows.export-excel-empty-message' }),
92+
const optionalFieldsFromWorkflow = items[0]?.fields ?? [];
93+
const headerLabels = getExportHeaderLabels(formatMessage, optionalFieldsFromWorkflow);
94+
const rows = buildWorkflowsExportRows({
95+
workflows: items,
96+
users,
97+
groups,
98+
selectedFields: fields,
99+
optionalFieldsFromWorkflow,
100+
timezone: timezone ?? undefined,
101+
headerLabels,
102+
multipleTasksLabel: formatMessage({ id: 'workflows.multiple-active-tasks' }),
103+
deletedGroupFallbackTemplate: formatMessage(
104+
{ id: 'workflows.export-deleted-group' },
105+
{ id: '{id}' },
106+
),
85107
});
86-
return;
108+
if (exportFormat === 'csv') {
109+
downloadWorkflowsCsv(rows);
110+
} else {
111+
const buffer = await buildWorkflowsXlsxBuffer(rows);
112+
downloadWorkflowsExcel(buffer);
113+
}
114+
} catch (error) {
115+
NotificationManager.notifyApiError(error, { message: 'workflows.export-excel-fail' });
116+
} finally {
117+
setIsExporting(false);
87118
}
88-
89-
const optionalFieldsFromWorkflow = items[0]?.fields ?? [];
90-
const headerLabels = getExportHeaderLabels(formatMessage, optionalFieldsFromWorkflow);
91-
const rows = buildWorkflowsExportRows({
92-
workflows: items,
93-
users,
94-
groups,
95-
selectedFields: fields,
96-
optionalFieldsFromWorkflow,
97-
timezone: timezone ?? undefined,
98-
headerLabels,
99-
multipleTasksLabel: formatMessage({ id: 'workflows.multiple-active-tasks' }),
100-
deletedGroupFallbackTemplate: formatMessage(
101-
{ id: 'workflows.export-deleted-group' },
102-
{ id: '{id}' },
103-
),
104-
});
105-
const buffer = await buildWorkflowsXlsxBuffer(rows);
106-
downloadWorkflowsExcel(buffer);
107-
} catch (error) {
108-
NotificationManager.notifyApiError(error, { message: 'workflows.export-excel-fail' });
109-
} finally {
110-
setIsExporting(false);
111-
}
112-
}, [
113-
formatMessage,
114-
selectedFields,
115-
statusFilter,
116-
sorting,
117-
templatesIdsFilter,
118-
tasksApiNamesFilter,
119-
performersIdsFilter,
120-
performersGroupIdsFilter,
121-
workflowStartersIdsFilter,
122-
searchText,
123-
users,
124-
groups,
125-
timezone,
126-
]);
119+
},
120+
[
121+
formatMessage,
122+
selectedFields,
123+
statusFilter,
124+
sorting,
125+
templatesIdsFilter,
126+
tasksApiNamesFilter,
127+
performersIdsFilter,
128+
performersGroupIdsFilter,
129+
workflowStartersIdsFilter,
130+
searchText,
131+
users,
132+
groups,
133+
timezone,
134+
],
135+
);
127136

128137
return { handleExportClick, isExporting };
129138
}

frontend/src/public/lang/locales/en_US.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -920,10 +920,15 @@ export const enMessages = {
920920
'workflows.comment-reaction-button': 'Toggle reaction',
921921
'workflow.tune-view-button': 'Tune View',
922922
'workflow.tune-view-tooltip': 'To fine-tune the table view — select a Template',
923+
'workflows.export-menu': 'export',
924+
'workflows.export-menu-aria': 'Export workflows',
925+
'workflows.export-as-excel': 'Excel (.xlsx)',
926+
'workflows.export-as-csv': 'CSV (.csv)',
927+
'workflows.export-loading': 'Exporting…',
928+
'workflows.export-tooltip': 'Load workflows first to export',
929+
'workflows.export-tooltip-all': 'Export all workflows matching current filters',
923930
'workflows.export-excel': 'Export Excel',
924931
'workflows.export-excel-loading': 'Exporting…',
925-
'workflows.export-excel-tooltip': 'Load workflows first to export',
926-
'workflows.export-excel-tooltip-all': 'Export all workflows matching current filters to Excel',
927932
'workflows.export-excel-fail': 'Failed to export workflows',
928933
'workflows.export-excel-empty-title': 'No workflows to export',
929934
'workflows.export-excel-empty-message': 'No workflows match the current filters.',

frontend/src/public/lang/locales/ru_RU.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -854,10 +854,15 @@ export const ruMessages = {
854854
'workflows.filter-column-template-name': 'Шаблон',
855855
'workflows.filter-column-step': 'Шаг',
856856
'workflows.filter-column-performers': 'Исполнители',
857+
'workflows.export-menu': 'Экспорт',
858+
'workflows.export-menu-aria': 'Экспорт процессов',
859+
'workflows.export-as-excel': 'Excel (.xlsx)',
860+
'workflows.export-as-csv': 'CSV (.csv)',
861+
'workflows.export-loading': 'Экспорт…',
862+
'workflows.export-tooltip': 'Загрузите процессы для экспорта',
863+
'workflows.export-tooltip-all': 'Экспорт всех процессов по текущим фильтрам',
857864
'workflows.export-excel': 'Экспорт в Excel',
858865
'workflows.export-excel-loading': 'Экспорт…',
859-
'workflows.export-excel-tooltip': 'Загрузите процессы для экспорта',
860-
'workflows.export-excel-tooltip-all': 'Экспорт всех процессов по текущим фильтрам в Excel',
861866
'workflows.export-excel-fail': 'Не удалось экспортировать процессы',
862867
'workflows.export-excel-empty-title': 'Нет процессов для экспорта',
863868
'workflows.export-excel-empty-message': 'По текущим фильтрам процессы не найдены.',

0 commit comments

Comments
 (0)