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
This commit is contained in:
Vikrant Gupta 2024-04-01 12:40:15 +05:30 committed by GitHub
parent 39e0ef68ca
commit 00d74bfebb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 116 additions and 46 deletions

View File

@ -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"
}

View File

@ -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'
),
);

View File

@ -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',
},
];

View File

@ -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;

View File

@ -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,

View File

@ -16,6 +16,7 @@ import {
ScrollText,
Settings,
Slack,
Unplug,
// Unplug,
UserPlus,
} from 'lucide-react';
@ -90,11 +91,11 @@ const menuItems: SidebarItem[] = [
label: 'Alerts',
icon: <BellDot size={16} />,
},
// {
// key: ROUTES.INTEGRATIONS_INSTALLED,
// label: 'Integrations',
// icon: <Unplug size={16} />,
// },
{
key: ROUTES.INTEGRATIONS,
label: 'Integrations',
icon: <Unplug size={16} />,
},
{
key: ROUTES.ALL_ERROR,
label: 'Exceptions',
@ -127,7 +128,6 @@ export const NEW_ROUTES_MENU_ITEM_KEY_MAP: Record<string, string> = {
[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;

View File

@ -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];

View File

@ -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(
<Typography.Text className="typography">Configure</Typography.Text>
</Button>
),
children: <Configure configuration={integrationData.configuration} />,
children: (
<Configure
configuration={integrationData.configuration}
integrationId={integrationId}
/>
),
},
{
key: 'dataCollected',

View File

@ -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 (
<div className="integration-detail-configure">
<div className="configure-menu">

View File

@ -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 {

View File

@ -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();
}
}}

View File

@ -123,6 +123,7 @@ function IntegrationDetailPage(props: IntegrationDetailPageProps): JSX.Element {
<IntegrationDetailContent
activeDetailTab={activeDetailTab}
integrationData={integrationData}
integrationId={selectedIntegration}
/>
{connectionStatus !== ConnectionStates.NotInstalled && (
@ -130,6 +131,7 @@ function IntegrationDetailPage(props: IntegrationDetailPageProps): JSX.Element {
integrationTitle={defaultTo(integrationData?.title, '')}
integrationId={selectedIntegration}
refetchIntegrationDetails={refetch}
connectionStatus={connectionStatus}
/>
)}
</>

View File

@ -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,
});

View File

@ -6,7 +6,7 @@
.integrations-content {
width: calc(100% - 30px);
max-width: 736px;
max-width: 920px;
.integrations-header {
.title {

View File

@ -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<string | null>(
'overview',
);
useEffect(() => {
trackPageView(location.pathname);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const [searchTerm, setSearchTerm] = useState<string>('');
return (
<div className="integrations-container">

View File

@ -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',
};

View File

@ -10,6 +10,6 @@ export const installedIntegrations: TabRoutes = {
<Compass size={16} /> Integrations
</div>
),
route: ROUTES.INTEGRATIONS_INSTALLED,
key: ROUTES.INTEGRATIONS_INSTALLED,
route: ROUTES.INTEGRATIONS,
key: ROUTES.INTEGRATIONS,
};

View File

@ -96,7 +96,5 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
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'],
};