Skip to content

Commit

Permalink
Merge pull request #5191 from kobotoolbox/task-884-implement-reusable…
Browse files Browse the repository at this point in the history
…-export-button

[TASK-884] Implement reusable export button
  • Loading branch information
pauloamorimbr authored Oct 30, 2024
2 parents 1c7ccea + 72d942a commit a2c4ca0
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 6 deletions.
28 changes: 27 additions & 1 deletion jsapp/js/components/activity/activityLogs.query.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {keepPreviousData, useQuery} from '@tanstack/react-query';
import type {KoboSelectOption} from 'js/components/common/koboSelect';
import type {PaginatedResponse} from 'js/dataInterface';
import type {FailResponse, PaginatedResponse} from 'js/dataInterface';
import moment from 'moment';
import {QueryKeys} from 'js/query/queryKeys';

Expand Down Expand Up @@ -70,6 +70,26 @@ const getFilterOptions = async () =>
setTimeout(() => resolve(mockOptions), 1000);
});

/**
* Starts the exporting process of the activity logs.
* @returns {Promise<void>} The promise that starts the export
*/
const startActivityLogsExport = async () =>
new Promise<void>((resolve, reject) => {
// Simulates backend export process.
setTimeout(() => {
if (Math.random() > 0.5) {
resolve();
} else {
const failResponse: FailResponse = {
status: 500,
statusText: 'Mocked error',
};
reject(failResponse);
}
}, 500);
});

/**
*
* This is a hook that fetches activity logs from the server.
Expand All @@ -94,3 +114,9 @@ export const useActivityLogsFilterOptionsQuery = () =>
queryKey: [QueryKeys.activityLogsFilter],
queryFn: () => getFilterOptions(),
});

/**
* This is a hook to start the exporting process of the activity logs.
* @returns {() => void} The function to start the export
*/
export const useExportActivityLogs = () => startActivityLogsExport;
11 changes: 6 additions & 5 deletions jsapp/js/components/activity/formActivity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ import '../../../scss/components/_kobo.form-view.scss';
import type {KoboSelectOption} from '../common/koboSelect';
import KoboSelect from '../common/koboSelect';
import type {UniversalTableColumn} from 'jsapp/js/universalTable/universalTable.component';
import Button from '../common/button';
import PaginatedQueryUniversalTable from 'jsapp/js/universalTable/paginatedQueryUniversalTable.component';
import type {ActivityLogsItem} from './activityLogs.query';
import {
useActivityLogsFilterOptionsQuery,
useActivityLogsQuery,
useExportActivityLogs,
} from './activityLogs.query';
import styles from './formActivity.module.scss';
import cx from 'classnames';
import {formatTime} from 'jsapp/js/utils';
import Avatar from '../common/avatar';
import ExportToEmailButton from '../exportToEmailButton/exportToEmailButton.component';

const EventDescription = ({
who,
Expand Down Expand Up @@ -56,6 +57,8 @@ export default function FormActivity() {
const [selectedFilterOption, setSelectedFilterOption] =
useState<KoboSelectOption | null>(null);

const exportData = useExportActivityLogs();

const handleFilterChange = (value: string | null) => {
setSelectedFilterOption(
filterOptions?.find((option) => option.value === value) || null
Expand All @@ -78,11 +81,9 @@ export default function FormActivity() {
placeholder={t('Filter by')}
options={filterOptions || []}
/>
<Button
size='m'
type='primary'
startIcon='download'
<ExportToEmailButton
label={t('Export all data')}
exportFunction={exportData}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import Button from '../common/button';
import KoboPrompt from '../modals/koboPrompt';
import {useState} from 'react';
import {handleApiFail} from 'jsapp/js/api';
import type {FailResponse} from 'jsapp/js/dataInterface';

const MessageModal = ({onClose}: {onClose: () => void}) => (
<KoboPrompt
isOpen
onRequestClose={onClose}
title={t('Exporting data')}
buttons={[
{
label: 'Ok',
onClick: onClose,
},
]}
>
{t(
"Your export request is currently being processed. Once the export is complete, you'll receive an email with all the details."
)}
</KoboPrompt>
);

/**
* Button to be used in views that export data to email.
* The button receives a label and an export function that should return a promise.
* The function is called when the button is clicked and if no error occurs, a message is shown to the user.
*/
export default function ExportToEmailButton({
exportFunction,
label,
}: {
exportFunction: () => Promise<void>;
label: string;
}) {
const [isMessageOpen, setIsMessageOpen] = useState(false);
const [isPending, setIsPending] = useState(false);

const handleClick = () => {
setIsPending(true);
exportFunction()
.then(() => {
setIsMessageOpen(true);
})
.catch((error) => handleApiFail(error as FailResponse))
.finally(() => {
setIsPending(false);
});
};

return (
<>
<Button
size='m'
type='primary'
label={label}
startIcon='download'
onClick={handleClick}
isPending={isPending}
/>
{isMessageOpen && (
<MessageModal onClose={() => setIsMessageOpen(false)} />
)}
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type {Meta, StoryFn} from '@storybook/react';

import ExportToEmailButton from './exportToEmailButton.component';

export default {
title: 'misc/ExportToEmailButton',
component: ExportToEmailButton,
argTypes: {
label: {
control: 'text',
},
},
} as Meta<typeof ExportToEmailButton>;

const Template: StoryFn<typeof ExportToEmailButton> = (args) => (
<ExportToEmailButton {...args} />
);

export const Primary = Template.bind({});
Primary.args = {
label: 'Export all data',
exportFunction: () => new Promise((resolve) => setTimeout(resolve, 500)),
};

0 comments on commit a2c4ca0

Please sign in to comment.