mirror of
https://github.com/umami-software/umami.git
synced 2026-02-06 21:57:16 +01:00
Added download functionality.
This commit is contained in:
parent
0debe89d05
commit
7670ec4136
17 changed files with 216 additions and 5 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { useContext } from 'react';
|
||||
import { ReportContext } from './Report';
|
||||
import styles from './ReportBody.module.css';
|
||||
import { DownloadButton } from '@/components/input/DownloadButton';
|
||||
|
||||
export function ReportBody({ children }) {
|
||||
const { report } = useContext(ReportContext);
|
||||
|
|
@ -9,7 +10,12 @@ export function ReportBody({ children }) {
|
|||
return null;
|
||||
}
|
||||
|
||||
return <div className={styles.body}>{children}</div>;
|
||||
return (
|
||||
<div className={styles.body}>
|
||||
<DownloadButton filename={report.name} data={report.data} />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ReportBody;
|
||||
|
|
|
|||
|
|
@ -31,5 +31,9 @@ export default function ReportPage({ reportId }: { reportId: string }) {
|
|||
|
||||
const ReportComponent = reports[report.type];
|
||||
|
||||
if (!ReportComponent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <ReportComponent reportId={reportId} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Dropdown, Item } from 'react-basics';
|
||||
import classNames from 'classnames';
|
||||
import { useDateRange, useMessages, useSticky } from '@/components/hooks';
|
||||
import { useDateRange, useMessages, useNavigation, useSticky } from '@/components/hooks';
|
||||
import WebsiteDateFilter from '@/components/input/WebsiteDateFilter';
|
||||
import MetricCard from '@/components/metrics/MetricCard';
|
||||
import MetricsBar from '@/components/metrics/MetricsBar';
|
||||
|
|
@ -8,6 +8,7 @@ import { formatShortTime, formatLongNumber } from '@/lib/format';
|
|||
import useWebsiteStats from '@/components/hooks/queries/useWebsiteStats';
|
||||
import useStore, { setWebsiteDateCompare } from '@/store/websites';
|
||||
import WebsiteFilterButton from './WebsiteFilterButton';
|
||||
import { ExportButton } from '@/components/input/ExportButton';
|
||||
import styles from './WebsiteMetricsBar.module.css';
|
||||
|
||||
export function WebsiteMetricsBar({
|
||||
|
|
@ -31,6 +32,9 @@ export function WebsiteMetricsBar({
|
|||
websiteId,
|
||||
compareMode && dateCompare,
|
||||
);
|
||||
const {
|
||||
query: { view },
|
||||
} = useNavigation();
|
||||
const isAllTime = dateRange.value === 'all';
|
||||
|
||||
const { pageviews, visitors, visits, bounces, totaltime } = data || {};
|
||||
|
|
@ -109,7 +113,10 @@ export function WebsiteMetricsBar({
|
|||
</MetricsBar>
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
{showFilter && <WebsiteFilterButton websiteId={websiteId} />}
|
||||
<div>
|
||||
{showFilter && <WebsiteFilterButton websiteId={websiteId} />}
|
||||
{!view && <ExportButton websiteId={websiteId} />}
|
||||
</div>
|
||||
<WebsiteDateFilter websiteId={websiteId} showAllTime={!compareMode} />
|
||||
{compareMode && (
|
||||
<div className={styles.vs}>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export default function WebsiteTableView({ websiteId }: { websiteId: string }) {
|
|||
const pathname = usePathname();
|
||||
const tableProps = {
|
||||
websiteId,
|
||||
allowDownload: false,
|
||||
limit: 10,
|
||||
};
|
||||
const isSharePage = pathname.includes('/share/');
|
||||
|
|
|
|||
75
src/app/api/websites/[websiteId]/export/route.ts
Normal file
75
src/app/api/websites/[websiteId]/export/route.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { z } from 'zod';
|
||||
import JSZip from 'jszip';
|
||||
import Papa from 'papaparse';
|
||||
import { getRequestFilters, parseRequest } from '@/lib/request';
|
||||
import { unauthorized, json } from '@/lib/response';
|
||||
import { canViewWebsite } from '@/lib/auth';
|
||||
import { pagingParams } from '@/lib/schema';
|
||||
import { getEventMetrics, getWebsiteEvents } from '@/queries';
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ websiteId: string }> },
|
||||
) {
|
||||
const schema = z.object({
|
||||
startAt: z.coerce.number().int(),
|
||||
endAt: z.coerce.number().int(),
|
||||
...pagingParams,
|
||||
});
|
||||
|
||||
const { auth, query, error } = await parseRequest(request, schema);
|
||||
|
||||
if (error) {
|
||||
return error();
|
||||
}
|
||||
|
||||
const { websiteId } = await params;
|
||||
const { startAt, endAt } = query;
|
||||
|
||||
if (!(await canViewWebsite(auth, websiteId))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
const startDate = new Date(+startAt);
|
||||
const endDate = new Date(+endAt);
|
||||
const limit = 10;
|
||||
const offset = 0;
|
||||
|
||||
const filters = {
|
||||
...(await getRequestFilters(query)),
|
||||
startDate,
|
||||
endDate,
|
||||
};
|
||||
|
||||
const [events, pages, referrers, browsers, os, devices, countries] = await Promise.all([
|
||||
getWebsiteEvents(websiteId, { startDate, endDate }, query),
|
||||
getEventMetrics(websiteId, 'url', filters, limit, offset),
|
||||
getEventMetrics(websiteId, 'referrer', filters, limit, offset),
|
||||
getEventMetrics(websiteId, 'browser', filters, limit, offset),
|
||||
getEventMetrics(websiteId, 'os', filters, limit, offset),
|
||||
getEventMetrics(websiteId, 'device', filters, limit, offset),
|
||||
getEventMetrics(websiteId, 'country', filters, limit, offset),
|
||||
]);
|
||||
|
||||
const zip = new JSZip();
|
||||
|
||||
const parse = (data: any) => {
|
||||
return Papa.unparse(data, {
|
||||
header: true,
|
||||
skipEmptyLines: true,
|
||||
});
|
||||
};
|
||||
|
||||
zip.file('events.csv', parse(events?.data));
|
||||
zip.file('pages.csv', parse(pages));
|
||||
zip.file('referrers.csv', parse(referrers));
|
||||
zip.file('browsers.csv', parse(browsers));
|
||||
zip.file('os.csv', parse(os));
|
||||
zip.file('devices.csv', parse(devices));
|
||||
zip.file('countries.csv', parse(countries));
|
||||
|
||||
const content = await zip.generateAsync({ type: 'nodebuffer' });
|
||||
const base64 = content.toString('base64');
|
||||
|
||||
return json({ zip: base64 });
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue