From 00d74bfebb83428e973239f61422b8bc460c82f5 Mon Sep 17 00:00:00 2001 From: Vikrant Gupta Date: Mon, 1 Apr 2024 12:40:15 +0530 Subject: [PATCH] feat: add integrations to the side-nav for cloud users (#4756) * feat: add integrations to the side-nav for cloud users * feat: change the route from integrations/installed to /integrations * feat: light mode table color * feat: increase the width of the integrations panel by 25 percent * feat: added telemetry constants and page view * feat: added telemetry events for integrations * feat: address review comments --- frontend/public/locales/en/titles.json | 2 +- frontend/src/AppRoutes/pageComponents.ts | 8 ------- frontend/src/AppRoutes/routes.ts | 12 ++--------- frontend/src/constants/routes.ts | 4 +--- frontend/src/container/SideNav/SideNav.tsx | 11 ++++++++++ frontend/src/container/SideNav/menuItems.tsx | 12 +++++------ .../TopNav/DateTimeSelectionV2/config.ts | 4 +--- .../IntegrationDetailContent.tsx | 10 +++++++-- .../Configure.tsx | 20 ++++++++++++++++-- .../IntegrationDetailContentTabs.styles.scss | 4 ++-- .../IntegrationDetailHeader.tsx | 14 +++++++++++++ .../IntegrationDetailPage.tsx | 2 ++ .../IntegrationsUninstallBar.tsx | 21 ++++++++++++++++++- .../Integrations/Integrations.styles.scss | 2 +- .../src/pages/Integrations/Integrations.tsx | 16 ++++++++++++-- frontend/src/pages/Integrations/utils.ts | 12 +++++++++++ .../IntegrationsModulePage/constants.tsx | 4 ++-- frontend/src/utils/permission/index.ts | 4 +--- 18 files changed, 116 insertions(+), 46 deletions(-) diff --git a/frontend/public/locales/en/titles.json b/frontend/public/locales/en/titles.json index e707c998f7..8aef9c9af6 100644 --- a/frontend/public/locales/en/titles.json +++ b/frontend/public/locales/en/titles.json @@ -48,5 +48,5 @@ "TRACES_SAVE_VIEWS": "SigNoz | Traces Saved Views", "DEFAULT": "Open source Observability Platform | SigNoz", "SHORTCUTS": "SigNoz | Shortcuts", - "INTEGRATIONS_INSTALLED": "SigNoz | Integrations" + "INTEGRATIONS": "SigNoz | Integrations" } diff --git a/frontend/src/AppRoutes/pageComponents.ts b/frontend/src/AppRoutes/pageComponents.ts index bea07a7e51..1252496c08 100644 --- a/frontend/src/AppRoutes/pageComponents.ts +++ b/frontend/src/AppRoutes/pageComponents.ts @@ -197,11 +197,3 @@ export const InstalledIntegrations = Loadable( /* webpackChunkName: "InstalledIntegrations" */ 'pages/IntegrationsModulePage' ), ); - -export const IntegrationsMarketPlace = Loadable( - // eslint-disable-next-line sonarjs/no-identical-functions - () => - import( - /* webpackChunkName: "IntegrationsMarketPlace" */ 'pages/IntegrationsModulePage' - ), -); diff --git a/frontend/src/AppRoutes/routes.ts b/frontend/src/AppRoutes/routes.ts index 360c74d8da..fed77f186e 100644 --- a/frontend/src/AppRoutes/routes.ts +++ b/frontend/src/AppRoutes/routes.ts @@ -15,7 +15,6 @@ import { ErrorDetails, IngestionSettings, InstalledIntegrations, - IntegrationsMarketPlace, LicensePage, ListAllALertsPage, LiveLogs, @@ -338,18 +337,11 @@ const routes: AppRoutes[] = [ key: 'SHORTCUTS', }, { - path: ROUTES.INTEGRATIONS_INSTALLED, + path: ROUTES.INTEGRATIONS, exact: true, component: InstalledIntegrations, isPrivate: true, - key: 'INTEGRATIONS_INSTALLED', - }, - { - path: ROUTES.INTEGRATIONS_MARKETPLACE, - exact: true, - component: IntegrationsMarketPlace, - isPrivate: true, - key: 'INTEGRATIONS_MARKETPLACE', + key: 'INTEGRATIONS', }, ]; diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts index 0b087ff8cd..cbeb672a5c 100644 --- a/frontend/src/constants/routes.ts +++ b/frontend/src/constants/routes.ts @@ -51,9 +51,7 @@ const ROUTES = { TRACES_SAVE_VIEWS: '/traces/saved-views', WORKSPACE_LOCKED: '/workspace-locked', SHORTCUTS: '/shortcuts', - INTEGRATIONS_BASE: '/integrations', - INTEGRATIONS_INSTALLED: '/integrations/installed', - INTEGRATIONS_MARKETPLACE: '/integrations/marketplace', + INTEGRATIONS: '/integrations', } as const; export default ROUTES; diff --git a/frontend/src/container/SideNav/SideNav.tsx b/frontend/src/container/SideNav/SideNav.tsx index 665a406710..6b11ae140c 100644 --- a/frontend/src/container/SideNav/SideNav.tsx +++ b/frontend/src/container/SideNav/SideNav.tsx @@ -271,6 +271,17 @@ function SideNav({ } }, [isCloudUserVal, isEnterprise, isFetching]); + useEffect(() => { + if (!isCloudUserVal) { + let updatedMenuItems = [...menuItems]; + updatedMenuItems = updatedMenuItems.filter( + (item) => item.key !== ROUTES.INTEGRATIONS, + ); + setMenuItems(updatedMenuItems); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const [isCurrentOrgSettings] = useComponentPermission( ['current_org_settings'], role, diff --git a/frontend/src/container/SideNav/menuItems.tsx b/frontend/src/container/SideNav/menuItems.tsx index ed6f10b10a..38529a7f3b 100644 --- a/frontend/src/container/SideNav/menuItems.tsx +++ b/frontend/src/container/SideNav/menuItems.tsx @@ -16,6 +16,7 @@ import { ScrollText, Settings, Slack, + Unplug, // Unplug, UserPlus, } from 'lucide-react'; @@ -90,11 +91,11 @@ const menuItems: SidebarItem[] = [ label: 'Alerts', icon: , }, - // { - // key: ROUTES.INTEGRATIONS_INSTALLED, - // label: 'Integrations', - // icon: , - // }, + { + key: ROUTES.INTEGRATIONS, + label: 'Integrations', + icon: , + }, { key: ROUTES.ALL_ERROR, label: 'Exceptions', @@ -127,7 +128,6 @@ export const NEW_ROUTES_MENU_ITEM_KEY_MAP: Record = { [ROUTES.TRACES_EXPLORER]: ROUTES.TRACE, [ROUTES.TRACE_EXPLORER]: ROUTES.TRACE, [ROUTES.LOGS_BASE]: ROUTES.LOGS_EXPLORER, - [ROUTES.INTEGRATIONS_BASE]: ROUTES.INTEGRATIONS_INSTALLED, }; export default menuItems; diff --git a/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts b/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts index f92beb6b8d..eefa31475c 100644 --- a/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts +++ b/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts @@ -199,9 +199,7 @@ export const routesToSkip = [ ROUTES.TRACES_EXPLORER, ROUTES.TRACES_SAVE_VIEWS, ROUTES.SHORTCUTS, - ROUTES.INTEGRATIONS_BASE, - ROUTES.INTEGRATIONS_INSTALLED, - ROUTES.INTEGRATIONS_MARKETPLACE, + ROUTES.INTEGRATIONS, ]; export const routesToDisable = [ROUTES.LOGS_EXPLORER, ROUTES.LIVE_LOGS]; diff --git a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContent.tsx b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContent.tsx index ec81d51db6..c0b3a52f44 100644 --- a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContent.tsx +++ b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContent.tsx @@ -12,12 +12,13 @@ import Overview from './IntegrationDetailContentTabs/Overview'; interface IntegrationDetailContentProps { activeDetailTab: string; integrationData: IntegrationDetailedProps; + integrationId: string; } function IntegrationDetailContent( props: IntegrationDetailContentProps, ): JSX.Element { - const { activeDetailTab, integrationData } = props; + const { activeDetailTab, integrationData, integrationId } = props; const items: TabsProps['items'] = [ { key: 'overview', @@ -49,7 +50,12 @@ function IntegrationDetailContent( Configure ), - children: , + children: ( + + ), }, { key: 'dataCollected', diff --git a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/Configure.tsx b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/Configure.tsx index 92a5e0c823..2984ba40fe 100644 --- a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/Configure.tsx +++ b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/Configure.tsx @@ -3,20 +3,36 @@ import './IntegrationDetailContentTabs.styles.scss'; import { Button, Typography } from 'antd'; import cx from 'classnames'; import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer'; -import { useState } from 'react'; +import useAnalytics from 'hooks/analytics/useAnalytics'; +import { INTEGRATION_TELEMETRY_EVENTS } from 'pages/Integrations/utils'; +import { useEffect, useState } from 'react'; interface ConfigurationProps { configuration: Array<{ title: string; instructions: string }>; + integrationId: string; } function Configure(props: ConfigurationProps): JSX.Element { // TODO Mardown renderer support once instructions are ready - const { configuration } = props; + const { configuration, integrationId } = props; const [selectedConfigStep, setSelectedConfigStep] = useState(0); const handleMenuClick = (index: number): void => { setSelectedConfigStep(index); }; + + const { trackEvent } = useAnalytics(); + + useEffect(() => { + trackEvent( + INTEGRATION_TELEMETRY_EVENTS.INTEGRATIONS_DETAIL_CONFIGURE_INSTRUCTION, + { + integration: integrationId, + }, + ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return (
diff --git a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/IntegrationDetailContentTabs.styles.scss b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/IntegrationDetailContentTabs.styles.scss index 81dcb6bf59..bf542f539d 100644 --- a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/IntegrationDetailContentTabs.styles.scss +++ b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailContentTabs/IntegrationDetailContentTabs.styles.scss @@ -260,7 +260,7 @@ .logs-section { .table-row-dark { - background: rgba(255, 255, 255, 0.01); + background: var(--bg-vanilla-300); } .logs-section-table { @@ -271,7 +271,7 @@ .metrics-section { .table-row-dark { - background: rgba(255, 255, 255, 0.01); + background: var(--bg-vanilla-300); } .metrics-section-table { diff --git a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailHeader.tsx b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailHeader.tsx index cab49391f5..f630f3ecc4 100644 --- a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailHeader.tsx +++ b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailHeader.tsx @@ -5,12 +5,14 @@ import { Button, Modal, Tooltip, Typography } from 'antd'; import installIntegration from 'api/Integrations/installIntegration'; import { SOMETHING_WENT_WRONG } from 'constants/api'; import dayjs from 'dayjs'; +import useAnalytics from 'hooks/analytics/useAnalytics'; import { useNotifications } from 'hooks/useNotifications'; import { ArrowLeftRight, Check } from 'lucide-react'; import { useState } from 'react'; import { useMutation } from 'react-query'; import { IntegrationConnectionStatus } from 'types/api/integrations/types'; +import { INTEGRATION_TELEMETRY_EVENTS } from '../utils'; import TestConnection, { ConnectionStates } from './TestConnection'; interface IntegrationDetailHeaderProps { @@ -37,6 +39,8 @@ function IntegrationDetailHeader( } = props; const [isModalOpen, setIsModalOpen] = useState(false); + const { trackEvent } = useAnalytics(); + const { notifications } = useNotifications(); const showModal = (): void => { @@ -120,8 +124,18 @@ function IntegrationDetailHeader( disabled={isInstallLoading} onClick={(): void => { if (connectionState === ConnectionStates.NotInstalled) { + trackEvent(INTEGRATION_TELEMETRY_EVENTS.INTEGRATIONS_DETAIL_CONNECT, { + integration: id, + }); mutate({ integration_id: id, config: {} }); } else { + trackEvent( + INTEGRATION_TELEMETRY_EVENTS.INTEGRATIONS_DETAIL_TEST_CONNECTION, + { + integration: id, + connectionStatus: connectionState, + }, + ); showModal(); } }} diff --git a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailPage.tsx b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailPage.tsx index 88be0dc3a3..a0e97dfe1c 100644 --- a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailPage.tsx +++ b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationDetailPage.tsx @@ -123,6 +123,7 @@ function IntegrationDetailPage(props: IntegrationDetailPageProps): JSX.Element { {connectionStatus !== ConnectionStates.NotInstalled && ( @@ -130,6 +131,7 @@ function IntegrationDetailPage(props: IntegrationDetailPageProps): JSX.Element { integrationTitle={defaultTo(integrationData?.title, '')} integrationId={selectedIntegration} refetchIntegrationDetails={refetch} + connectionStatus={connectionStatus} /> )} diff --git a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationsUninstallBar.tsx b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationsUninstallBar.tsx index 41e985abf8..a1ad762ec6 100644 --- a/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationsUninstallBar.tsx +++ b/frontend/src/pages/Integrations/IntegrationDetailPage/IntegrationsUninstallBar.tsx @@ -3,23 +3,35 @@ import './IntegrationDetailPage.styles.scss'; import { Button, Modal, Typography } from 'antd'; import unInstallIntegration from 'api/Integrations/uninstallIntegration'; import { SOMETHING_WENT_WRONG } from 'constants/api'; +import useAnalytics from 'hooks/analytics/useAnalytics'; import { useNotifications } from 'hooks/useNotifications'; import { X } from 'lucide-react'; import { useState } from 'react'; import { useMutation } from 'react-query'; +import { INTEGRATION_TELEMETRY_EVENTS } from '../utils'; +import { ConnectionStates } from './TestConnection'; + interface IntergrationsUninstallBarProps { integrationTitle: string; integrationId: string; refetchIntegrationDetails: () => void; + connectionStatus: ConnectionStates; } function IntergrationsUninstallBar( props: IntergrationsUninstallBarProps, ): JSX.Element { - const { integrationTitle, integrationId, refetchIntegrationDetails } = props; + const { + integrationTitle, + integrationId, + refetchIntegrationDetails, + connectionStatus, + } = props; const { notifications } = useNotifications(); const [isModalOpen, setIsModalOpen] = useState(false); + const { trackEvent } = useAnalytics(); + const { mutate: uninstallIntegration, isLoading: isUninstallLoading, @@ -40,6 +52,13 @@ function IntergrationsUninstallBar( }; const handleOk = (): void => { + trackEvent( + INTEGRATION_TELEMETRY_EVENTS.INTEGRATIONS_DETAIL_REMOVE_INTEGRATION, + { + integration: integrationId, + integrationStatus: connectionStatus, + }, + ); uninstallIntegration({ integration_id: integrationId, }); diff --git a/frontend/src/pages/Integrations/Integrations.styles.scss b/frontend/src/pages/Integrations/Integrations.styles.scss index 794b596407..aec8433a26 100644 --- a/frontend/src/pages/Integrations/Integrations.styles.scss +++ b/frontend/src/pages/Integrations/Integrations.styles.scss @@ -6,7 +6,7 @@ .integrations-content { width: calc(100% - 30px); - max-width: 736px; + max-width: 920px; .integrations-header { .title { diff --git a/frontend/src/pages/Integrations/Integrations.tsx b/frontend/src/pages/Integrations/Integrations.tsx index bda4184eab..1f1644fbc7 100644 --- a/frontend/src/pages/Integrations/Integrations.tsx +++ b/frontend/src/pages/Integrations/Integrations.tsx @@ -1,18 +1,22 @@ import './Integrations.styles.scss'; +import useAnalytics from 'hooks/analytics/useAnalytics'; import useUrlQuery from 'hooks/useUrlQuery'; -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; import Header from './Header'; import IntegrationDetailPage from './IntegrationDetailPage/IntegrationDetailPage'; import IntegrationsList from './IntegrationsList'; +import { INTEGRATION_TELEMETRY_EVENTS } from './utils'; function Integrations(): JSX.Element { const urlQuery = useUrlQuery(); const history = useHistory(); const location = useLocation(); + const { trackPageView, trackEvent } = useAnalytics(); + const selectedIntegration = useMemo(() => urlQuery.get('integration'), [ urlQuery, ]); @@ -20,6 +24,9 @@ function Integrations(): JSX.Element { const setSelectedIntegration = useCallback( (integration: string | null) => { if (integration) { + trackEvent(INTEGRATION_TELEMETRY_EVENTS.INTEGRATIONS_ITEM_LIST_CLICKED, { + integration, + }); urlQuery.set('integration', integration); } else { urlQuery.set('integration', ''); @@ -27,13 +34,18 @@ function Integrations(): JSX.Element { const generatedUrl = `${location.pathname}?${urlQuery.toString()}`; history.push(generatedUrl); }, - [history, location.pathname, urlQuery], + [history, location.pathname, trackEvent, urlQuery], ); const [activeDetailTab, setActiveDetailTab] = useState( 'overview', ); + useEffect(() => { + trackPageView(location.pathname); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const [searchTerm, setSearchTerm] = useState(''); return (
diff --git a/frontend/src/pages/Integrations/utils.ts b/frontend/src/pages/Integrations/utils.ts index 81c70b6091..a244da4c82 100644 --- a/frontend/src/pages/Integrations/utils.ts +++ b/frontend/src/pages/Integrations/utils.ts @@ -7,3 +7,15 @@ export const handleContactSupport = (isCloudUser: boolean): void => { window.open('https://signoz.io/slack', '_blank'); } }; + +export const INTEGRATION_TELEMETRY_EVENTS = { + INTEGRATIONS_ITEM_LIST_CLICKED: 'Integrations Page: Clicked an integration', + INTEGRATIONS_DETAIL_CONNECT: + 'Integrations Detail Page: Clicked connect integration button', + INTEGRATIONS_DETAIL_TEST_CONNECTION: + 'Integrations Detail Page: Clicked test Connection button for integration', + INTEGRATIONS_DETAIL_REMOVE_INTEGRATION: + 'Integrations Detail Page: Clicked remove Integration button for integration', + INTEGRATIONS_DETAIL_CONFIGURE_INSTRUCTION: + 'Integrations Detail Page: Navigated to configure an integration', +}; diff --git a/frontend/src/pages/IntegrationsModulePage/constants.tsx b/frontend/src/pages/IntegrationsModulePage/constants.tsx index d0100798a8..333c9df44c 100644 --- a/frontend/src/pages/IntegrationsModulePage/constants.tsx +++ b/frontend/src/pages/IntegrationsModulePage/constants.tsx @@ -10,6 +10,6 @@ export const installedIntegrations: TabRoutes = { Integrations
), - route: ROUTES.INTEGRATIONS_INSTALLED, - key: ROUTES.INTEGRATIONS_INSTALLED, + route: ROUTES.INTEGRATIONS, + key: ROUTES.INTEGRATIONS, }; diff --git a/frontend/src/utils/permission/index.ts b/frontend/src/utils/permission/index.ts index b18be180cd..1c992965d4 100644 --- a/frontend/src/utils/permission/index.ts +++ b/frontend/src/utils/permission/index.ts @@ -96,7 +96,5 @@ export const routePermission: Record = { LOGS_BASE: [], OLD_LOGS_EXPLORER: [], SHORTCUTS: ['ADMIN', 'EDITOR', 'VIEWER'], - INTEGRATIONS_BASE: ['ADMIN', 'EDITOR', 'VIEWER'], - INTEGRATIONS_INSTALLED: ['ADMIN', 'EDITOR', 'VIEWER'], - INTEGRATIONS_MARKETPLACE: ['ADMIN', 'EDITOR', 'VIEWER'], + INTEGRATIONS: ['ADMIN', 'EDITOR', 'VIEWER'], };