From c5f550d88fc645f161e92fa6d333ccf58d08ccfc Mon Sep 17 00:00:00 2001 From: Gaurav Tewari Date: Wed, 22 Jul 2026 09:16:30 +0530 Subject: [PATCH] feat(gcp): add GCP integration config, types, and page wiring Adds the GCP integration tile, config types, region constants, account DTO mapping, and routes the 'gcp' integration id to the shared CloudIntegration page. Accounts list, service details, and account removal work for GCP; the add-account and edit-account drawers land in follow-up commits (their buttons are no-ops for GCP in this commit). Also narrows Azure config checks by 'resource_groups' instead of 'deployment_region', since the GCP config in the widened CloudAccount.config union also has a deployment_region field. --- frontend/src/assets/Logos/gcp.svg | 1 + .../AccountActions/AccountActions.tsx | 31 ++++++- .../ServiceDetails/ServiceDetails.tsx | 54 ++++++++---- .../EditAccount/AccountSettingsModal.tsx | 6 +- .../RemoveIntegrationAccount.tsx | 61 ++++++++----- .../mapCloudAccountFromDto.ts | 24 +++++ .../IntegrationDetailPage.tsx | 23 ++--- .../src/container/Integrations/constants.ts | 88 ++++++++++++++++++- frontend/src/container/Integrations/types.ts | 18 +++- .../azure/useAccountSettingsModal.ts | 6 +- 10 files changed, 251 insertions(+), 61 deletions(-) create mode 100644 frontend/src/assets/Logos/gcp.svg diff --git a/frontend/src/assets/Logos/gcp.svg b/frontend/src/assets/Logos/gcp.svg new file mode 100644 index 0000000000..2e0200e474 --- /dev/null +++ b/frontend/src/assets/Logos/gcp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/container/Integrations/CloudIntegration/AmazonWebServices/AccountActions/AccountActions.tsx b/frontend/src/container/Integrations/CloudIntegration/AmazonWebServices/AccountActions/AccountActions.tsx index 55920fc262..efc60ae47b 100644 --- a/frontend/src/container/Integrations/CloudIntegration/AmazonWebServices/AccountActions/AccountActions.tsx +++ b/frontend/src/container/Integrations/CloudIntegration/AmazonWebServices/AccountActions/AccountActions.tsx @@ -20,6 +20,7 @@ import AzureAccountSettingsModal from '../../AzureCloudServices/EditAccount/Acco import { mapAccountDtoToAwsCloudAccount, mapAccountDtoToAzureCloudAccount, + mapAccountDtoToGcpCloudAccount, } from '../../mapCloudAccountFromDto'; import AwsCloudAccountSetupModal from '../AddNewAccount/CloudAccountSetupModal'; import AwsAccountSettingsModal from '../EditAccount/AccountSettingsModal'; @@ -156,6 +157,18 @@ function AccountActions({ type }: { type: IntegrationType }): JSX.Element { }); } + if (type === IntegrationType.GCP_SERVICES) { + raw.forEach((account) => { + if (!account) { + return; + } + const mapped = mapAccountDtoToGcpCloudAccount(account); + if (mapped) { + mappedAccounts.push(mapped); + } + }); + } + return mappedAccounts; }, [listAccountsResponse, type]); @@ -207,13 +220,23 @@ function AccountActions({ type }: { type: IntegrationType }): JSX.Element { // log telemetry event when an account is viewed. useEffect(() => { if (activeAccount) { + const { config } = activeAccount; + let enabledRegions: string[]; + if ('regions' in config) { + // AWS + enabledRegions = config.regions; + } else if ('resource_groups' in config) { + // Azure + enabledRegions = config.resource_groups; + } else { + // GCP + enabledRegions = config.project_ids; + } + logEvent(`${type} Integration: Account viewed`, { cloudAccountId: activeAccount?.cloud_account_id, status: activeAccount?.status, - enabledRegions: - 'regions' in activeAccount.config - ? activeAccount.config.regions - : activeAccount.config.resource_groups, + enabledRegions, }); } }, [activeAccount, type]); diff --git a/frontend/src/container/Integrations/CloudIntegration/AmazonWebServices/ServiceDetails/ServiceDetails.tsx b/frontend/src/container/Integrations/CloudIntegration/AmazonWebServices/ServiceDetails/ServiceDetails.tsx index ce03341cb9..0f28594965 100644 --- a/frontend/src/container/Integrations/CloudIntegration/AmazonWebServices/ServiceDetails/ServiceDetails.tsx +++ b/frontend/src/container/Integrations/CloudIntegration/AmazonWebServices/ServiceDetails/ServiceDetails.tsx @@ -46,14 +46,31 @@ const EMPTY_FORM_VALUES: ServiceConfigFormValues = { s3BucketsByRegion: {}, }; +function getIntegrationServiceConfig( + type: IntegrationType, + serviceDetailsData?: ServiceDetailsData, +): + | { logs?: { enabled?: boolean }; metrics?: { enabled?: boolean } } + | undefined { + const config = serviceDetailsData?.cloudIntegrationService?.config; + + if (type === IntegrationType.AWS_SERVICES) { + return config?.aws; + } + if (type === IntegrationType.GCP_SERVICES) { + return config?.gcp; + } + return config?.azure; +} + function getInitialFormValues( type: IntegrationType, serviceDetailsData?: ServiceDetailsData, ): ServiceConfigFormValues { - const integrationConfig = - type === IntegrationType.AWS_SERVICES - ? serviceDetailsData?.cloudIntegrationService?.config?.aws - : serviceDetailsData?.cloudIntegrationService?.config?.azure; + const integrationConfig = getIntegrationServiceConfig( + type, + serviceDetailsData, + ); return { logsEnabled: integrationConfig?.logs?.enabled || false, @@ -98,16 +115,21 @@ function getServiceConfigPayload({ }; } - return { - azure: { - logs: { - enabled: isLogsSupported ? logsEnabled : false, - }, - metrics: { - enabled: isMetricsSupported ? metricsEnabled : false, - }, + // Azure and GCP share the same simple logs/metrics enable-flag shape. + const signalConfig = { + logs: { + enabled: isLogsSupported ? logsEnabled : false, + }, + metrics: { + enabled: isMetricsSupported ? metricsEnabled : false, }, }; + + if (type === IntegrationType.GCP_SERVICES) { + return { gcp: signalConfig }; + } + + return { azure: signalConfig }; } function ServiceDetails({ @@ -163,10 +185,10 @@ function ServiceDetails({ ? isAccountServiceLoading : isReadOnlyServiceLoading; - const integrationConfig = - type === IntegrationType.AWS_SERVICES - ? serviceDetailsData?.cloudIntegrationService?.config?.aws - : serviceDetailsData?.cloudIntegrationService?.config?.azure; + const integrationConfig = getIntegrationServiceConfig( + type, + serviceDetailsData, + ); const isServiceEnabledInPersistedConfig = Boolean(integrationConfig?.logs?.enabled) || Boolean(integrationConfig?.metrics?.enabled); diff --git a/frontend/src/container/Integrations/CloudIntegration/AzureCloudServices/EditAccount/AccountSettingsModal.tsx b/frontend/src/container/Integrations/CloudIntegration/AzureCloudServices/EditAccount/AccountSettingsModal.tsx index cbe0b1dc63..81b9ba9a3f 100644 --- a/frontend/src/container/Integrations/CloudIntegration/AzureCloudServices/EditAccount/AccountSettingsModal.tsx +++ b/frontend/src/container/Integrations/CloudIntegration/AzureCloudServices/EditAccount/AccountSettingsModal.tsx @@ -36,8 +36,12 @@ function AccountSettingsModal({ const queryClient = useQueryClient(); + // `account.config` is the shared per-provider union (Azure | AWS | GCP). + // Narrow to Azure by `resource_groups` (Azure-only) rather than + // `deployment_region`, which GCP also has — so it no longer identifies + // Azure uniquely. const azureConfig = useMemo( - () => ('deployment_region' in account.config ? account.config : null), + () => ('resource_groups' in account.config ? account.config : null), [account.config], ); diff --git a/frontend/src/container/Integrations/CloudIntegration/RemoveAccount/RemoveIntegrationAccount.tsx b/frontend/src/container/Integrations/CloudIntegration/RemoveAccount/RemoveIntegrationAccount.tsx index 9e3f166eb1..548046fa9e 100644 --- a/frontend/src/container/Integrations/CloudIntegration/RemoveAccount/RemoveIntegrationAccount.tsx +++ b/frontend/src/container/Integrations/CloudIntegration/RemoveAccount/RemoveIntegrationAccount.tsx @@ -60,6 +60,44 @@ function RemoveIntegrationAccount({ setIsModalOpen(false); }; + let modalDescription: JSX.Element; + if (cloudProvider === INTEGRATION_TYPES.AWS) { + modalDescription = ( + <> + Removing this account will remove all components created for sending + telemetry to SigNoz in your AWS account within the next ~15 minutes + (cloudformation stacks named signoz-integration-telemetry-collection in + enabled regions).
+
+ After that, you can delete the cloudformation stack that was created + manually when connecting this account. + + ); + } else if (cloudProvider === INTEGRATION_TYPES.GCP) { + modalDescription = ( + <> + Removing this account will stop SigNoz from monitoring it.
+
+ Since you manage the GCP resources yourself, remember to manually tear down + the OTel collector and Pub/Sub resources you created for this integration if + you no longer need them. + + ); + } else { + modalDescription = ( + <> + Removing this account will remove all components created for sending + telemetry to SigNoz in your Azure subscription within the next ~15 minutes + (deployment stack named signoz-integration-telemetry will be deleted + automatically).
+
+ After that, you have to manually delete 'signoz-integration' + deployment stack that was created while connecting this account (Takes ~20 + minutes to delete). + + ); + } + return (
); diff --git a/frontend/src/container/Integrations/CloudIntegration/mapCloudAccountFromDto.ts b/frontend/src/container/Integrations/CloudIntegration/mapCloudAccountFromDto.ts index cbd84f6338..71d0b15cdb 100644 --- a/frontend/src/container/Integrations/CloudIntegration/mapCloudAccountFromDto.ts +++ b/frontend/src/container/Integrations/CloudIntegration/mapCloudAccountFromDto.ts @@ -47,3 +47,27 @@ export function mapAccountDtoToAzureCloudAccount( providerAccountId: account.providerAccountId, }; } + +export function mapAccountDtoToGcpCloudAccount( + account: CloudintegrationtypesAccountDTO, +): IntegrationCloudAccount | null { + if (!account.providerAccountId) { + return null; + } + + return { + id: account.id, + cloud_account_id: account.id, + config: { + deployment_region: account.config?.gcp?.deploymentRegion ?? '', + deployment_project_id: account.config?.gcp?.deploymentProjectId ?? '', + project_ids: account.config?.gcp?.projectIds ?? [], + }, + status: { + integration: { + last_heartbeat_ts_ms: account.agentReport?.timestampMillis ?? 0, + }, + }, + providerAccountId: account.providerAccountId, + }; +} diff --git a/frontend/src/container/Integrations/IntegrationDetailPage/IntegrationDetailPage.tsx b/frontend/src/container/Integrations/IntegrationDetailPage/IntegrationDetailPage.tsx index 340b5e7f30..d58824468b 100644 --- a/frontend/src/container/Integrations/IntegrationDetailPage/IntegrationDetailPage.tsx +++ b/frontend/src/container/Integrations/IntegrationDetailPage/IntegrationDetailPage.tsx @@ -13,8 +13,8 @@ import { ArrowLeft, MoveUpRight, RotateCw } from '@signozhq/icons'; import awwSnapUrl from '@/assets/Icons/awwSnap.svg'; import CloudIntegration from '../CloudIntegration/CloudIntegration'; -import { INTEGRATION_TYPES } from '../constants'; import { IntegrationType } from '../types'; +import { INTEGRATION_TYPES } from '../constants'; import { handleContactSupport } from '../utils'; import IntegrationDetailContent from './IntegrationDetailContent'; import IntegrationDetailHeader from './IntegrationDetailHeader'; @@ -24,6 +24,12 @@ import { getConnectionStatesFromConnectionStatus } from './utils'; import './IntegrationDetailPage.styles.scss'; +const cloudIntegrationTypeById: Record = { + [INTEGRATION_TYPES.AWS]: IntegrationType.AWS_SERVICES, + [INTEGRATION_TYPES.AZURE]: IntegrationType.AZURE_SERVICES, + [INTEGRATION_TYPES.GCP]: IntegrationType.GCP_SERVICES, +}; + // eslint-disable-next-line sonarjs/cognitive-complexity function IntegrationDetailPage(): JSX.Element { const history = useHistory(); @@ -55,19 +61,8 @@ function IntegrationDetailPage(): JSX.Element { ), ); - if ( - integrationId === INTEGRATION_TYPES.AWS || - integrationId === INTEGRATION_TYPES.AZURE - ) { - return ( - - ); + if (integrationId && cloudIntegrationTypeById[integrationId]) { + return ; } return ( diff --git a/frontend/src/container/Integrations/constants.ts b/frontend/src/container/Integrations/constants.ts index 8645bcf9a0..bc28506636 100644 --- a/frontend/src/container/Integrations/constants.ts +++ b/frontend/src/container/Integrations/constants.ts @@ -1,7 +1,8 @@ import awsDarkLogo from '@/assets/Logos/aws-dark.svg'; import azureOpenaiLogo from '@/assets/Logos/azure-openai.svg'; +import gcpLogo from '@/assets/Logos/gcp.svg'; -import { AzureRegion } from './types'; +import { AzureRegion, GCPRegion } from './types'; export const INTEGRATION_TELEMETRY_EVENTS = { INTEGRATIONS_LIST_VISITED: 'Integrations Page: Visited the list page', @@ -21,6 +22,7 @@ export const INTEGRATION_TELEMETRY_EVENTS = { export const INTEGRATION_TYPES = { AWS: 'aws', AZURE: 'azure', + GCP: 'gcp', }; export const AWS_INTEGRATION = { @@ -53,7 +55,26 @@ export const AZURE_INTEGRATION = { is_new: true, }; -export const ONE_CLICK_INTEGRATIONS = [AWS_INTEGRATION, AZURE_INTEGRATION]; +export const GCP_INTEGRATION = { + id: INTEGRATION_TYPES.GCP, + title: 'Google Cloud Platform', + description: 'Setup for GCP monitoring with SigNoz', + author: { + name: 'SigNoz', + email: 'integrations@signoz.io', + homepage: 'https://signoz.io', + }, + icon: gcpLogo, + icon_alt: 'gcp-logo', + is_installed: false, + is_new: true, +}; + +export const ONE_CLICK_INTEGRATIONS = [ + AWS_INTEGRATION, + AZURE_INTEGRATION, + GCP_INTEGRATION, +]; export const AZURE_REGIONS: AzureRegion[] = [ { @@ -165,3 +186,66 @@ export const AZURE_REGIONS: AzureRegion[] = [ { label: 'West US 2', value: 'westus2', geography: 'United States' }, { label: 'West US 3', value: 'westus3', geography: 'United States' }, ]; + +// Source of truth: pkg/types/cloudintegrationtypes/regions.go (GCP regions). +export const GCP_REGIONS: GCPRegion[] = [ + { label: 'Johannesburg', value: 'africa-south1', geography: 'Africa' }, + { label: 'Changhua County', value: 'asia-east1', geography: 'APAC' }, + { label: 'Hong Kong', value: 'asia-east2', geography: 'APAC' }, + { label: 'Tokyo', value: 'asia-northeast1', geography: 'APAC' }, + { label: 'Osaka', value: 'asia-northeast2', geography: 'APAC' }, + { label: 'Seoul', value: 'asia-northeast3', geography: 'APAC' }, + { label: 'Mumbai', value: 'asia-south1', geography: 'APAC' }, + { label: 'Delhi', value: 'asia-south2', geography: 'APAC' }, + { label: 'Singapore', value: 'asia-southeast1', geography: 'APAC' }, + { label: 'Jakarta', value: 'asia-southeast2', geography: 'APAC' }, + { label: 'Bangkok', value: 'asia-southeast3', geography: 'APAC' }, + { label: 'Sydney', value: 'australia-southeast1', geography: 'APAC' }, + { label: 'Melbourne', value: 'australia-southeast2', geography: 'APAC' }, + { label: 'Warsaw', value: 'europe-central2', geography: 'Europe' }, + { label: 'Hamina', value: 'europe-north1', geography: 'Europe' }, + { label: 'Stockholm', value: 'europe-north2', geography: 'Europe' }, + { label: 'Madrid', value: 'europe-southwest1', geography: 'Europe' }, + { label: 'St. Ghislain', value: 'europe-west1', geography: 'Europe' }, + { label: 'London', value: 'europe-west2', geography: 'Europe' }, + { label: 'Frankfurt', value: 'europe-west3', geography: 'Europe' }, + { label: 'Eemshaven', value: 'europe-west4', geography: 'Europe' }, + { label: 'Zurich', value: 'europe-west6', geography: 'Europe' }, + { label: 'Milan', value: 'europe-west8', geography: 'Europe' }, + { label: 'Paris', value: 'europe-west9', geography: 'Europe' }, + { label: 'Berlin', value: 'europe-west10', geography: 'Europe' }, + { label: 'Turin', value: 'europe-west12', geography: 'Europe' }, + { label: 'Doha', value: 'me-central1', geography: 'Middle East' }, + { label: 'Dammam', value: 'me-central2', geography: 'Middle East' }, + { label: 'Tel Aviv', value: 'me-west1', geography: 'Middle East' }, + { + label: 'Montréal', + value: 'northamerica-northeast1', + geography: 'North America', + }, + { + label: 'Toronto', + value: 'northamerica-northeast2', + geography: 'North America', + }, + { + label: 'Querétaro', + value: 'northamerica-south1', + geography: 'North America', + }, + { + label: 'São Paulo', + value: 'southamerica-east1', + geography: 'South America', + }, + { label: 'Santiago', value: 'southamerica-west1', geography: 'South America' }, + { label: 'Council Bluffs', value: 'us-central1', geography: 'North America' }, + { label: 'Moncks Corner', value: 'us-east1', geography: 'North America' }, + { label: 'Ashburn', value: 'us-east4', geography: 'North America' }, + { label: 'Columbus', value: 'us-east5', geography: 'North America' }, + { label: 'Dallas', value: 'us-south1', geography: 'North America' }, + { label: 'The Dalles', value: 'us-west1', geography: 'North America' }, + { label: 'Los Angeles', value: 'us-west2', geography: 'North America' }, + { label: 'Salt Lake City', value: 'us-west3', geography: 'North America' }, + { label: 'Las Vegas', value: 'us-west4', geography: 'North America' }, +]; diff --git a/frontend/src/container/Integrations/types.ts b/frontend/src/container/Integrations/types.ts index 9e29a1a087..aa154898b3 100644 --- a/frontend/src/container/Integrations/types.ts +++ b/frontend/src/container/Integrations/types.ts @@ -6,6 +6,7 @@ import { export enum IntegrationType { AWS_SERVICES = 'aws', AZURE_SERVICES = 'azure', + GCP_SERVICES = 'gcp', } interface LogField { @@ -87,7 +88,10 @@ export interface ServiceData { export interface CloudAccount { id: string; cloud_account_id: string; - config: AzureCloudAccountConfig | AWSCloudAccountConfig; + config: + | AzureCloudAccountConfig + | AWSCloudAccountConfig + | GCPCloudAccountConfig; status: AccountStatus | IServiceStatus; providerAccountId: string; } @@ -97,6 +101,12 @@ export interface AzureCloudAccountConfig { resource_groups: string[]; } +export interface GCPCloudAccountConfig { + deployment_region: string; + deployment_project_id: string; + project_ids: string[]; +} + export interface AccountStatus { integration: IntegrationStatus; } @@ -111,6 +121,12 @@ export interface AzureRegion { value: string; } +export interface GCPRegion { + label: string; + geography: string; + value: string; +} + export interface UpdateServiceConfigPayload { cloud_account_id: string; config: AzureServicesConfig; diff --git a/frontend/src/hooks/integration/azure/useAccountSettingsModal.ts b/frontend/src/hooks/integration/azure/useAccountSettingsModal.ts index 25eb705b41..7ffb9af58a 100644 --- a/frontend/src/hooks/integration/azure/useAccountSettingsModal.ts +++ b/frontend/src/hooks/integration/azure/useAccountSettingsModal.ts @@ -39,8 +39,12 @@ export function useAccountSettingsModal({ }: UseAccountSettingsModalProps): UseAccountSettingsModal { const [form] = Form.useForm(); const { mutate: updateAccount, isLoading } = useUpdateAccount(); + // `account.config` is the shared per-provider union (Azure | AWS | GCP). + // Narrow to Azure by `resource_groups` (Azure-only) rather than + // `deployment_region`, which GCP also has — so it no longer identifies + // Azure uniquely. const accountConfig = useMemo( - () => ('deployment_region' in account.config ? account.config : null), + () => ('resource_groups' in account.config ? account.config : null), [account.config], ); const [resourceGroups, setResourceGroups] = useState(