feat: revamp integration flow (#4832)

* feat: revamp integration flow

* feat: final design changes

* feat: make test connection button grey
This commit is contained in:
Vikrant Gupta 2024-04-09 11:29:54 +05:30 committed by GitHub
parent 872ed9e963
commit 59c242961f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 178 additions and 37 deletions

View File

@ -13,12 +13,18 @@ interface IntegrationDetailContentProps {
activeDetailTab: string; activeDetailTab: string;
integrationData: IntegrationDetailedProps; integrationData: IntegrationDetailedProps;
integrationId: string; integrationId: string;
setActiveDetailTab: React.Dispatch<React.SetStateAction<string | null>>;
} }
function IntegrationDetailContent( function IntegrationDetailContent(
props: IntegrationDetailContentProps, props: IntegrationDetailContentProps,
): JSX.Element { ): JSX.Element {
const { activeDetailTab, integrationData, integrationId } = props; const {
activeDetailTab,
integrationData,
integrationId,
setActiveDetailTab,
} = props;
const items: TabsProps['items'] = [ const items: TabsProps['items'] = [
{ {
key: 'overview', key: 'overview',
@ -78,7 +84,11 @@ function IntegrationDetailContent(
]; ];
return ( return (
<div className="integration-detail-container"> <div className="integration-detail-container">
<Tabs defaultActiveKey={activeDetailTab} items={items} /> <Tabs
activeKey={activeDetailTab}
items={items}
onChange={setActiveDetailTab}
/>
</div> </div>
); );
} }

View File

@ -3,6 +3,8 @@ import './IntegrationDetailPage.styles.scss';
import { Button, Modal, Tooltip, Typography } from 'antd'; import { Button, Modal, Tooltip, Typography } from 'antd';
import installIntegration from 'api/Integrations/installIntegration'; import installIntegration from 'api/Integrations/installIntegration';
import ConfigureIcon from 'assets/Integrations/ConfigureIcon';
import cx from 'classnames';
import { SOMETHING_WENT_WRONG } from 'constants/api'; import { SOMETHING_WENT_WRONG } from 'constants/api';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import useAnalytics from 'hooks/analytics/useAnalytics'; import useAnalytics from 'hooks/analytics/useAnalytics';
@ -23,6 +25,7 @@ interface IntegrationDetailHeaderProps {
refetchIntegrationDetails: () => void; refetchIntegrationDetails: () => void;
connectionState: ConnectionStates; connectionState: ConnectionStates;
connectionData: IntegrationConnectionStatus; connectionData: IntegrationConnectionStatus;
setActiveDetailTab: React.Dispatch<React.SetStateAction<string | null>>;
} }
// eslint-disable-next-line sonarjs/cognitive-complexity // eslint-disable-next-line sonarjs/cognitive-complexity
function IntegrationDetailHeader( function IntegrationDetailHeader(
@ -36,6 +39,7 @@ function IntegrationDetailHeader(
connectionState, connectionState,
connectionData, connectionData,
refetchIntegrationDetails, refetchIntegrationDetails,
setActiveDetailTab,
} = props; } = props;
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
@ -106,6 +110,12 @@ function IntegrationDetailHeader(
last_received_from: connectionData.metrics.last_received_from, last_received_from: connectionData.metrics.last_received_from,
}; };
} }
const isConnectionStatePending =
connectionState === ConnectionStates.NotInstalled ||
connectionState === ConnectionStates.TestingConnection;
const isConnectionStateNotInstalled =
connectionState === ConnectionStates.NotInstalled;
return ( return (
<div className="integration-connection-header"> <div className="integration-connection-header">
<div className="integration-detail-header" key={id}> <div className="integration-detail-header" key={id}>
@ -119,7 +129,10 @@ function IntegrationDetailHeader(
</div> </div>
</div> </div>
<Button <Button
className="configure-btn" className={cx(
'configure-btn',
!isConnectionStateNotInstalled && 'test-connection',
)}
icon={<ArrowLeftRight size={14} />} icon={<ArrowLeftRight size={14} />}
disabled={isInstallLoading} disabled={isInstallLoading}
onClick={(): void => { onClick={(): void => {
@ -127,7 +140,6 @@ function IntegrationDetailHeader(
trackEvent(INTEGRATION_TELEMETRY_EVENTS.INTEGRATIONS_DETAIL_CONNECT, { trackEvent(INTEGRATION_TELEMETRY_EVENTS.INTEGRATIONS_DETAIL_CONNECT, {
integration: id, integration: id,
}); });
mutate({ integration_id: id, config: {} });
} else { } else {
trackEvent( trackEvent(
INTEGRATION_TELEMETRY_EVENTS.INTEGRATIONS_DETAIL_TEST_CONNECTION, INTEGRATION_TELEMETRY_EVENTS.INTEGRATIONS_DETAIL_TEST_CONNECTION,
@ -136,13 +148,11 @@ function IntegrationDetailHeader(
connectionStatus: connectionState, connectionStatus: connectionState,
}, },
); );
showModal();
} }
showModal();
}} }}
> >
{connectionState === ConnectionStates.NotInstalled {isConnectionStateNotInstalled ? `Connect ${title}` : `Test Connection`}
? `Connect ${title}`
: `Test Connection`}
</Button> </Button>
</div> </div>
@ -153,15 +163,67 @@ function IntegrationDetailHeader(
<Modal <Modal
className="test-connection-modal" className="test-connection-modal"
open={isModalOpen} open={isModalOpen}
title="Test Connection" title={
onOk={handleOk} isConnectionStateNotInstalled
? `Connect ${title}`
: `Test ${title} Connection`
}
onCancel={handleCancel} onCancel={handleCancel}
okText="I understand" footer={
okButtonProps={{ className: 'understandBtn', icon: <Check size={14} /> }} <div
cancelButtonProps={{ style: { display: 'none' } }} className={cx(
'connection-footer',
!isConnectionStatePending && 'not-pending',
)}
>
<Button
type="text"
icon={
isConnectionStateNotInstalled ? <ConfigureIcon /> : <Check size={14} />
}
onClick={(): void => {
if (isConnectionStateNotInstalled) {
setActiveDetailTab('configuration');
}
handleOk();
}}
className="understandBtn"
>
{isConnectionStatePending
? isConnectionStateNotInstalled
? 'Show Configuration Steps'
: 'I have already configured'
: 'I understand'}
</Button>
{isConnectionStatePending && (
<Button
type="primary"
icon={
isConnectionStateNotInstalled ? <Check size={14} /> : <ConfigureIcon />
}
onClick={(): void => {
if (isConnectionStateNotInstalled) {
mutate({ integration_id: id, config: {} });
} else {
setActiveDetailTab('configuration');
}
handleOk();
}}
className="configureBtn"
>
{isConnectionStateNotInstalled
? 'I have already configured'
: 'Show Configuration Steps'}
</Button>
)}
</div>
}
> >
<div className="connection-content"> <div className="connection-content">
<TestConnection connectionState={connectionState} /> {!isConnectionStateNotInstalled && (
<TestConnection connectionState={connectionState} />
)}
{connectionState === ConnectionStates.Connected || {connectionState === ConnectionStates.Connected ||
connectionState === ConnectionStates.NoDataSinceLong ? ( connectionState === ConnectionStates.NoDataSinceLong ? (
<> <>
@ -210,12 +272,26 @@ function IntegrationDetailHeader(
) : connectionState === ConnectionStates.TestingConnection ? ( ) : connectionState === ConnectionStates.TestingConnection ? (
<div className="data-test-connection"> <div className="data-test-connection">
<div className="last-data"> <div className="last-data">
After adding the {title} integration, you need to manually configure We have not received data from your {title} Instance yet. You need to
your Redis data source to start sending data to SigNoz. manually configure your {title} instance to start sending data to
SigNoz.
</div> </div>
<div className="last-data"> <div className="last-data">
The status bar above would turn green if we are successfully receiving If you have already configured your resources to send data, sit tight
the data. and wait for the data to flow in, Or else, see the steps to configure
your resources to start sending data.
</div>
</div>
) : isConnectionStateNotInstalled ? (
<div className="data-test-connection">
<div className="last-data">
You would need to manually configure your {title} instance to start
sending data to SigNoz.
</div>
<div className="last-data">
If you have already configured your resources to send data, sit tight
and wait for the data to flow in, Or else, see the steps to configure
your resources to start sending data.
</div> </div>
</div> </div>
) : null} ) : null}

View File

@ -160,6 +160,14 @@
font-weight: 500; font-weight: 500;
line-height: 10px; /* 83.333% */ line-height: 10px; /* 83.333% */
letter-spacing: 0.12px; letter-spacing: 0.12px;
box-shadow: none;
&.test-connection {
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
color: var(--bg-vanilla-400);
}
} }
} }
@ -380,25 +388,56 @@
display: flex; display: flex;
flex-direction: row-reverse; flex-direction: row-reverse;
.understandBtn { .connection-footer {
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
box-shadow: none;
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 10px; /* 83.333% */
letter-spacing: 0.12px;
display: flex; display: flex;
justify-content: center; width: 100%;
align-items: center; .understandBtn {
width: 131px; width: 50%;
height: 30px; border-radius: 2px;
padding: 6px; border: 1px solid var(--bg-slate-400);
flex-shrink: 0; background: var(--bg-ink-300);
box-shadow: none;
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 10px; /* 83.333% */
letter-spacing: 0.12px;
display: flex;
justify-content: center;
align-items: center;
height: 34px;
padding: 6px;
flex-shrink: 0;
}
.configureBtn {
width: 50%;
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 10px; /* 83.333% */
letter-spacing: 0.12px;
border-radius: 2px;
background: var(--bg-robin-500);
display: flex;
height: 34px;
padding: 6px;
justify-content: center;
align-items: center;
gap: 6px;
flex: 1 0 0;
}
&.not-pending {
flex-direction: row-reverse;
.understandBtn {
width: 131px;
}
}
} }
} }
} }
@ -544,6 +583,13 @@
color: var(--bg-slate-200); color: var(--bg-slate-200);
} }
} }
.configure-btn {
&.test-connection {
border: 1px solid rgba(53, 59, 76, 0.2);
background: var(--bg-vanilla-200);
color: var(--bg-slate-200);
}
}
} }
.testingConnection { .testingConnection {

View File

@ -22,10 +22,16 @@ interface IntegrationDetailPageProps {
selectedIntegration: string; selectedIntegration: string;
setSelectedIntegration: (id: string | null) => void; setSelectedIntegration: (id: string | null) => void;
activeDetailTab: string; activeDetailTab: string;
setActiveDetailTab: React.Dispatch<React.SetStateAction<string | null>>;
} }
function IntegrationDetailPage(props: IntegrationDetailPageProps): JSX.Element { function IntegrationDetailPage(props: IntegrationDetailPageProps): JSX.Element {
const { selectedIntegration, setSelectedIntegration, activeDetailTab } = props; const {
selectedIntegration,
setSelectedIntegration,
activeDetailTab,
setActiveDetailTab,
} = props;
const { const {
data, data,
@ -119,11 +125,13 @@ function IntegrationDetailPage(props: IntegrationDetailPageProps): JSX.Element {
metrics: null, metrics: null,
})} })}
refetchIntegrationDetails={refetch} refetchIntegrationDetails={refetch}
setActiveDetailTab={setActiveDetailTab}
/> />
<IntegrationDetailContent <IntegrationDetailContent
activeDetailTab={activeDetailTab} activeDetailTab={activeDetailTab}
integrationData={integrationData} integrationData={integrationData}
integrationId={selectedIntegration} integrationId={selectedIntegration}
setActiveDetailTab={setActiveDetailTab}
/> />
{connectionStatus !== ConnectionStates.NotInstalled && ( {connectionStatus !== ConnectionStates.NotInstalled && (

View File

@ -55,6 +55,7 @@ function Integrations(): JSX.Element {
selectedIntegration={selectedIntegration} selectedIntegration={selectedIntegration}
setSelectedIntegration={setSelectedIntegration} setSelectedIntegration={setSelectedIntegration}
activeDetailTab={activeDetailTab} activeDetailTab={activeDetailTab}
setActiveDetailTab={setActiveDetailTab}
/> />
) : ( ) : (
<> <>