Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/dashboard/src/data/configurations/configuration-queries.ts
2501 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 { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
8
import { useCurrentOrg } from "../organizations/orgs-query";
9
import { configurationClient } from "../../service/public-api";
10
import { SortOrder } from "@gitpod/public-api/lib/gitpod/v1/sorting_pb";
11
import { TableSortOrder } from "@podkit/tables/SortableTable";
12
import type { Configuration, UpdateConfigurationRequest } from "@gitpod/public-api/lib/gitpod/v1/configuration_pb";
13
import type { PartialMessage } from "@bufbuild/protobuf";
14
import { envVarClient } from "../../service/public-api";
15
import {
16
ConfigurationEnvironmentVariable,
17
EnvironmentVariableAdmission,
18
} from "@gitpod/public-api/lib/gitpod/v1/envvar_pb";
19
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
20
21
const BASE_KEY = "configurations";
22
23
type ListConfigurationsArgs = {
24
pageSize?: number;
25
searchTerm?: string;
26
prebuildsEnabled?: boolean;
27
sortBy: string;
28
sortOrder: TableSortOrder;
29
};
30
31
export const useListConfigurations = (options: ListConfigurationsArgs) => {
32
const { data: org } = useCurrentOrg();
33
const { searchTerm = "", prebuildsEnabled, pageSize, sortBy, sortOrder } = options;
34
35
return useInfiniteQuery(
36
getListConfigurationsQueryKey(org?.id ?? "", options),
37
// QueryFn receives the past page's pageParam as it's argument
38
async ({ pageParam: nextToken }) => {
39
if (!org) {
40
throw new Error("No org currently selected");
41
}
42
43
const { configurations, pagination } = await configurationClient.listConfigurations({
44
organizationId: org.id,
45
searchTerm,
46
prebuildsEnabled,
47
pagination: { pageSize, token: nextToken },
48
sort: [
49
{
50
field: sortBy,
51
order: sortOrder === "desc" ? SortOrder.DESC : SortOrder.ASC,
52
},
53
],
54
});
55
56
return {
57
configurations,
58
pagination,
59
};
60
},
61
{
62
enabled: !!org,
63
keepPreviousData: true,
64
// This enables the query to know if there are more pages, and passes the last page's nextToken to the queryFn
65
getNextPageParam: (lastPage) => {
66
// Must ensure we return undefined if there are no more pages
67
return lastPage.pagination?.nextToken || undefined;
68
},
69
},
70
);
71
};
72
73
export const getListConfigurationsQueryKey = (orgId: string, args?: ListConfigurationsArgs) => {
74
const key: any[] = [BASE_KEY, "list", { orgId }];
75
if (args) {
76
key.push(args);
77
}
78
79
return key;
80
};
81
82
export const getListConfigurationsVariablesQueryKey = (configurationId: string) => {
83
return [BASE_KEY, "variable", "list", { configurationId }];
84
};
85
86
export const useConfiguration = (configurationId?: string) => {
87
// As we want to return "undefined" from this query/cache, and useQuery doesn't allow that, we need:
88
// - use "null" internally/in the cache
89
// - transform it to "undefined" using the "select" option
90
return useQuery<Configuration | null, Error, Configuration | undefined>(
91
getConfigurationQueryKey(configurationId),
92
async () => {
93
if (!configurationId) {
94
return null;
95
}
96
97
const { configuration } = await configurationClient.getConfiguration({
98
configurationId,
99
});
100
101
return configuration || null;
102
},
103
{
104
select: (data) => data || undefined,
105
retry: (failureCount, error) => {
106
if (!configurationId) {
107
return false;
108
}
109
110
if (failureCount > 3) {
111
return false;
112
}
113
114
if (error && [ErrorCodes.NOT_FOUND, ErrorCodes.PERMISSION_DENIED].includes((error as any).code)) {
115
return false;
116
}
117
118
return true;
119
},
120
cacheTime: 1000 * 60 * 5, // 5m
121
staleTime: 1000 * 30, // 30s
122
},
123
);
124
};
125
126
type DeleteConfigurationArgs = {
127
configurationId: string;
128
};
129
export const useDeleteConfiguration = () => {
130
const queryClient = useQueryClient();
131
132
return useMutation({
133
mutationFn: async ({ configurationId }: DeleteConfigurationArgs) => {
134
return await configurationClient.deleteConfiguration({
135
configurationId,
136
});
137
},
138
onSuccess: (_, { configurationId }) => {
139
// todo: look into updating the cache instead of invalidating it
140
queryClient.invalidateQueries({ queryKey: ["configurations", "list"] });
141
queryClient.invalidateQueries({ queryKey: getConfigurationQueryKey(configurationId) });
142
},
143
});
144
};
145
146
export type PartialConfiguration = PartialMessage<UpdateConfigurationRequest> &
147
Pick<UpdateConfigurationRequest, "configurationId">;
148
149
export const useConfigurationMutation = () => {
150
const queryClient = useQueryClient();
151
152
return useMutation<Configuration, Error, PartialConfiguration>({
153
mutationFn: async (configuration) => {
154
const updated = await configurationClient.updateConfiguration({
155
configurationId: configuration.configurationId,
156
name: configuration.name,
157
workspaceSettings: configuration.workspaceSettings,
158
prebuildSettings: configuration.prebuildSettings,
159
});
160
161
if (!updated.configuration) {
162
throw new Error("Failed to update configuration");
163
}
164
165
queryClient.invalidateQueries({ queryKey: ["configurations", "list"] });
166
167
return updated.configuration;
168
},
169
onSuccess: (configuration) => {
170
if (configuration) {
171
queryClient.setQueryData(getConfigurationQueryKey(configuration.id), configuration);
172
}
173
},
174
});
175
};
176
177
export const getConfigurationQueryKey = (configurationId?: string) => {
178
const key: any[] = [BASE_KEY, { configurationId: configurationId || "undefined" }];
179
180
return key;
181
};
182
183
export const getConfigurationVariableQueryKey = (variableId: string) => {
184
const key: any[] = [BASE_KEY, "variable", { configurationId: variableId }];
185
186
return key;
187
};
188
189
export type CreateConfigurationArgs = {
190
name: string;
191
cloneUrl: string;
192
};
193
194
export const useCreateConfiguration = () => {
195
const { data: org } = useCurrentOrg();
196
const queryClient = useQueryClient();
197
198
return useMutation<Configuration, Error, CreateConfigurationArgs>({
199
mutationFn: async ({ name, cloneUrl }) => {
200
if (!org) {
201
throw new Error("No org currently selected");
202
}
203
204
const response = await configurationClient.createConfiguration({
205
name,
206
cloneUrl,
207
organizationId: org.id,
208
});
209
if (!response.configuration) {
210
throw new Error("Failed to create configuration");
211
}
212
213
return response.configuration;
214
},
215
onSuccess: (configuration) => {
216
queryClient.setQueryData(getConfigurationQueryKey(configuration.id), configuration);
217
queryClient.invalidateQueries({ queryKey: ["configurations", "list"] });
218
},
219
});
220
};
221
222
export const useListConfigurationVariables = (configurationId: string) => {
223
return useQuery<ConfigurationEnvironmentVariable[]>(getListConfigurationsVariablesQueryKey(configurationId), {
224
queryFn: async () => {
225
const { environmentVariables } = await envVarClient.listConfigurationEnvironmentVariables({
226
configurationId,
227
});
228
229
return environmentVariables;
230
},
231
cacheTime: 1000 * 60 * 60 * 24, // one day
232
});
233
};
234
235
type DeleteVariableArgs = {
236
variableId: string;
237
configurationId: string;
238
};
239
export const useDeleteConfigurationVariable = () => {
240
const queryClient = useQueryClient();
241
242
return useMutation<void, Error, DeleteVariableArgs>({
243
mutationFn: async ({ variableId }) => {
244
void (await envVarClient.deleteConfigurationEnvironmentVariable({
245
environmentVariableId: variableId,
246
}));
247
},
248
onSuccess: (_, { configurationId, variableId }) => {
249
queryClient.invalidateQueries({ queryKey: getListConfigurationsVariablesQueryKey(configurationId) });
250
queryClient.invalidateQueries({ queryKey: getConfigurationVariableQueryKey(variableId) });
251
},
252
});
253
};
254
255
type CreateVariableArgs = {
256
configurationId: string;
257
name: string;
258
value: string;
259
admission: EnvironmentVariableAdmission;
260
};
261
export const useCreateConfigurationVariable = () => {
262
const queryClient = useQueryClient();
263
264
return useMutation<ConfigurationEnvironmentVariable, Error, CreateVariableArgs>({
265
mutationFn: async ({ configurationId, name, value, admission }) => {
266
const { environmentVariable } = await envVarClient.createConfigurationEnvironmentVariable({
267
configurationId,
268
name,
269
value,
270
admission,
271
});
272
if (!environmentVariable) {
273
throw new Error("Failed to create environment variable");
274
}
275
276
return environmentVariable;
277
},
278
onSuccess: (_, { configurationId }) => {
279
queryClient.invalidateQueries({ queryKey: getListConfigurationsVariablesQueryKey(configurationId) });
280
},
281
});
282
};
283
284
type UpdateVariableArgs = CreateVariableArgs & {
285
variableId: string;
286
};
287
export const useUpdateConfigurationVariable = () => {
288
const queryClient = useQueryClient();
289
290
return useMutation<ConfigurationEnvironmentVariable, Error, UpdateVariableArgs>({
291
mutationFn: async ({ variableId, name, value, admission, configurationId }: UpdateVariableArgs) => {
292
const { environmentVariable } = await envVarClient.updateConfigurationEnvironmentVariable({
293
environmentVariableId: variableId,
294
configurationId,
295
name,
296
value,
297
admission,
298
});
299
if (!environmentVariable) {
300
throw new Error("Failed to update environment variable");
301
}
302
303
return environmentVariable;
304
},
305
onSuccess: (_, { configurationId, variableId }) => {
306
queryClient.invalidateQueries({ queryKey: getListConfigurationsVariablesQueryKey(configurationId) });
307
queryClient.invalidateQueries({ queryKey: getConfigurationVariableQueryKey(variableId) });
308
},
309
});
310
};
311
312