mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
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 <scottraywilson@gmail.com>
This commit is contained in:
@@ -53,4 +53,36 @@ export const registerChecklyConnectionRouter = async (server: FastifyZodProvider
|
|||||||
return { accounts };
|
return { accounts };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/accounts/:accountId/groups`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid(),
|
||||||
|
accountId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
groups: z
|
||||||
|
.object({
|
||||||
|
name: z.string(),
|
||||||
|
id: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId, accountId } = req.params;
|
||||||
|
|
||||||
|
const groups = await server.services.appConnection.checkly.listGroups(connectionId, accountId, req.permission);
|
||||||
|
|
||||||
|
return { groups };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { AxiosInstance, AxiosRequestConfig, AxiosResponse, HttpStatusCode, isAxi
|
|||||||
|
|
||||||
import { createRequestClient } from "@app/lib/config/request";
|
import { createRequestClient } from "@app/lib/config/request";
|
||||||
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||||
|
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
|
||||||
|
|
||||||
import { ChecklyConnectionMethod } from "./checkly-connection-constants";
|
import { ChecklyConnectionMethod } from "./checkly-connection-constants";
|
||||||
import { TChecklyAccount, TChecklyConnectionConfig, TChecklyVariable } from "./checkly-connection-types";
|
import { TChecklyAccount, TChecklyConnectionConfig, TChecklyVariable } from "./checkly-connection-types";
|
||||||
@@ -181,6 +182,122 @@ class ChecklyPublicClient {
|
|||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getCheckGroups(connection: TChecklyConnectionConfig, accountId: string, limit = 50, page = 1) {
|
||||||
|
const res = await this.send<{ id: number; name: string }[]>(connection, {
|
||||||
|
accountId,
|
||||||
|
method: "GET",
|
||||||
|
url: `/v1/check-groups`,
|
||||||
|
params: { limit, page }
|
||||||
|
});
|
||||||
|
|
||||||
|
return res?.map((group) => ({
|
||||||
|
id: group.id.toString(),
|
||||||
|
name: group.name
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCheckGroup(connection: TChecklyConnectionConfig, accountId: string, groupId: string) {
|
||||||
|
try {
|
||||||
|
type ChecklyGroupResponse = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
environmentVariables: Array<{
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
locked: boolean;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await this.send<ChecklyGroupResponse>(connection, {
|
||||||
|
accountId,
|
||||||
|
method: "GET",
|
||||||
|
url: `/v1/check-groups/${groupId}`
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: res.id.toString(),
|
||||||
|
name: res.name,
|
||||||
|
environmentVariables: res.environmentVariables
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (isAxiosError(error) && error.response?.status === HttpStatusCode.NotFound) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateCheckGroupEnvironmentVariables(
|
||||||
|
connection: TChecklyConnectionConfig,
|
||||||
|
accountId: string,
|
||||||
|
groupId: string,
|
||||||
|
environmentVariables: Array<{ key: string; value: string; locked?: boolean }>
|
||||||
|
) {
|
||||||
|
if (environmentVariables.length > 50) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
message: "Checkly does not support syncing more than 50 variables to Check Group",
|
||||||
|
shouldRetry: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiVariables = environmentVariables.map((v) => ({
|
||||||
|
key: v.key,
|
||||||
|
value: v.value,
|
||||||
|
locked: v.locked ?? false,
|
||||||
|
secret: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
const group = await this.getCheckGroup(connection, accountId, groupId);
|
||||||
|
|
||||||
|
await this.send(connection, {
|
||||||
|
accountId,
|
||||||
|
method: "PUT",
|
||||||
|
url: `/v2/check-groups/${groupId}`,
|
||||||
|
data: { name: group?.name, environmentVariables: apiVariables }
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.getCheckGroup(connection, accountId, groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCheckGroupEnvironmentVariables(connection: TChecklyConnectionConfig, accountId: string, groupId: string) {
|
||||||
|
const group = await this.getCheckGroup(connection, accountId, groupId);
|
||||||
|
return group?.environmentVariables || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async upsertCheckGroupEnvironmentVariables(
|
||||||
|
connection: TChecklyConnectionConfig,
|
||||||
|
accountId: string,
|
||||||
|
groupId: string,
|
||||||
|
variables: Array<{ key: string; value: string; locked?: boolean }>
|
||||||
|
) {
|
||||||
|
const existingVars = await this.getCheckGroupEnvironmentVariables(connection, accountId, groupId);
|
||||||
|
const varMap = new Map(existingVars.map((v) => [v.key, v]));
|
||||||
|
|
||||||
|
for (const newVar of variables) {
|
||||||
|
varMap.set(newVar.key, {
|
||||||
|
key: newVar.key,
|
||||||
|
value: newVar.value,
|
||||||
|
locked: newVar.locked ?? false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.updateCheckGroupEnvironmentVariables(connection, accountId, groupId, Array.from(varMap.values()));
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteCheckGroupEnvironmentVariable(
|
||||||
|
connection: TChecklyConnectionConfig,
|
||||||
|
accountId: string,
|
||||||
|
groupId: string,
|
||||||
|
variableKey: string
|
||||||
|
) {
|
||||||
|
const existingVars = await this.getCheckGroupEnvironmentVariables(connection, accountId, groupId);
|
||||||
|
const filteredVars = existingVars.filter((v) => v.key !== variableKey);
|
||||||
|
|
||||||
|
return this.updateCheckGroupEnvironmentVariables(connection, accountId, groupId, filteredVars);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChecklyPublicAPI = new ChecklyPublicClient();
|
export const ChecklyPublicAPI = new ChecklyPublicClient();
|
||||||
|
|||||||
@@ -24,7 +24,19 @@ export const checklyConnectionService = (getAppConnection: TGetAppConnectionFunc
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const listGroups = async (connectionId: string, accountId: string, actor: OrgServiceActor) => {
|
||||||
|
const appConnection = await getAppConnection(AppConnection.Checkly, connectionId, actor);
|
||||||
|
try {
|
||||||
|
const groups = await ChecklyPublicAPI.getCheckGroups(appConnection, accountId);
|
||||||
|
return groups!;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error, "Failed to list accounts on Checkly");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
listAccounts
|
listAccounts,
|
||||||
|
listGroups
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,3 +33,15 @@ export type TChecklyAccount = {
|
|||||||
name: string;
|
name: string;
|
||||||
runtimeId: string;
|
runtimeId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TChecklyGroupEnvironmentVariable = {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
locked: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TChecklyGroup = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
environmentVariables?: TChecklyGroupEnvironmentVariable[];
|
||||||
|
};
|
||||||
|
|||||||
@@ -23,56 +23,120 @@ export const ChecklySyncFns = {
|
|||||||
|
|
||||||
const config = secretSync.destinationConfig;
|
const config = secretSync.destinationConfig;
|
||||||
|
|
||||||
const variables = await ChecklyPublicAPI.getVariables(secretSync.connection, config.accountId);
|
if (config.groupId) {
|
||||||
|
// Handle group environment variables
|
||||||
|
const groupVars = await ChecklyPublicAPI.getCheckGroupEnvironmentVariables(
|
||||||
|
secretSync.connection,
|
||||||
|
config.accountId,
|
||||||
|
config.groupId
|
||||||
|
);
|
||||||
|
|
||||||
const checklySecrets = Object.fromEntries(variables!.map((variable) => [variable.key, variable]));
|
const checklyGroupSecrets = Object.fromEntries(groupVars.map((variable) => [variable.key, variable]));
|
||||||
|
|
||||||
for await (const key of Object.keys(secretMap)) {
|
// Prepare all variables to update at once
|
||||||
try {
|
const updatedVariables = { ...checklyGroupSecrets };
|
||||||
|
|
||||||
|
for (const key of Object.keys(secretMap)) {
|
||||||
const entry = secretMap[key];
|
const entry = secretMap[key];
|
||||||
|
|
||||||
// If value is empty, we skip the upsert - checkly does not allow empty values
|
// If value is empty, we skip adding it - checkly does not allow empty values
|
||||||
if (entry.value.trim() === "") {
|
if (entry.value.trim() === "") {
|
||||||
// Delete the secret from Checkly if its empty
|
// Delete the secret from the group if it's empty
|
||||||
if (!disableSecretDeletion) {
|
if (!disableSecretDeletion) {
|
||||||
await ChecklyPublicAPI.deleteVariable(secretSync.connection, config.accountId, {
|
delete updatedVariables[key];
|
||||||
key
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
continue; // Skip empty values
|
continue; // Skip empty values
|
||||||
}
|
}
|
||||||
|
|
||||||
await ChecklyPublicAPI.upsertVariable(secretSync.connection, config.accountId, {
|
// Add or update the variable
|
||||||
|
updatedVariables[key] = {
|
||||||
key,
|
key,
|
||||||
value: entry.value,
|
value: entry.value,
|
||||||
secret: true,
|
|
||||||
locked: true
|
locked: true
|
||||||
});
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove secrets that are not in the secretMap if deletion is enabled
|
||||||
|
if (!disableSecretDeletion) {
|
||||||
|
for (const key of Object.keys(checklyGroupSecrets)) {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
if (!matchesSchema(key, environment?.slug || "", keySchema)) continue;
|
||||||
|
|
||||||
|
if (!secretMap[key]) {
|
||||||
|
delete updatedVariables[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update all group environment variables at once
|
||||||
|
try {
|
||||||
|
await ChecklyPublicAPI.updateCheckGroupEnvironmentVariables(
|
||||||
|
secretSync.connection,
|
||||||
|
config.accountId,
|
||||||
|
config.groupId,
|
||||||
|
Object.values(updatedVariables)
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof SecretSyncError) throw error;
|
||||||
|
|
||||||
throw new SecretSyncError({
|
throw new SecretSyncError({
|
||||||
error,
|
error,
|
||||||
secretKey: key
|
secretKey: "group_update"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
// Handle global variables (existing logic)
|
||||||
|
const variables = await ChecklyPublicAPI.getVariables(secretSync.connection, config.accountId);
|
||||||
|
|
||||||
if (disableSecretDeletion) return;
|
const checklySecrets = Object.fromEntries(variables!.map((variable) => [variable.key, variable]));
|
||||||
|
|
||||||
for await (const key of Object.keys(checklySecrets)) {
|
for await (const key of Object.keys(secretMap)) {
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line no-continue
|
const entry = secretMap[key];
|
||||||
if (!matchesSchema(key, environment?.slug || "", keySchema)) continue;
|
|
||||||
|
|
||||||
if (!secretMap[key]) {
|
// If value is empty, we skip the upsert - checkly does not allow empty values
|
||||||
await ChecklyPublicAPI.deleteVariable(secretSync.connection, config.accountId, {
|
if (entry.value.trim() === "") {
|
||||||
key
|
// Delete the secret from Checkly if its empty
|
||||||
|
if (!disableSecretDeletion) {
|
||||||
|
await ChecklyPublicAPI.deleteVariable(secretSync.connection, config.accountId, {
|
||||||
|
key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
continue; // Skip empty values
|
||||||
|
}
|
||||||
|
|
||||||
|
await ChecklyPublicAPI.upsertVariable(secretSync.connection, config.accountId, {
|
||||||
|
key,
|
||||||
|
value: entry.value,
|
||||||
|
secret: true,
|
||||||
|
locked: true
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error,
|
||||||
|
secretKey: key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disableSecretDeletion) return;
|
||||||
|
|
||||||
|
for await (const key of Object.keys(checklySecrets)) {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
if (!matchesSchema(key, environment?.slug || "", keySchema)) continue;
|
||||||
|
|
||||||
|
if (!secretMap[key]) {
|
||||||
|
await ChecklyPublicAPI.deleteVariable(secretSync.connection, config.accountId, {
|
||||||
|
key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error,
|
||||||
|
secretKey: key
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
throw new SecretSyncError({
|
|
||||||
error,
|
|
||||||
secretKey: key
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -80,23 +144,54 @@ export const ChecklySyncFns = {
|
|||||||
async removeSecrets(secretSync: TChecklySyncWithCredentials, secretMap: TSecretMap) {
|
async removeSecrets(secretSync: TChecklySyncWithCredentials, secretMap: TSecretMap) {
|
||||||
const config = secretSync.destinationConfig;
|
const config = secretSync.destinationConfig;
|
||||||
|
|
||||||
const variables = await ChecklyPublicAPI.getVariables(secretSync.connection, config.accountId);
|
if (config.groupId) {
|
||||||
|
// Handle group environment variables
|
||||||
|
const groupVars = await ChecklyPublicAPI.getCheckGroupEnvironmentVariables(
|
||||||
|
secretSync.connection,
|
||||||
|
config.accountId,
|
||||||
|
config.groupId
|
||||||
|
);
|
||||||
|
|
||||||
const checklySecrets = Object.fromEntries(variables!.map((variable) => [variable.key, variable]));
|
const checklyGroupSecrets = Object.fromEntries(groupVars.map((variable) => [variable.key, variable]));
|
||||||
|
|
||||||
|
// Filter out the secrets to remove
|
||||||
|
const remainingVariables = Object.keys(checklyGroupSecrets)
|
||||||
|
.filter((key) => !(key in secretMap))
|
||||||
|
.map((key) => checklyGroupSecrets[key]);
|
||||||
|
|
||||||
for await (const secret of Object.keys(checklySecrets)) {
|
|
||||||
try {
|
try {
|
||||||
if (secret in secretMap) {
|
await ChecklyPublicAPI.updateCheckGroupEnvironmentVariables(
|
||||||
await ChecklyPublicAPI.deleteVariable(secretSync.connection, config.accountId, {
|
secretSync.connection,
|
||||||
key: secret
|
config.accountId,
|
||||||
});
|
config.groupId,
|
||||||
}
|
remainingVariables
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new SecretSyncError({
|
throw new SecretSyncError({
|
||||||
error,
|
error,
|
||||||
secretKey: secret
|
secretKey: "group_remove"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Handle global variables (existing logic)
|
||||||
|
const variables = await ChecklyPublicAPI.getVariables(secretSync.connection, config.accountId);
|
||||||
|
|
||||||
|
const checklySecrets = Object.fromEntries(variables!.map((variable) => [variable.key, variable]));
|
||||||
|
|
||||||
|
for await (const secret of Object.keys(checklySecrets)) {
|
||||||
|
try {
|
||||||
|
if (secret in secretMap) {
|
||||||
|
await ChecklyPublicAPI.deleteVariable(secretSync.connection, config.accountId, {
|
||||||
|
key: secret
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error,
|
||||||
|
secretKey: secret
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,17 @@ import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"
|
|||||||
|
|
||||||
const ChecklySyncDestinationConfigSchema = z.object({
|
const ChecklySyncDestinationConfigSchema = z.object({
|
||||||
accountId: z.string().min(1, "Account ID is required").max(255, "Account ID must be less than 255 characters"),
|
accountId: z.string().min(1, "Account ID is required").max(255, "Account ID must be less than 255 characters"),
|
||||||
accountName: z.string().min(1, "Account Name is required").max(255, "Account ID must be less than 255 characters")
|
accountName: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Account Name is required")
|
||||||
|
.max(255, "Account ID must be less than 255 characters")
|
||||||
|
.optional(),
|
||||||
|
groupId: z.string().min(1, "Group ID is required").max(255, "Group ID must be less than 255 characters").optional(),
|
||||||
|
groupName: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Group Name is required")
|
||||||
|
.max(255, "Group Name must be less than 255 characters")
|
||||||
|
.optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
const ChecklySyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
|
const ChecklySyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 618 KiB After Width: | Height: | Size: 592 KiB |
@@ -37,6 +37,7 @@ description: "Learn how to configure a Checkly Sync for Infisical."
|
|||||||
|
|
||||||
- **Checkly Connection**: The Checkly Connection to authenticate with.
|
- **Checkly Connection**: The Checkly Connection to authenticate with.
|
||||||
- **Account**: The Checkly account to sync secrets to.
|
- **Account**: The Checkly account to sync secrets to.
|
||||||
|
- **Group**: The Checkly check group to sync secrets to (Optional).
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Configure Sync Options">
|
<Step title="Configure Sync Options">
|
||||||
Configure the **Sync Options** to specify how secrets should be synced, then click **Next**.
|
Configure the **Sync Options** to specify how secrets should be synced, then click **Next**.
|
||||||
|
|||||||
@@ -5,14 +5,15 @@ import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/Se
|
|||||||
import { FilterableSelect, FormControl } from "@app/components/v2";
|
import { FilterableSelect, FormControl } from "@app/components/v2";
|
||||||
import {
|
import {
|
||||||
TChecklyAccount,
|
TChecklyAccount,
|
||||||
useChecklyConnectionListAccounts
|
useChecklyConnectionListAccounts,
|
||||||
|
useChecklyConnectionListGroups
|
||||||
} from "@app/hooks/api/appConnections/checkly";
|
} from "@app/hooks/api/appConnections/checkly";
|
||||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
import { TSecretSyncForm } from "../schemas";
|
import { TSecretSyncForm } from "../schemas";
|
||||||
|
|
||||||
export const ChecklySyncFields = () => {
|
export const ChecklySyncFields = () => {
|
||||||
const { control, setValue } = useFormContext<
|
const { control, setValue, watch } = useFormContext<
|
||||||
TSecretSyncForm & { destination: SecretSync.Checkly }
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<SecretSyncConnectionField
|
<SecretSyncConnectionField
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
setValue("destinationConfig.accountId", "");
|
setValue("destinationConfig.accountId", "");
|
||||||
setValue("destinationConfig.accountName", "");
|
setValue("destinationConfig.accountName", "");
|
||||||
|
setValue("destinationConfig.groupId", undefined);
|
||||||
|
setValue("destinationConfig.groupName", undefined);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
@@ -60,6 +73,37 @@ export const ChecklySyncFields = () => {
|
|||||||
</FormControl>
|
</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 = () => {
|
export const ChecklySyncReviewFields = () => {
|
||||||
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Checkly }>();
|
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),
|
destination: z.literal(SecretSync.Checkly),
|
||||||
destinationConfig: z.object({
|
destinationConfig: z.object({
|
||||||
accountId: z.string(),
|
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 = {
|
const checklyConnectionKeys = {
|
||||||
all: [...appConnectionKeys.all, "checkly"] as const,
|
all: [...appConnectionKeys.all, "checkly"] as const,
|
||||||
listAccounts: (connectionId: string) =>
|
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 = (
|
export const useChecklyConnectionListAccounts = (
|
||||||
@@ -35,3 +37,29 @@ export const useChecklyConnectionListAccounts = (
|
|||||||
...options
|
...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 { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
|
import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
|
||||||
|
|
||||||
|
export enum ChecklySyncScope {
|
||||||
|
Global = "global",
|
||||||
|
Group = "group"
|
||||||
|
}
|
||||||
export type TChecklySync = TRootSecretSync & {
|
export type TChecklySync = TRootSecretSync & {
|
||||||
destination: SecretSync.Checkly;
|
destination: SecretSync.Checkly;
|
||||||
destinationConfig: {
|
destinationConfig: {
|
||||||
accountId: string;
|
accountId: string;
|
||||||
accountName: string;
|
accountName: string;
|
||||||
|
groupId?: string;
|
||||||
|
groupName?: string;
|
||||||
};
|
};
|
||||||
connection: {
|
connection: {
|
||||||
app: AppConnection.Checkly;
|
app: AppConnection.Checkly;
|
||||||
|
|||||||
@@ -175,8 +175,8 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
|
|||||||
secondaryText = "Railway Project";
|
secondaryText = "Railway Project";
|
||||||
break;
|
break;
|
||||||
case SecretSync.Checkly:
|
case SecretSync.Checkly:
|
||||||
primaryText = destinationConfig.accountName;
|
primaryText = destinationConfig.accountName || destinationConfig.accountId;
|
||||||
secondaryText = "Checkly Account";
|
secondaryText = destinationConfig.groupName || destinationConfig.groupId || "Checkly Account";
|
||||||
break;
|
break;
|
||||||
case SecretSync.Supabase:
|
case SecretSync.Supabase:
|
||||||
primaryText = destinationConfig.projectName;
|
primaryText = destinationConfig.projectName;
|
||||||
|
|||||||
@@ -8,5 +8,12 @@ type Props = {
|
|||||||
export const ChecklySyncDestinationSection = ({ secretSync }: Props) => {
|
export const ChecklySyncDestinationSection = ({ secretSync }: Props) => {
|
||||||
const { destinationConfig } = secretSync;
|
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>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user