Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/dashboard/src/usage/download/DownloadUsage.tsx
2500 views
1
/**
2
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
3
* Licensed under the GNU Affero General Public License (AGPL).
4
* See License.AGPL.txt in the project root for license information.
5
*/
6
7
import { FC, useCallback, useEffect, useMemo, useState } from "react";
8
import { useDownloadUsageCSV } from "./download-usage-csv";
9
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
10
import { Dayjs } from "dayjs";
11
import { useToast } from "../../components/toasts/Toasts";
12
import { useCurrentOrg } from "../../data/organizations/orgs-query";
13
import { ReactComponent as DownloadIcon } from "../../icons/Download.svg";
14
import { ReactComponent as ExclamationIcon } from "../../images/exclamation.svg";
15
import { LinkButton } from "../../components/LinkButton";
16
import { saveAs } from "file-saver";
17
import prettyBytes from "pretty-bytes";
18
import { ProgressBar } from "../../components/ProgressBar";
19
import { useTemporaryState } from "../../hooks/use-temporary-value";
20
import { Button } from "@podkit/buttons/Button";
21
22
type Props = {
23
attributionId: AttributionId;
24
startDate: Dayjs;
25
endDate: Dayjs;
26
};
27
export const DownloadUsage: FC<Props> = ({ attributionId, startDate, endDate }) => {
28
const { data: org } = useCurrentOrg();
29
const { toast } = useToast();
30
// When we start the download, we disable the button for a short time
31
const [downloadDisabled, setDownloadDisabled] = useTemporaryState(false, 1000);
32
33
const handleDownload = useCallback(async () => {
34
if (!org) {
35
return;
36
}
37
38
setDownloadDisabled(true);
39
toast(
40
<DownloadUsageToast
41
orgName={org?.slug ?? org?.id}
42
attributionId={attributionId}
43
startDate={startDate}
44
endDate={endDate}
45
/>,
46
{
47
autoHide: false,
48
},
49
);
50
}, [attributionId, endDate, org, setDownloadDisabled, startDate, toast]);
51
52
return (
53
// TODO: Convert this to use an IconButton when we add one to podkit
54
<Button variant="secondary" onClick={handleDownload} className="gap-1" disabled={downloadDisabled}>
55
<DownloadIcon />
56
<span>Export as CSV</span>
57
</Button>
58
);
59
};
60
61
type DownloadUsageToastProps = Props & {
62
orgName: string;
63
};
64
const DownloadUsageToast: FC<DownloadUsageToastProps> = ({ attributionId, endDate, startDate, orgName }) => {
65
const [progress, setProgress] = useState(0);
66
67
const queryArgs = useMemo(
68
() => ({
69
orgName,
70
attributionId: AttributionId.render(attributionId),
71
from: startDate.startOf("day").valueOf(),
72
to: endDate.endOf("day").valueOf(),
73
onProgress: setProgress,
74
}),
75
[attributionId, endDate, orgName, startDate],
76
);
77
const { data, error, isLoading, abort, remove } = useDownloadUsageCSV(queryArgs);
78
79
const saveFile = useCallback(() => {
80
if (!data || !data.blob) {
81
return;
82
}
83
84
saveAs(data.blob, data.filename);
85
}, [data]);
86
87
useEffect(() => {
88
return () => {
89
abort();
90
remove();
91
};
92
// eslint-disable-next-line react-hooks/exhaustive-deps
93
}, []);
94
95
if (isLoading) {
96
return (
97
<div>
98
<span>Preparing usage export</span>
99
<ProgressBar inverted value={progress} />
100
</div>
101
);
102
}
103
104
if (error) {
105
return (
106
<div className="flex flex-row items-start space-x-2">
107
<ExclamationIcon className="w-5 h-5 mt-0.5" />
108
<div>
109
<span>Error exporting your usage data:</span>
110
<pre className="mt-2 whitespace-normal text-sm">{error.message}</pre>
111
</div>
112
</div>
113
);
114
}
115
116
if (!data || !data.blob || data.count === 0) {
117
return <span>No usage data for the selected period.</span>;
118
}
119
120
const readableSize = prettyBytes(data.blob.size);
121
const formattedCount = Intl.NumberFormat().format(data.count);
122
123
return (
124
<div className="flex flex-row items-start justify-between space-x-2">
125
<div>
126
<span>Usage export complete.</span>
127
<p className="text-pk-content-invert-primary/90">
128
{readableSize} &middot; {formattedCount} {data.count !== 1 ? "entries" : "entry"} exported
129
</p>
130
</div>
131
<div>
132
<LinkButton inverted onClick={saveFile} className="text-left text-base">
133
Download CSV
134
</LinkButton>
135
</div>
136
</div>
137
);
138
};
139
140