Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/dashboard/src/usage/download/download-usage-csv.ts
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 { useCallback } from "react";
8
import dayjs from "dayjs";
9
import { useQuery, useQueryClient } from "@tanstack/react-query";
10
import { ListUsageRequest } from "@gitpod/gitpod-protocol/lib/usage";
11
import { getAllUsageRecords } from "./get-usage-records";
12
import { transformUsageRecord, UsageCSVRow } from "./transform-usage-record";
13
import { noPersistence } from "../../data/setup";
14
15
type Args = Pick<ListUsageRequest, "attributionId" | "from" | "to"> & {
16
orgName: string;
17
signal?: AbortSignal;
18
onProgress?: (percentage: number) => void;
19
};
20
21
export type DownloadUsageCSVResponse = {
22
blob: Blob | null;
23
filename: string;
24
count: number;
25
};
26
27
const downloadUsageCSV = async ({
28
attributionId,
29
from,
30
to,
31
orgName,
32
signal,
33
onProgress,
34
}: Args): Promise<DownloadUsageCSVResponse> => {
35
const start = dayjs(from).format("YYYYMMDD");
36
const end = dayjs(to).format("YYYYMMDD");
37
const filename = `gitpod-usage-${orgName}-${start}-${end}.csv`;
38
39
const records = await getAllUsageRecords({
40
attributionId,
41
from,
42
to,
43
signal,
44
onProgress,
45
});
46
47
if (records.length === 0) {
48
return {
49
blob: null,
50
filename,
51
count: 0,
52
};
53
}
54
55
const rows = records.map(transformUsageRecord).filter((r) => !!r);
56
const fields = Object.keys(rows[0]) as (keyof UsageCSVRow)[];
57
58
// TODO: look into a lib to handle this more robustly
59
// CSV Rows
60
const csvRows = rows.map((row) => {
61
const rowString = fields.map((fieldName) => JSON.stringify(row[fieldName])).join(",");
62
63
return rowString;
64
});
65
66
// Prepend Header
67
csvRows.unshift(fields.join(","));
68
69
const blob = new Blob([`\ufeff${csvRows.join("\n")}`], {
70
type: "text/csv;charset=utf-8",
71
});
72
73
return {
74
blob,
75
filename,
76
count: rows.length,
77
};
78
};
79
80
export const useDownloadUsageCSV = (args: Args) => {
81
const client = useQueryClient();
82
const key = getDownloadUsageCSVQueryKey(args);
83
84
const abort = useCallback(() => {
85
client.removeQueries([key]);
86
}, [client, key]);
87
88
const query = useQuery<DownloadUsageCSVResponse, Error>(
89
key,
90
async ({ signal }) => {
91
const resp = await downloadUsageCSV({ ...args, signal });
92
93
// Introduce a slight artificial delay when completed to allow progress to finish the transition to 100%
94
// While this feels a bit odd here instead of in the component, it's much easier to add
95
// the delay here than track it in react state
96
// 1000ms because the transition duration is 1000ms
97
await new Promise((r) => setTimeout(r, 1000));
98
99
return resp;
100
},
101
{
102
retry: false,
103
cacheTime: 0,
104
staleTime: 0,
105
},
106
);
107
108
return {
109
...query,
110
abort,
111
};
112
};
113
114
const getDownloadUsageCSVQueryKey = (args: Args) => {
115
return noPersistence(["usage-export", args]);
116
};
117
118