Path: blob/main/components/dashboard/src/data/configurations/configuration-queries.ts
2501 views
/**1* Copyright (c) 2023 Gitpod GmbH. All rights reserved.2* Licensed under the GNU Affero General Public License (AGPL).3* See License.AGPL.txt in the project root for license information.4*/56import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";7import { useCurrentOrg } from "../organizations/orgs-query";8import { configurationClient } from "../../service/public-api";9import { SortOrder } from "@gitpod/public-api/lib/gitpod/v1/sorting_pb";10import { TableSortOrder } from "@podkit/tables/SortableTable";11import type { Configuration, UpdateConfigurationRequest } from "@gitpod/public-api/lib/gitpod/v1/configuration_pb";12import type { PartialMessage } from "@bufbuild/protobuf";13import { envVarClient } from "../../service/public-api";14import {15ConfigurationEnvironmentVariable,16EnvironmentVariableAdmission,17} from "@gitpod/public-api/lib/gitpod/v1/envvar_pb";18import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";1920const BASE_KEY = "configurations";2122type ListConfigurationsArgs = {23pageSize?: number;24searchTerm?: string;25prebuildsEnabled?: boolean;26sortBy: string;27sortOrder: TableSortOrder;28};2930export const useListConfigurations = (options: ListConfigurationsArgs) => {31const { data: org } = useCurrentOrg();32const { searchTerm = "", prebuildsEnabled, pageSize, sortBy, sortOrder } = options;3334return useInfiniteQuery(35getListConfigurationsQueryKey(org?.id ?? "", options),36// QueryFn receives the past page's pageParam as it's argument37async ({ pageParam: nextToken }) => {38if (!org) {39throw new Error("No org currently selected");40}4142const { configurations, pagination } = await configurationClient.listConfigurations({43organizationId: org.id,44searchTerm,45prebuildsEnabled,46pagination: { pageSize, token: nextToken },47sort: [48{49field: sortBy,50order: sortOrder === "desc" ? SortOrder.DESC : SortOrder.ASC,51},52],53});5455return {56configurations,57pagination,58};59},60{61enabled: !!org,62keepPreviousData: true,63// This enables the query to know if there are more pages, and passes the last page's nextToken to the queryFn64getNextPageParam: (lastPage) => {65// Must ensure we return undefined if there are no more pages66return lastPage.pagination?.nextToken || undefined;67},68},69);70};7172export const getListConfigurationsQueryKey = (orgId: string, args?: ListConfigurationsArgs) => {73const key: any[] = [BASE_KEY, "list", { orgId }];74if (args) {75key.push(args);76}7778return key;79};8081export const getListConfigurationsVariablesQueryKey = (configurationId: string) => {82return [BASE_KEY, "variable", "list", { configurationId }];83};8485export const useConfiguration = (configurationId?: string) => {86// As we want to return "undefined" from this query/cache, and useQuery doesn't allow that, we need:87// - use "null" internally/in the cache88// - transform it to "undefined" using the "select" option89return useQuery<Configuration | null, Error, Configuration | undefined>(90getConfigurationQueryKey(configurationId),91async () => {92if (!configurationId) {93return null;94}9596const { configuration } = await configurationClient.getConfiguration({97configurationId,98});99100return configuration || null;101},102{103select: (data) => data || undefined,104retry: (failureCount, error) => {105if (!configurationId) {106return false;107}108109if (failureCount > 3) {110return false;111}112113if (error && [ErrorCodes.NOT_FOUND, ErrorCodes.PERMISSION_DENIED].includes((error as any).code)) {114return false;115}116117return true;118},119cacheTime: 1000 * 60 * 5, // 5m120staleTime: 1000 * 30, // 30s121},122);123};124125type DeleteConfigurationArgs = {126configurationId: string;127};128export const useDeleteConfiguration = () => {129const queryClient = useQueryClient();130131return useMutation({132mutationFn: async ({ configurationId }: DeleteConfigurationArgs) => {133return await configurationClient.deleteConfiguration({134configurationId,135});136},137onSuccess: (_, { configurationId }) => {138// todo: look into updating the cache instead of invalidating it139queryClient.invalidateQueries({ queryKey: ["configurations", "list"] });140queryClient.invalidateQueries({ queryKey: getConfigurationQueryKey(configurationId) });141},142});143};144145export type PartialConfiguration = PartialMessage<UpdateConfigurationRequest> &146Pick<UpdateConfigurationRequest, "configurationId">;147148export const useConfigurationMutation = () => {149const queryClient = useQueryClient();150151return useMutation<Configuration, Error, PartialConfiguration>({152mutationFn: async (configuration) => {153const updated = await configurationClient.updateConfiguration({154configurationId: configuration.configurationId,155name: configuration.name,156workspaceSettings: configuration.workspaceSettings,157prebuildSettings: configuration.prebuildSettings,158});159160if (!updated.configuration) {161throw new Error("Failed to update configuration");162}163164queryClient.invalidateQueries({ queryKey: ["configurations", "list"] });165166return updated.configuration;167},168onSuccess: (configuration) => {169if (configuration) {170queryClient.setQueryData(getConfigurationQueryKey(configuration.id), configuration);171}172},173});174};175176export const getConfigurationQueryKey = (configurationId?: string) => {177const key: any[] = [BASE_KEY, { configurationId: configurationId || "undefined" }];178179return key;180};181182export const getConfigurationVariableQueryKey = (variableId: string) => {183const key: any[] = [BASE_KEY, "variable", { configurationId: variableId }];184185return key;186};187188export type CreateConfigurationArgs = {189name: string;190cloneUrl: string;191};192193export const useCreateConfiguration = () => {194const { data: org } = useCurrentOrg();195const queryClient = useQueryClient();196197return useMutation<Configuration, Error, CreateConfigurationArgs>({198mutationFn: async ({ name, cloneUrl }) => {199if (!org) {200throw new Error("No org currently selected");201}202203const response = await configurationClient.createConfiguration({204name,205cloneUrl,206organizationId: org.id,207});208if (!response.configuration) {209throw new Error("Failed to create configuration");210}211212return response.configuration;213},214onSuccess: (configuration) => {215queryClient.setQueryData(getConfigurationQueryKey(configuration.id), configuration);216queryClient.invalidateQueries({ queryKey: ["configurations", "list"] });217},218});219};220221export const useListConfigurationVariables = (configurationId: string) => {222return useQuery<ConfigurationEnvironmentVariable[]>(getListConfigurationsVariablesQueryKey(configurationId), {223queryFn: async () => {224const { environmentVariables } = await envVarClient.listConfigurationEnvironmentVariables({225configurationId,226});227228return environmentVariables;229},230cacheTime: 1000 * 60 * 60 * 24, // one day231});232};233234type DeleteVariableArgs = {235variableId: string;236configurationId: string;237};238export const useDeleteConfigurationVariable = () => {239const queryClient = useQueryClient();240241return useMutation<void, Error, DeleteVariableArgs>({242mutationFn: async ({ variableId }) => {243void (await envVarClient.deleteConfigurationEnvironmentVariable({244environmentVariableId: variableId,245}));246},247onSuccess: (_, { configurationId, variableId }) => {248queryClient.invalidateQueries({ queryKey: getListConfigurationsVariablesQueryKey(configurationId) });249queryClient.invalidateQueries({ queryKey: getConfigurationVariableQueryKey(variableId) });250},251});252};253254type CreateVariableArgs = {255configurationId: string;256name: string;257value: string;258admission: EnvironmentVariableAdmission;259};260export const useCreateConfigurationVariable = () => {261const queryClient = useQueryClient();262263return useMutation<ConfigurationEnvironmentVariable, Error, CreateVariableArgs>({264mutationFn: async ({ configurationId, name, value, admission }) => {265const { environmentVariable } = await envVarClient.createConfigurationEnvironmentVariable({266configurationId,267name,268value,269admission,270});271if (!environmentVariable) {272throw new Error("Failed to create environment variable");273}274275return environmentVariable;276},277onSuccess: (_, { configurationId }) => {278queryClient.invalidateQueries({ queryKey: getListConfigurationsVariablesQueryKey(configurationId) });279},280});281};282283type UpdateVariableArgs = CreateVariableArgs & {284variableId: string;285};286export const useUpdateConfigurationVariable = () => {287const queryClient = useQueryClient();288289return useMutation<ConfigurationEnvironmentVariable, Error, UpdateVariableArgs>({290mutationFn: async ({ variableId, name, value, admission, configurationId }: UpdateVariableArgs) => {291const { environmentVariable } = await envVarClient.updateConfigurationEnvironmentVariable({292environmentVariableId: variableId,293configurationId,294name,295value,296admission,297});298if (!environmentVariable) {299throw new Error("Failed to update environment variable");300}301302return environmentVariable;303},304onSuccess: (_, { configurationId, variableId }) => {305queryClient.invalidateQueries({ queryKey: getListConfigurationsVariablesQueryKey(configurationId) });306queryClient.invalidateQueries({ queryKey: getConfigurationVariableQueryKey(variableId) });307},308});309};310311312