diff --git a/frontend/src/api/Integrations/removeAwsIntegrationAccount.ts b/frontend/src/api/Integrations/removeAwsIntegrationAccount.ts new file mode 100644 index 0000000000..11ffaf4ed8 --- /dev/null +++ b/frontend/src/api/Integrations/removeAwsIntegrationAccount.ts @@ -0,0 +1,19 @@ +import axios from 'api'; +import { ErrorResponse, SuccessResponse } from 'types/api'; + +const removeAwsIntegrationAccount = async ( + accountId: string, +): Promise> | ErrorResponse> => { + const response = await axios.post( + `/cloud-integrations/aws/accounts/${accountId}/disconnect`, + ); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data.data, + }; +}; + +export default removeAwsIntegrationAccount; diff --git a/frontend/src/api/integrations/aws/index.ts b/frontend/src/api/integrations/aws/index.ts index 360a8e30ea..92f8c22431 100644 --- a/frontend/src/api/integrations/aws/index.ts +++ b/frontend/src/api/integrations/aws/index.ts @@ -9,19 +9,22 @@ import { import { AccountConfigPayload, AccountConfigResponse, + ConnectionParams, ConnectionUrlResponse, } from 'types/api/integrations/aws'; export const getAwsAccounts = async (): Promise => { const response = await axios.get('/cloud-integrations/aws/accounts'); - return response.data.data; + return response.data.data.accounts; }; export const getAwsServices = async ( - accountId?: string, + cloudAccountId?: string, ): Promise => { - const params = accountId ? { account_id: accountId } : undefined; + const params = cloudAccountId + ? { cloud_account_id: cloudAccountId } + : undefined; const response = await axios.get('/cloud-integrations/aws/services', { params, }); @@ -31,9 +34,11 @@ export const getAwsServices = async ( export const getServiceDetails = async ( serviceId: string, - accountId?: string, + cloudAccountId?: string, ): Promise => { - const params = accountId ? { account_id: accountId } : undefined; + const params = cloudAccountId + ? { cloud_account_id: cloudAccountId } + : undefined; const response = await axios.get( `/cloud-integrations/aws/services/${serviceId}`, { params }, @@ -74,3 +79,10 @@ export const updateServiceConfig = async ( ); return response.data; }; + +export const getConnectionParams = async (): Promise => { + const response = await axios.get( + '/cloud-integrations/aws/accounts/generate-connection-params', + ); + return response.data.data; +}; diff --git a/frontend/src/constants/reactQueryKeys.ts b/frontend/src/constants/reactQueryKeys.ts index a05d12b300..d88dd7a04a 100644 --- a/frontend/src/constants/reactQueryKeys.ts +++ b/frontend/src/constants/reactQueryKeys.ts @@ -41,5 +41,6 @@ export const REACT_QUERY_KEY = { AWS_UPDATE_ACCOUNT_CONFIG: 'AWS_UPDATE_ACCOUNT_CONFIG', AWS_UPDATE_SERVICE_CONFIG: 'AWS_UPDATE_SERVICE_CONFIG', AWS_GENERATE_CONNECTION_URL: 'AWS_GENERATE_CONNECTION_URL', + AWS_GET_CONNECTION_PARAMS: 'AWS_GET_CONNECTION_PARAMS', GET_ATTRIBUTE_VALUES: 'GET_ATTRIBUTE_VALUES', }; diff --git a/frontend/src/container/CloudIntegrationPage/Header/Header.tsx b/frontend/src/container/CloudIntegrationPage/Header/Header.tsx index 13842d18ae..d37ba5742a 100644 --- a/frontend/src/container/CloudIntegrationPage/Header/Header.tsx +++ b/frontend/src/container/CloudIntegrationPage/Header/Header.tsx @@ -24,7 +24,9 @@ function Header(): JSX.Element { }, { title: ( -
AWS web services
+
+ Amazon Web Services +
), }, ]} diff --git a/frontend/src/container/CloudIntegrationPage/HeroSection/HeroSection.tsx b/frontend/src/container/CloudIntegrationPage/HeroSection/HeroSection.tsx index 16be03ffd0..cfc34d7e83 100644 --- a/frontend/src/container/CloudIntegrationPage/HeroSection/HeroSection.tsx +++ b/frontend/src/container/CloudIntegrationPage/HeroSection/HeroSection.tsx @@ -21,7 +21,7 @@ function HeroSection(): JSX.Element { aws-logo
-
AWS Web Services
+
Amazon Web Services
One-click setup for AWS monitoring with SigNoz
diff --git a/frontend/src/container/CloudIntegrationPage/HeroSection/components/AccountActions.style.scss b/frontend/src/container/CloudIntegrationPage/HeroSection/components/AccountActions.style.scss index b1f36654ce..e16df41dfb 100644 --- a/frontend/src/container/CloudIntegrationPage/HeroSection/components/AccountActions.style.scss +++ b/frontend/src/container/CloudIntegrationPage/HeroSection/components/AccountActions.style.scss @@ -1,41 +1,56 @@ -.hero-section__actions { - margin-top: 12px; +.hero-section { + &__actions { + margin-top: 12px; - &-with-account { - display: flex; - flex-direction: column; - gap: 10px; - } -} -.hero-section__action-buttons { - display: flex; - align-items: center; - gap: 8px; -} - -.hero-section__action-button { - font-family: 'Inter'; - border-radius: 2px; - cursor: pointer; - font-size: 12px; - font-weight: 500; - line-height: 16px; - padding: 8px 17px; - - &.primary { - background: var(--bg-robin-500); - border: none; - color: var(--bg-vanilla-100); + &-with-account { + display: flex; + flex-direction: column; + gap: 10px; + } } - &.secondary { + &__input-skeleton { + width: 300px; + margin-bottom: 16px; + } + + &__new-account-button-skeleton { + width: 180px; + margin-right: 8px; + } + + &__account-settings-button-skeleton { + width: 140px; + } + &__action-buttons { display: flex; align-items: center; - border: 1px solid var(--bg-ink-300); - color: var(--bg-vanilla-100); + gap: 8px; + } + &__action-button { + font-family: 'Inter'; border-radius: 2px; - background: var(--bg-slate-400); - box-shadow: none; + cursor: pointer; + font-size: 12px; + font-weight: 500; + line-height: 16px; + padding: 8px 17px; + + &.primary { + background: var(--bg-robin-500); + border: none; + color: var(--bg-vanilla-100); + } + + &.secondary { + display: flex; + align-items: center; + border: 1px solid var(--bg-ink-300); + color: var(--bg-vanilla-100); + border-radius: 2px; + background: var(--bg-slate-400); + box-shadow: none; + } } } diff --git a/frontend/src/container/CloudIntegrationPage/HeroSection/components/AccountActions.tsx b/frontend/src/container/CloudIntegrationPage/HeroSection/components/AccountActions.tsx index fd5e246e7d..09c56e5918 100644 --- a/frontend/src/container/CloudIntegrationPage/HeroSection/components/AccountActions.tsx +++ b/frontend/src/container/CloudIntegrationPage/HeroSection/components/AccountActions.tsx @@ -1,7 +1,7 @@ import './AccountActions.style.scss'; import { Color } from '@signozhq/design-tokens'; -import { Button, Select } from 'antd'; +import { Button, Select, Skeleton } from 'antd'; import { SelectProps } from 'antd/lib'; import { useAwsAccounts } from 'hooks/integrations/aws/useAwsAccounts'; import useUrlQuery from 'hooks/useUrlQuery'; @@ -53,15 +53,100 @@ const getAccountById = ( ): CloudAccount | null => accounts.find((account) => account.cloud_account_id === accountId) || null; +function AccountActionsRenderer({ + accounts, + isLoading, + activeAccount, + selectOptions, + onAccountChange, + onIntegrationModalOpen, + onAccountSettingsModalOpen, +}: { + accounts: CloudAccount[] | undefined; + isLoading: boolean; + activeAccount: CloudAccount | null; + selectOptions: SelectProps['options']; + onAccountChange: (value: string) => void; + onIntegrationModalOpen: () => void; + onAccountSettingsModalOpen: () => void; +}): JSX.Element { + if (isLoading) { + return ( +
+ +
+ + +
+
+ ); + } + if (accounts?.length) { + return ( +
+ } - optionRender={(option): JSX.Element => - renderOption(option, activeAccount?.cloud_account_id) - } - onChange={(value): void => { - setActiveAccount(getAccountById(accounts, value)); - urlQuery.set('accountId', value); - navigate({ search: urlQuery.toString() }); - }} - /> -
- - -
-
- ) : ( - + { + if (accounts) { + setActiveAccount(getAccountById(accounts, value)); + urlQuery.set('cloudAccountId', value); + navigate({ search: urlQuery.toString() }); + } + }} + onIntegrationModalOpen={(): void => setIsIntegrationModalOpen(true)} + onAccountSettingsModalOpen={(): void => setIsAccountSettingsModalOpen(true)} + /> + + {isIntegrationModalOpen && ( + setIsIntegrationModalOpen(false)} + /> )} - setIsIntegrationModalOpen(false)} - /> - - setIsAccountSettingsModalOpen(false)} - account={activeAccount as CloudAccount} - setActiveAccount={setActiveAccount} - /> + {isAccountSettingsModalOpen && ( + setIsAccountSettingsModalOpen(false)} + account={activeAccount as CloudAccount} + setActiveAccount={setActiveAccount} + /> + )}
); } diff --git a/frontend/src/container/CloudIntegrationPage/HeroSection/components/AccountSettingsModal.tsx b/frontend/src/container/CloudIntegrationPage/HeroSection/components/AccountSettingsModal.tsx index bbbced4a52..bcb09c3209 100644 --- a/frontend/src/container/CloudIntegrationPage/HeroSection/components/AccountSettingsModal.tsx +++ b/frontend/src/container/CloudIntegrationPage/HeroSection/components/AccountSettingsModal.tsx @@ -2,27 +2,27 @@ import './AccountSettingsModal.style.scss'; import { Form, Select, Switch } from 'antd'; import SignozModal from 'components/SignozModal/SignozModal'; +import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { getRegionPreviewText, useAccountSettingsModal, } from 'hooks/integrations/aws/useAccountSettingsModal'; -import IntergrationsUninstallBar from 'pages/Integrations/IntegrationDetailPage/IntegrationsUninstallBar'; -import { ConnectionStates } from 'pages/Integrations/IntegrationDetailPage/TestConnection'; -import { AWS_INTEGRATION } from 'pages/Integrations/IntegrationsList'; +import useUrlQuery from 'hooks/useUrlQuery'; +import history from 'lib/history'; import { Dispatch, SetStateAction, useCallback } from 'react'; +import { useQueryClient } from 'react-query'; import { CloudAccount } from '../../ServicesSection/types'; import { RegionSelector } from './RegionSelector'; +import RemoveIntegrationAccount from './RemoveIntegrationAccount'; interface AccountSettingsModalProps { - isOpen: boolean; onClose: () => void; account: CloudAccount; setActiveAccount: Dispatch>; } function AccountSettingsModal({ - isOpen, onClose, account, setActiveAccount, @@ -42,6 +42,16 @@ function AccountSettingsModal({ handleClose, } = useAccountSettingsModal({ onClose, account, setActiveAccount }); + const queryClient = useQueryClient(); + const urlQuery = useUrlQuery(); + + const handleRemoveIntegrationAccountSuccess = (): void => { + queryClient.invalidateQueries([REACT_QUERY_KEY.AWS_ACCOUNTS]); + urlQuery.delete('cloudAccountId'); + handleClose(); + history.replace({ search: urlQuery.toString() }); + }; + const renderRegionSelector = useCallback(() => { if (isRegionSelectOpen) { return ( @@ -120,7 +130,7 @@ function AccountSettingsModal({ return (
-
diff --git a/frontend/src/container/CloudIntegrationPage/HeroSection/components/CloudAccountSetupModal.tsx b/frontend/src/container/CloudIntegrationPage/HeroSection/components/CloudAccountSetupModal.tsx index b00ef0033f..5f4656dfc2 100644 --- a/frontend/src/container/CloudIntegrationPage/HeroSection/components/CloudAccountSetupModal.tsx +++ b/frontend/src/container/CloudIntegrationPage/HeroSection/components/CloudAccountSetupModal.tsx @@ -2,11 +2,9 @@ import './CloudAccountSetupModal.style.scss'; import { Color } from '@signozhq/design-tokens'; import SignozModal from 'components/SignozModal/SignozModal'; -import ROUTES from 'constants/routes'; import { useIntegrationModal } from 'hooks/integrations/aws/useIntegrationModal'; import { SquareArrowOutUpRight } from 'lucide-react'; import { useCallback } from 'react'; -import { useNavigate } from 'react-router-dom-v5-compat'; import { ActiveViewEnum, @@ -18,7 +16,6 @@ import { RegionSelector } from './RegionSelector'; import { SuccessView } from './SuccessView'; function CloudAccountSetupModal({ - isOpen, onClose, }: IntegrationModalProps): JSX.Element { const { @@ -41,6 +38,8 @@ function CloudAccountSetupModal({ accountId, selectedDeploymentRegion, handleRegionChange, + connectionParams, + isConnectionParamsLoading, } = useIntegrationModal({ onClose }); const renderContent = useCallback(() => { @@ -71,6 +70,8 @@ function CloudAccountSetupModal({ accountId={accountId} selectedDeploymentRegion={selectedDeploymentRegion} handleRegionChange={handleRegionChange} + connectionParams={connectionParams} + isConnectionParamsLoading={isConnectionParamsLoading} /> ); }, [ @@ -86,6 +87,8 @@ function CloudAccountSetupModal({ accountId, selectedDeploymentRegion, handleRegionChange, + connectionParams, + isConnectionParamsLoading, setSelectedRegions, setIncludeAllRegions, ]); @@ -96,11 +99,6 @@ function CloudAccountSetupModal({ [selectedRegions, allRegions], ); - const navigate = useNavigate(); - const handleGoToDashboards = useCallback((): void => { - navigate(ROUTES.ALL_DASHBOARD); - }, [navigate]); - const getModalConfig = useCallback(() => { // Handle success state first if (modalState === ModalStateEnum.SUCCESS) { @@ -108,11 +106,11 @@ function CloudAccountSetupModal({ title: 'AWS Webservice Integration', okText: (
- Go to Dashboards + Continue
), block: true, - onOk: handleGoToDashboards, + onOk: handleClose, cancelButtonProps: { style: { display: 'none' } }, disabled: false, }; @@ -151,7 +149,7 @@ function CloudAccountSetupModal({ isLoading, isGeneratingUrl, activeView, - handleGoToDashboards, + handleClose, setActiveView, ]); @@ -159,7 +157,7 @@ function CloudAccountSetupModal({ return ( regions.flatMap((r) => r.subRegions.map((sr) => sr.name)); @@ -35,6 +36,8 @@ export function RegionForm({ accountId, selectedDeploymentRegion, handleRegionChange, + connectionParams, + isConnectionParamsLoading, }: RegionFormProps): JSX.Element { const startTimeRef = useRef(Date.now()); const refetchInterval = 10 * 1000; @@ -88,6 +91,11 @@ export function RegionForm({ isFormDisabled={isFormDisabled} /> + ); diff --git a/frontend/src/container/CloudIntegrationPage/HeroSection/components/RemoveIntegrationAccount.scss b/frontend/src/container/CloudIntegrationPage/HeroSection/components/RemoveIntegrationAccount.scss new file mode 100644 index 0000000000..080c18c588 --- /dev/null +++ b/frontend/src/container/CloudIntegrationPage/HeroSection/components/RemoveIntegrationAccount.scss @@ -0,0 +1,48 @@ +.remove-integration-account { + display: flex; + justify-content: space-between; + padding: 16px; + border-radius: 4px; + border: 1px solid rgba(218, 85, 101, 0.2); + background: rgba(218, 85, 101, 0.06); + &__header { + display: flex; + flex-direction: column; + gap: 6px; + } + + &__title { + color: var(--bg-cherry-500); + font-size: 14px; + letter-spacing: -0.07px; + } + + &__subtitle { + color: var(--bg-cherry-300); + font-size: 14px; + line-height: 22px; + letter-spacing: -0.07px; + } + + &__button { + display: flex; + align-items: center; + background: var(--bg-cherry-500); + border: none; + color: var(--bg-vanilla-100); + font-size: 12px; + font-weight: 500; + line-height: 13.3px; /* 110.833% */ + padding: 9px 13px; + .ant-btn-icon { + margin-inline-end: 4px !important; + } + + &:hover { + &.ant-btn-default { + color: var(--bg-vanilla-300) !important; + } + } + } +} + diff --git a/frontend/src/container/CloudIntegrationPage/HeroSection/components/RemoveIntegrationAccount.tsx b/frontend/src/container/CloudIntegrationPage/HeroSection/components/RemoveIntegrationAccount.tsx new file mode 100644 index 0000000000..cfd541e972 --- /dev/null +++ b/frontend/src/container/CloudIntegrationPage/HeroSection/components/RemoveIntegrationAccount.tsx @@ -0,0 +1,94 @@ +import './RemoveIntegrationAccount.scss'; + +import { Button, Modal } from 'antd'; +import logEvent from 'api/common/logEvent'; +import removeAwsIntegrationAccount from 'api/Integrations/removeAwsIntegrationAccount'; +import { SOMETHING_WENT_WRONG } from 'constants/api'; +import { useNotifications } from 'hooks/useNotifications'; +import { X } from 'lucide-react'; +import { INTEGRATION_TELEMETRY_EVENTS } from 'pages/Integrations/utils'; +import { useState } from 'react'; +import { useMutation } from 'react-query'; + +function RemoveIntegrationAccount({ + accountId, + onRemoveIntegrationAccountSuccess, +}: { + accountId: string; + onRemoveIntegrationAccountSuccess: () => void; +}): JSX.Element { + const { notifications } = useNotifications(); + const [isModalOpen, setIsModalOpen] = useState(false); + + const showModal = (): void => { + setIsModalOpen(true); + }; + + const { + mutate: removeIntegration, + isLoading: isRemoveIntegrationLoading, + } = useMutation(removeAwsIntegrationAccount, { + onSuccess: () => { + onRemoveIntegrationAccountSuccess?.(); + setIsModalOpen(false); + }, + onError: () => { + notifications.error({ + message: SOMETHING_WENT_WRONG, + }); + }, + }); + const handleOk = (): void => { + logEvent(INTEGRATION_TELEMETRY_EVENTS.AWS_INTEGRATION_ACCOUNT_REMOVED, { + accountId, + }); + removeIntegration(accountId); + }; + + const handleCancel = (): void => { + setIsModalOpen(false); + }; + + return ( +
+
+
Remove Integration
+
+ Removing this integration won't delete any existing data but will stop + collecting new data from AWS. +
+
+ + +
+ 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. +
+
+
+ ); +} + +export default RemoveIntegrationAccount; diff --git a/frontend/src/container/CloudIntegrationPage/HeroSection/components/RenderConnectionParams.tsx b/frontend/src/container/CloudIntegrationPage/HeroSection/components/RenderConnectionParams.tsx new file mode 100644 index 0000000000..c765262a2d --- /dev/null +++ b/frontend/src/container/CloudIntegrationPage/HeroSection/components/RenderConnectionParams.tsx @@ -0,0 +1,61 @@ +import { Form, Input } from 'antd'; +import { ConnectionParams } from 'types/api/integrations/aws'; + +function RenderConnectionFields({ + isConnectionParamsLoading, + connectionParams, + isFormDisabled, +}: { + isConnectionParamsLoading?: boolean; + connectionParams?: ConnectionParams | null; + isFormDisabled?: boolean; +}): JSX.Element | null { + if ( + isConnectionParamsLoading || + (!!connectionParams?.ingestion_url && + !!connectionParams?.ingestion_key && + !!connectionParams?.signoz_api_url) + ) { + return null; + } + + return ( + + {!connectionParams?.ingestion_url && ( + + + + )} + {!connectionParams?.ingestion_key && ( + + + + )} + {!connectionParams?.signoz_api_url && ( + + + + )} + + ); +} + +RenderConnectionFields.defaultProps = { + connectionParams: null, + isFormDisabled: false, + isConnectionParamsLoading: false, +}; + +export default RenderConnectionFields; diff --git a/frontend/src/container/CloudIntegrationPage/HeroSection/components/SuccessView.tsx b/frontend/src/container/CloudIntegrationPage/HeroSection/components/SuccessView.tsx index 7cd22870d5..b92b6f2225 100644 --- a/frontend/src/container/CloudIntegrationPage/HeroSection/components/SuccessView.tsx +++ b/frontend/src/container/CloudIntegrationPage/HeroSection/components/SuccessView.tsx @@ -53,23 +53,18 @@ export function SuccessView(): JSX.Element { WHAT NEXT
- {[ - 'Understand your AWS services with SigNoz’s out-of-the-box dashboards', - 'Set up alerts for real-time monitoring.', - 'Track logs and traces.', - ].map((item) => ( - -
-
{item}
+ +
+
+ Set up your AWS services effortlessly under your enabled account.
- } - type="info" - className="what-next-items-wrapper__item" - /> - ))} +
+ } + type="info" + className="what-next-items-wrapper__item" + /> diff --git a/frontend/src/container/CloudIntegrationPage/HeroSection/types.ts b/frontend/src/container/CloudIntegrationPage/HeroSection/types.ts index 0bda02de15..ab0ae861db 100644 --- a/frontend/src/container/CloudIntegrationPage/HeroSection/types.ts +++ b/frontend/src/container/CloudIntegrationPage/HeroSection/types.ts @@ -1,5 +1,6 @@ import { FormInstance } from 'antd'; import { Dispatch, SetStateAction } from 'react'; +import { ConnectionParams } from 'types/api/integrations/aws'; export enum ActiveViewEnum { SELECT_REGIONS = 'select-regions', @@ -25,9 +26,10 @@ export interface RegionFormProps { accountId?: string; selectedDeploymentRegion: string | undefined; handleRegionChange: (value: string) => void; + connectionParams?: ConnectionParams; + isConnectionParamsLoading?: boolean; } export interface IntegrationModalProps { - isOpen: boolean; onClose: () => void; } diff --git a/frontend/src/container/CloudIntegrationPage/ServicesSection/CloudServiceDashboards.tsx b/frontend/src/container/CloudIntegrationPage/ServicesSection/CloudServiceDashboards.tsx index f9e03cad2e..947eefef13 100644 --- a/frontend/src/container/CloudIntegrationPage/ServicesSection/CloudServiceDashboards.tsx +++ b/frontend/src/container/CloudIntegrationPage/ServicesSection/CloudServiceDashboards.tsx @@ -1,3 +1,5 @@ +import { Link } from 'react-router-dom'; + import { ServiceData } from './types'; function DashboardItem({ @@ -5,8 +7,8 @@ function DashboardItem({ }: { dashboard: ServiceData['assets']['dashboards'][number]; }): JSX.Element { - return ( -
+ const content = ( + <>
{dashboard.title}
+ + ); + + return ( +
+ {dashboard.url ? ( + + {content} + + ) : ( + content + )}
); } diff --git a/frontend/src/container/CloudIntegrationPage/ServicesSection/ServiceDetails.tsx b/frontend/src/container/CloudIntegrationPage/ServicesSection/ServiceDetails.tsx index e1df0b9113..ccbf6f79f7 100644 --- a/frontend/src/container/CloudIntegrationPage/ServicesSection/ServiceDetails.tsx +++ b/frontend/src/container/CloudIntegrationPage/ServicesSection/ServiceDetails.tsx @@ -1,4 +1,3 @@ -import { Color } from '@signozhq/design-tokens'; import { Button, Tabs, TabsProps } from 'antd'; import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer'; import Spinner from 'components/Spinner'; @@ -8,8 +7,7 @@ import { IServiceStatus } from 'container/CloudIntegrationPage/ServicesSection/t import dayjs from 'dayjs'; import { useServiceDetails } from 'hooks/integrations/aws/useServiceDetails'; import useUrlQuery from 'hooks/useUrlQuery'; -import { Wrench } from 'lucide-react'; -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import ConfigureServiceModal from './ConfigureServiceModal'; @@ -38,7 +36,7 @@ const getStatus = ( function ServiceStatus({ serviceStatus, }: { - serviceStatus: IServiceStatus | null; + serviceStatus: IServiceStatus | undefined; }): JSX.Element { const logsLastReceivedTimestamp = serviceStatus?.logs?.last_received_ts_ms; const metricsLastReceivedTimestamp = @@ -54,7 +52,7 @@ function ServiceStatus({ function ServiceDetails(): JSX.Element | null { const urlQuery = useUrlQuery(); - const accountId = urlQuery.get('accountId'); + const cloudAccountId = urlQuery.get('cloudAccountId'); const serviceId = urlQuery.get('service'); const [isConfigureServiceModalOpen, setIsConfigureServiceModalOpen] = useState( false, @@ -62,7 +60,24 @@ function ServiceDetails(): JSX.Element | null { const { data: serviceDetailsData, isLoading } = useServiceDetails( serviceId || '', - accountId || undefined, + cloudAccountId || undefined, + ); + + // eslint-disable-next-line @typescript-eslint/naming-convention + const { config, supported_signals } = serviceDetailsData ?? {}; + + const totalSupportedSignals = Object.entries(supported_signals || {}).filter( + ([, value]) => !!value, + ).length; + const enabledSignals = useMemo( + () => + Object.values(config || {}).filter((item) => item && item.enabled).length, + [config], + ); + + const isAnySignalConfigured = useMemo( + () => !!config?.logs?.enabled || !!config?.metrics?.enabled, + [config], ); if (isLoading) { @@ -96,16 +111,22 @@ function ServiceDetails(): JSX.Element | null {
Details
- {serviceDetailsData?.status && ( - - )} - {!!accountId && ( + + + {!!cloudAccountId && isAnySignalConfigured ? ( + ) : ( + )}
@@ -119,15 +140,17 @@ function ServiceDetails(): JSX.Element | null {
- setIsConfigureServiceModalOpen(false)} - serviceName={serviceDetailsData.title} - serviceId={serviceId || ''} - cloudAccountId={accountId || ''} - initialConfig={serviceDetailsData.config} - supportedSignals={serviceDetailsData.supported_signals || {}} - /> + {isConfigureServiceModalOpen && ( + setIsConfigureServiceModalOpen(false)} + serviceName={serviceDetailsData.title} + serviceId={serviceId || ''} + cloudAccountId={cloudAccountId || ''} + initialConfig={serviceDetailsData.config} + supportedSignals={serviceDetailsData.supported_signals || {}} + /> + )}
); } diff --git a/frontend/src/container/CloudIntegrationPage/ServicesSection/ServicesList.tsx b/frontend/src/container/CloudIntegrationPage/ServicesSection/ServicesList.tsx index bd80271117..e9d86bf00e 100644 --- a/frontend/src/container/CloudIntegrationPage/ServicesSection/ServicesList.tsx +++ b/frontend/src/container/CloudIntegrationPage/ServicesSection/ServicesList.tsx @@ -1,26 +1,34 @@ import Spinner from 'components/Spinner'; import { useGetAccountServices } from 'hooks/integrations/aws/useGetAccountServices'; import useUrlQuery from 'hooks/useUrlQuery'; -import { useMemo } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { useNavigate } from 'react-router-dom-v5-compat'; import ServiceItem from './ServiceItem'; interface ServicesListProps { - accountId: string; + cloudAccountId: string; filter: 'all_services' | 'enabled' | 'available'; } -function ServicesList({ accountId, filter }: ServicesListProps): JSX.Element { +function ServicesList({ + cloudAccountId, + filter, +}: ServicesListProps): JSX.Element { const urlQuery = useUrlQuery(); const navigate = useNavigate(); - const { data: services = [], isLoading } = useGetAccountServices(accountId); + const { data: services = [], isLoading } = useGetAccountServices( + cloudAccountId, + ); const activeService = urlQuery.get('service'); - const handleServiceClick = (serviceId: string): void => { - urlQuery.set('service', serviceId); - navigate({ search: urlQuery.toString() }); - }; + const handleActiveService = useCallback( + (serviceId: string): void => { + urlQuery.set('service', serviceId); + navigate({ search: urlQuery.toString() }); + }, + [navigate, urlQuery], + ); const filteredServices = useMemo(() => { if (filter === 'all_services') return services; @@ -32,6 +40,12 @@ function ServicesList({ accountId, filter }: ServicesListProps): JSX.Element { }); }, [services, filter]); + useEffect(() => { + if (activeService || !services?.length) return; + + handleActiveService(services[0].id); + }, [services, activeService, handleActiveService]); + if (isLoading) return ; if (!services) return
No services found
; @@ -41,7 +55,7 @@ function ServicesList({ accountId, filter }: ServicesListProps): JSX.Element { ))} diff --git a/frontend/src/container/CloudIntegrationPage/ServicesSection/ServicesTabs.style.scss b/frontend/src/container/CloudIntegrationPage/ServicesSection/ServicesTabs.style.scss index 45e4f25931..70b46a98e9 100644 --- a/frontend/src/container/CloudIntegrationPage/ServicesSection/ServicesTabs.style.scss +++ b/frontend/src/container/CloudIntegrationPage/ServicesSection/ServicesTabs.style.scss @@ -135,18 +135,25 @@ color: var(--bg-cherry-400); } } + .configure-button { - display: flex; - align-items: center; - gap: 6px; - color: var(--bg-vanilla-400); - background: var(--bg-ink-300); - border: 1px solid var(--bg-slate-400); border-radius: 2px; font-size: 12px; - font-weight: 400; - line-height: 10px; /* 83.333% */ letter-spacing: 0.12px; + font-weight: 500; + width: 116px; + box-shadow: none; + &--default { + color: var(--bg-vanilla-400); + background: var(--bg-slate-400); + border: 1px solid var(--bg-slate-400); + } + &--primary { + background-color: var(--bg-robin-500); + color: var(--bg-vanilla-100); + font-weight: 500; + color: var(--Vanilla-100, #fff); + } } } } diff --git a/frontend/src/container/CloudIntegrationPage/ServicesSection/ServicesTabs.tsx b/frontend/src/container/CloudIntegrationPage/ServicesSection/ServicesTabs.tsx index db22c32fa3..d49e265315 100644 --- a/frontend/src/container/CloudIntegrationPage/ServicesSection/ServicesTabs.tsx +++ b/frontend/src/container/CloudIntegrationPage/ServicesSection/ServicesTabs.tsx @@ -20,17 +20,17 @@ export enum ServiceFilterType { } interface ServicesFilterProps { - accountId: string; + cloudAccountId: string; onFilterChange: (value: ServiceFilterType) => void; } function ServicesFilter({ - accountId, + cloudAccountId, onFilterChange, }: ServicesFilterProps): JSX.Element | null { const { data: services, isLoading } = useQuery( - [REACT_QUERY_KEY.AWS_SERVICES, accountId], - () => getAwsServices(accountId), + [REACT_QUERY_KEY.AWS_SERVICES, cloudAccountId], + () => getAwsServices(cloudAccountId), ); const { enabledCount, availableCount } = useMemo(() => { @@ -77,7 +77,7 @@ function ServicesFilter({ function ServicesSection(): JSX.Element { const urlQuery = useUrlQuery(); - const accountId = urlQuery.get('accountId') || ''; + const cloudAccountId = urlQuery.get('cloudAccountId') || ''; const [activeFilter, setActiveFilter] = useState< 'all_services' | 'enabled' | 'available' @@ -86,8 +86,11 @@ function ServicesSection(): JSX.Element { return (
- - + +
diff --git a/frontend/src/hooks/integrations/aws/useConnectionParams.ts b/frontend/src/hooks/integrations/aws/useConnectionParams.ts new file mode 100644 index 0000000000..8593ef7698 --- /dev/null +++ b/frontend/src/hooks/integrations/aws/useConnectionParams.ts @@ -0,0 +1,17 @@ +import { getConnectionParams } from 'api/integrations/aws'; +import { AxiosError } from 'axios'; +import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; +import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query'; +import { ConnectionParams } from 'types/api/integrations/aws'; + +export function useConnectionParams({ + options, +}: { + options?: UseQueryOptions; +}): UseQueryResult { + return useQuery( + [REACT_QUERY_KEY.AWS_GET_CONNECTION_PARAMS], + getConnectionParams, + options, + ); +} diff --git a/frontend/src/hooks/integrations/aws/useIntegrationModal.ts b/frontend/src/hooks/integrations/aws/useIntegrationModal.ts index 2323377a06..5e33e3eb07 100644 --- a/frontend/src/hooks/integrations/aws/useIntegrationModal.ts +++ b/frontend/src/hooks/integrations/aws/useIntegrationModal.ts @@ -4,6 +4,7 @@ import { ActiveViewEnum, ModalStateEnum, } from 'container/CloudIntegrationPage/HeroSection/types'; +import useAxiosError from 'hooks/useAxiosError'; import { Dispatch, SetStateAction, @@ -13,11 +14,13 @@ import { useState, } from 'react'; import { + ConnectionParams, ConnectionUrlResponse, GenerateConnectionUrlPayload, } from 'types/api/integrations/aws'; import { regions } from 'utils/regions'; +import { useConnectionParams } from './useConnectionParams'; import { useGenerateConnectionUrl } from './useGenerateConnectionUrl'; interface UseIntegrationModalProps { @@ -44,6 +47,8 @@ interface UseIntegrationModal { accountId?: string; selectedDeploymentRegion: string | undefined; handleRegionChange: (value: string) => void; + connectionParams?: ConnectionParams; + isConnectionParamsLoading: boolean; } export function useIntegrationModal({ @@ -102,6 +107,12 @@ export function useIntegrationModal({ isLoading: isGeneratingUrl, } = useGenerateConnectionUrl(); + const handleError = useAxiosError(); + const { + data: connectionParams, + isLoading: isConnectionParamsLoading, + } = useConnectionParams({ options: { onError: handleError } }); + const handleGenerateUrl = useCallback( (payload: GenerateConnectionUrlPayload): void => { generateUrl(payload, { @@ -126,6 +137,9 @@ export function useIntegrationModal({ const payload: GenerateConnectionUrlPayload = { agent_config: { region: values.region, + ingestion_url: connectionParams?.ingestion_url || values.ingestion_url, + ingestion_key: connectionParams?.ingestion_key || values.ingestion_key, + signoz_api_url: connectionParams?.signoz_api_url || values.signoz_api_url, }, account_config: { regions: includeAllRegions ? ['all'] : selectedRegions, @@ -138,7 +152,13 @@ export function useIntegrationModal({ } finally { setIsLoading(false); } - }, [form, includeAllRegions, selectedRegions, handleGenerateUrl]); + }, [ + form, + includeAllRegions, + selectedRegions, + handleGenerateUrl, + connectionParams, + ]); return { form, @@ -160,5 +180,7 @@ export function useIntegrationModal({ setModalState, selectedDeploymentRegion, handleRegionChange, + connectionParams, + isConnectionParamsLoading, }; } diff --git a/frontend/src/hooks/integrations/aws/useServiceDetails.ts b/frontend/src/hooks/integrations/aws/useServiceDetails.ts index 7e304b5137..bb1584ac7c 100644 --- a/frontend/src/hooks/integrations/aws/useServiceDetails.ts +++ b/frontend/src/hooks/integrations/aws/useServiceDetails.ts @@ -5,11 +5,11 @@ import { useQuery, UseQueryResult } from 'react-query'; export function useServiceDetails( serviceId: string, - accountId?: string, + cloudAccountId?: string, ): UseQueryResult { return useQuery( - [REACT_QUERY_KEY.AWS_SERVICE_DETAILS, serviceId, accountId], - () => getServiceDetails(serviceId, accountId), + [REACT_QUERY_KEY.AWS_SERVICE_DETAILS, serviceId, cloudAccountId], + () => getServiceDetails(serviceId, cloudAccountId), { enabled: !!serviceId, }, diff --git a/frontend/src/pages/Integrations/IntegrationsList.tsx b/frontend/src/pages/Integrations/IntegrationsList.tsx index 901afcec92..89be2c7ec0 100644 --- a/frontend/src/pages/Integrations/IntegrationsList.tsx +++ b/frontend/src/pages/Integrations/IntegrationsList.tsx @@ -16,7 +16,7 @@ import { handleContactSupport, INTEGRATION_TYPES } from './utils'; export const AWS_INTEGRATION = { id: INTEGRATION_TYPES.AWS_INTEGRATION, - title: 'AWS Web Services', + title: 'Amazon Web Services', description: 'One-click setup for AWS monitoring with SigNoz', author: { name: 'SigNoz', diff --git a/frontend/src/pages/Integrations/utils.ts b/frontend/src/pages/Integrations/utils.ts index c3fbb4b2d6..330bf44c2e 100644 --- a/frontend/src/pages/Integrations/utils.ts +++ b/frontend/src/pages/Integrations/utils.ts @@ -19,6 +19,8 @@ export const INTEGRATION_TELEMETRY_EVENTS = { 'Integrations Detail Page: Clicked remove Integration button for integration', INTEGRATIONS_DETAIL_CONFIGURE_INSTRUCTION: 'Integrations Detail Page: Navigated to configure an integration', + AWS_INTEGRATION_ACCOUNT_REMOVED: + 'AWS Integration Detail page: Clicked remove Integration button for integration', }; export const INTEGRATION_TYPES = { diff --git a/frontend/src/types/api/integrations/aws.ts b/frontend/src/types/api/integrations/aws.ts index 31b191b231..e51df70d4c 100644 --- a/frontend/src/types/api/integrations/aws.ts +++ b/frontend/src/types/api/integrations/aws.ts @@ -1,9 +1,15 @@ import { CloudAccount } from 'container/CloudIntegrationPage/ServicesSection/types'; +export interface ConnectionParams { + ingestion_url?: string; + ingestion_key?: string; + signoz_api_url?: string; +} + export interface GenerateConnectionUrlPayload { agent_config: { region: string; - }; + } & ConnectionParams; account_config: { regions: string[]; };