feat: Support Checkly group variables (ENG-3478) (#4418)

* feat: checkly group sync

* fix: remove scope discriminator

* fix: forms

* fix: queries

* fix: 500 error

* fix: update docs

* lint: fix

* fix: review changes

* fix: PR changes

* fix: resolve group select UI not clearing

---------

Co-authored-by: Scott Wilson <[email protected]>
This commit is contained in:
Sid
2025-08-28 21:55:53 +05:30
committed by GitHub
co-authored by Scott Wilson
parent c7987772e3
commit a0da2f2d4c
15 changed files with 428 additions and 47 deletions
@@ -5,14 +5,15 @@ import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/Se
import { FilterableSelect, FormControl } from "@app/components/v2";
import {
TChecklyAccount,
useChecklyConnectionListAccounts
useChecklyConnectionListAccounts,
useChecklyConnectionListGroups
} from "@app/hooks/api/appConnections/checkly";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { TSecretSyncForm } from "../schemas";
export const ChecklySyncFields = () => {
const { control, setValue } = useFormContext<
const { control, setValue, watch } = useFormContext<
TSecretSyncForm & { destination: SecretSync.Checkly }
>();
@@ -25,12 +26,24 @@ export const ChecklySyncFields = () => {
}
);
const accountId = watch("destinationConfig.accountId");
const { data: groups = [], isPending: isGroupsLoading } = useChecklyConnectionListGroups(
connectionId,
accountId,
{
enabled: Boolean(connectionId && accountId)
}
);
return (
<>
<SecretSyncConnectionField
onChange={() => {
setValue("destinationConfig.accountId", "");
setValue("destinationConfig.accountName", "");
setValue("destinationConfig.groupId", undefined);
setValue("destinationConfig.groupName", undefined);
}}
/>
<Controller
@@ -60,6 +73,37 @@ export const ChecklySyncFields = () => {
</FormControl>
)}
/>
<Controller
name="destinationConfig.groupId"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label="Select a group"
isOptional
helperText="If provided, secrets will be scoped to a check group instead"
tooltipClassName="max-w-md"
>
<FilterableSelect
isLoading={isGroupsLoading && Boolean(connectionId)}
isDisabled={!connectionId}
isClearable
value={groups.find((p) => p.id === value) ?? null}
onChange={(option) => {
const v = option as SingleValue<TChecklyAccount>;
onChange(v?.id ?? null);
setValue("destinationConfig.groupName", v?.name ?? undefined);
}}
options={groups}
placeholder="Select a group..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
/>
</FormControl>
)}
/>
</>
);
};
@@ -6,7 +6,16 @@ import { SecretSync } from "@app/hooks/api/secretSyncs";
export const ChecklySyncReviewFields = () => {
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Checkly }>();
const accountName = watch("destinationConfig.accountName");
const config = watch("destinationConfig");
return <GenericFieldLabel label="Account">{accountName}</GenericFieldLabel>;
return (
<>
<GenericFieldLabel label="Account">
{config.accountName ?? config.accountId}
</GenericFieldLabel>
{config.groupId && (
<GenericFieldLabel label="Group">{config.groupName ?? config.groupId}</GenericFieldLabel>
)}
</>
);
};
@@ -8,7 +8,15 @@ export const ChecklySyncDestinationSchema = BaseSecretSyncSchema().merge(
destination: z.literal(SecretSync.Checkly),
destinationConfig: z.object({
accountId: z.string(),
accountName: z.string()
accountName: z.string(),
groupId: z
.string()
.nullish()
.transform((val) => val || undefined),
groupName: z
.string()
.nullish()
.transform((val) => val || undefined)
})
})
);
@@ -8,7 +8,9 @@ import { TChecklyAccount } from "./types";
const checklyConnectionKeys = {
all: [...appConnectionKeys.all, "checkly"] as const,
listAccounts: (connectionId: string) =>
[...checklyConnectionKeys.all, "workspace-scopes", connectionId] as const
[...checklyConnectionKeys.all, "workspace-scopes", connectionId] as const,
listGroups: (connectionId: string, accountId: string) =>
[...checklyConnectionKeys.all, "groups", connectionId, accountId] as const
};
export const useChecklyConnectionListAccounts = (
@@ -35,3 +37,29 @@ export const useChecklyConnectionListAccounts = (
...options
});
};
export const useChecklyConnectionListGroups = (
connectionId: string,
accountId: string,
options?: Omit<
UseQueryOptions<
TChecklyAccount[],
unknown,
TChecklyAccount[],
ReturnType<typeof checklyConnectionKeys.listGroups>
>,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: checklyConnectionKeys.listGroups(connectionId, accountId),
queryFn: async () => {
const { data } = await apiRequest.get<{ groups: TChecklyAccount[] }>(
`/api/v1/app-connections/checkly/${connectionId}/accounts/${accountId}/groups`
);
return data.groups;
},
...options
});
};
@@ -3,11 +3,17 @@ import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
export enum ChecklySyncScope {
Global = "global",
Group = "group"
}
export type TChecklySync = TRootSecretSync & {
destination: SecretSync.Checkly;
destinationConfig: {
accountId: string;
accountName: string;
groupId?: string;
groupName?: string;
};
connection: {
app: AppConnection.Checkly;
@@ -175,8 +175,8 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
secondaryText = "Railway Project";
break;
case SecretSync.Checkly:
primaryText = destinationConfig.accountName;
secondaryText = "Checkly Account";
primaryText = destinationConfig.accountName || destinationConfig.accountId;
secondaryText = destinationConfig.groupName || destinationConfig.groupId || "Checkly Account";
break;
case SecretSync.Supabase:
primaryText = destinationConfig.projectName;
@@ -8,5 +8,12 @@ type Props = {
export const ChecklySyncDestinationSection = ({ secretSync }: Props) => {
const { destinationConfig } = secretSync;
return <GenericFieldLabel label="Account">{destinationConfig.accountName}</GenericFieldLabel>;
return (
<>
<GenericFieldLabel label="Account">{destinationConfig.accountName}</GenericFieldLabel>
{destinationConfig.groupId && (
<GenericFieldLabel label="Group">{destinationConfig.groupName}</GenericFieldLabel>
)}
</>
);
};