1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-06-18 01:18:23 +02:00

feat: display new completed dialog (#8255)

1. Now the dialog will not close when SDK got connected
2. It will start to show the suggested production code. ( this will be
attached in next PR)
3. Also, it has connected indicator on the right
4. Back button is removed in this stage.


![image](https://github.com/user-attachments/assets/c7290e0f-8fa7-4382-a91d-7206e32d81ae)

---------

Co-authored-by: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com>
This commit is contained in:
Jaanus Sellin 2024-09-26 09:24:15 +03:00 committed by GitHub
parent c7427f4b91
commit aca0de414e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 272 additions and 72 deletions

View File

@ -7,22 +7,21 @@ import {
useTheme, useTheme,
} from '@mui/material'; } from '@mui/material';
import { GenerateApiKey } from './GenerateApiKey'; import { GenerateApiKey } from './GenerateApiKey';
import { lazy, Suspense, useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { SelectSdk } from './SelectSdk'; import { SelectSdk } from './SelectSdk';
import { GenerateApiKeyConcepts, SelectSdkConcepts } from './UnleashConcepts'; import { GenerateApiKeyConcepts, SelectSdkConcepts } from './UnleashConcepts';
const TestSdkConnection = lazy(() => import('./TestSdkConnection'));
import type { Sdk } from './sharedTypes'; import type { Sdk } from './sharedTypes';
import { ConnectionInformation } from './ConnectionInformation'; import { ConnectionInformation } from './ConnectionInformation';
import Loader from 'component/common/Loader/Loader'; import { SdkConnection } from './SdkConnection';
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
interface IConnectSDKDialogProps { interface IConnectSDKDialogProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
project: string; project: string;
environments: string[]; environments: string[];
feature: string; feature?: string;
} }
const ConnectSdk = styled('main')(({ theme }) => ({ const ConnectSdk = styled('main')(({ theme }) => ({
@ -68,7 +67,7 @@ export const ConnectSdkDialog = ({
open, open,
onClose, onClose,
environments, environments,
project, project: projectId,
feature, feature,
}: IConnectSDKDialogProps) => { }: IConnectSDKDialogProps) => {
const theme = useTheme(); const theme = useTheme();
@ -77,6 +76,9 @@ export const ConnectSdkDialog = ({
const [environment, setEnvironment] = useState<string | null>(null); const [environment, setEnvironment] = useState<string | null>(null);
const [apiKey, setApiKey] = useState<string | null>(null); const [apiKey, setApiKey] = useState<string | null>(null);
const [stage, setStage] = useState<OnboardingStage>('select-sdk'); const [stage, setStage] = useState<OnboardingStage>('select-sdk');
const { project } = useProjectOverview(projectId, {
refreshInterval: 1000,
});
const isSelectSdkStage = stage === 'select-sdk'; const isSelectSdkStage = stage === 'select-sdk';
const isGenerateApiKeyStage = const isGenerateApiKeyStage =
@ -84,6 +86,8 @@ export const ConnectSdkDialog = ({
const isTestConnectionStage = const isTestConnectionStage =
stage === 'test-connection' && sdk && environment && apiKey; stage === 'test-connection' && sdk && environment && apiKey;
const onboarded = project.onboardingStatus.status === 'onboarded';
useEffect(() => { useEffect(() => {
if (environments.length > 0) { if (environments.length > 0) {
setEnvironment(environments[0]); setEnvironment(environments[0]);
@ -106,23 +110,21 @@ export const ConnectSdkDialog = ({
<GenerateApiKey <GenerateApiKey
environments={environments} environments={environments}
environment={environment} environment={environment}
project={project} project={projectId}
sdkType={sdk.type} sdkType={sdk.type}
onEnvSelect={setEnvironment} onEnvSelect={setEnvironment}
onApiKey={setApiKey} onApiKey={setApiKey}
/> />
) : null} ) : null}
{isTestConnectionStage ? ( {isTestConnectionStage ? (
<Suspense fallback={<Loader />}> <SdkConnection
<TestSdkConnection
sdk={sdk}
apiKey={apiKey} apiKey={apiKey}
sdk={sdk}
feature={feature} feature={feature}
onSdkChange={() => { onSdkChange={() => {
setStage('select-sdk'); setStage('select-sdk');
}} }}
/> />
</Suspense>
) : null} ) : null}
{stage === 'generate-api-key' ? ( {stage === 'generate-api-key' ? (
@ -152,6 +154,7 @@ export const ConnectSdkDialog = ({
{isTestConnectionStage ? ( {isTestConnectionStage ? (
<Navigation> <Navigation>
<NextStepSectionSpacedContainer> <NextStepSectionSpacedContainer>
{!onboarded ? (
<Button <Button
variant='text' variant='text'
color='inherit' color='inherit'
@ -161,13 +164,15 @@ export const ConnectSdkDialog = ({
> >
Back Back
</Button> </Button>
) : null}
<Button <Button
variant='contained' variant='contained'
onClick={() => { onClick={() => {
onClose(); onClose();
}} }}
> >
Finish Next
</Button> </Button>
</NextStepSectionSpacedContainer> </NextStepSectionSpacedContainer>
</Navigation> </Navigation>
@ -182,10 +187,9 @@ export const ConnectSdkDialog = ({
) : null} ) : null}
{isLargeScreen && isTestConnectionStage ? ( {isLargeScreen && isTestConnectionStage ? (
<ConnectionInformation <ConnectionInformation
projectId={project} projectId={projectId}
sdk={sdk.name} sdk={sdk.name}
environment={environment} environment={environment}
onConnection={onClose}
/> />
) : null} ) : null}
</Box> </Box>

View File

@ -1,12 +1,10 @@
import { styled, Typography, useTheme } from '@mui/material'; import { styled, Typography, useTheme } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { WhitePulsingAvatar } from 'component/common/PulsingAvatar/PulsingAvatar'; import { WhitePulsingAvatar } from 'component/common/PulsingAvatar/PulsingAvatar';
import Pending from '@mui/icons-material/Pending'; import Pending from '@mui/icons-material/Pending';
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview'; import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
import { useEffect } from 'react'; import Check from '@mui/icons-material/Check';
interface IConnectionInformationProps { interface IConnectionInformationProps {
onConnection: () => void;
projectId: string; projectId: string;
sdk: string; sdk: string;
environment: string; environment: string;
@ -50,8 +48,16 @@ export const ConnectionStatus = styled('div')(({ theme }) => ({
fontSize: theme.fontSizes.smallBody, fontSize: theme.fontSizes.smallBody,
})); }));
export const StyledCheck = styled(Check)(({ theme }) => ({
color: theme.palette.primary.main,
backgroundColor: theme.palette.background.paper,
borderRadius: '50%',
padding: theme.spacing(1),
width: '80px',
height: '80px',
}));
export const ConnectionInformation = ({ export const ConnectionInformation = ({
onConnection,
projectId, projectId,
sdk, sdk,
environment, environment,
@ -63,12 +69,6 @@ export const ConnectionInformation = ({
const onboarded = project.onboardingStatus.status === 'onboarded'; const onboarded = project.onboardingStatus.status === 'onboarded';
useEffect(() => {
if (onboarded) {
onConnection();
}
}, [onboarded]);
return ( return (
<Container> <Container>
<Title>Connection information</Title> <Title>Connection information</Title>
@ -86,6 +86,20 @@ export const ConnectionInformation = ({
<Typography variant='body2'>{sdk}</Typography> <Typography variant='body2'>{sdk}</Typography>
</Info> </Info>
</SdkInfo> </SdkInfo>
{onboarded ? (
<ConnectionStatus>
<Typography fontWeight='bold' variant='body2'>
Connection status
</Typography>
<Typography sx={{ mb: theme.spacing(4) }} variant='body2'>
Connected
</Typography>
<StyledCheck />
<Typography sx={{ mb: theme.spacing(4) }} variant='body2'>
We received metrics from your application!
</Typography>
</ConnectionStatus>
) : (
<ConnectionStatus> <ConnectionStatus>
<Typography fontWeight='bold' variant='body2'> <Typography fontWeight='bold' variant='body2'>
Connection status Connection status
@ -93,9 +107,6 @@ export const ConnectionInformation = ({
<Typography sx={{ mb: theme.spacing(4) }} variant='body2'> <Typography sx={{ mb: theme.spacing(4) }} variant='body2'>
Waiting for SDK data... Waiting for SDK data...
</Typography> </Typography>
<ConditionallyRender
condition={true}
show={
<WhitePulsingAvatar <WhitePulsingAvatar
sx={{ sx={{
width: 80, width: 80,
@ -105,9 +116,8 @@ export const ConnectionInformation = ({
> >
<Pending fontSize='large' /> <Pending fontSize='large' />
</WhitePulsingAvatar> </WhitePulsingAvatar>
}
/>
</ConnectionStatus> </ConnectionStatus>
)}
</Container> </Container>
); );
}; };

View File

@ -0,0 +1,150 @@
import type { FC } from 'react';
import { Box, IconButton, styled, Tooltip, Typography } from '@mui/material';
import { SectionHeader, StepperBox } from './SharedComponents';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import type { SdkName, Sdk } from './sharedTypes';
import copy from 'copy-to-clipboard';
import useToast from 'hooks/useToast';
import CopyIcon from '@mui/icons-material/FileCopy';
import { Stepper } from './Stepper';
import { Badge } from '../common/Badge/Badge';
import { Markdown } from 'component/common/Markdown/Markdown';
import type { CodeComponent } from 'react-markdown/lib/ast-to-react';
import android from './snippets/android.md?raw';
import go from './snippets/go.md?raw';
import javascript from './snippets/javascript.md?raw';
import nodejs from './snippets/nodejs.md?raw';
import python from './snippets/python.md?raw';
import ruby from './snippets/ruby.md?raw';
import svelte from './snippets/svelte.md?raw';
import vue from './snippets/vue.md?raw';
import flutter from './snippets/flutter.md?raw';
import java from './snippets/java.md?raw';
import dotnet from './snippets/dotnet.md?raw';
import php from './snippets/php.md?raw';
import react from './snippets/react.md?raw';
import rust from './snippets/rust.md?raw';
import swift from './snippets/swift.md?raw';
const snippets: Record<SdkName, string> = {
Android: android,
Go: go,
JavaScript: javascript,
'Node.js': nodejs,
Python: python,
Ruby: ruby,
Svelte: svelte,
Vue: vue,
Flutter: flutter,
Java: java,
'.NET': dotnet,
PHP: php,
React: react,
Rust: rust,
Swift: swift,
};
const SpacedContainer = styled('div')(({ theme }) => ({
padding: theme.spacing(5, 8, 2, 8),
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(3),
}));
const StyledCodeBlock = styled('pre')(({ theme }) => ({
backgroundColor: theme.palette.background.elevation1,
padding: theme.spacing(2),
borderRadius: theme.shape.borderRadius,
overflow: 'auto',
fontSize: theme.typography.body2.fontSize,
wordBreak: 'break-all',
whiteSpace: 'pre-wrap',
position: 'relative',
maxHeight: theme.spacing(34),
}));
const CopyToClipboard = styled(Tooltip)(({ theme }) => ({
position: 'absolute',
top: theme.spacing(1),
right: theme.spacing(1),
}));
const CopyBlock: FC<{ title: string; code: string }> = ({ title, code }) => {
const onCopyToClipboard = (data: string) => () => {
copy(data);
setToastData({
type: 'success',
title: 'Copied to clipboard',
});
};
const { setToastData } = useToast();
return (
<StyledCodeBlock>
{code}
<CopyToClipboard title={title} arrow>
<IconButton onClick={onCopyToClipboard(code)} size='small'>
<CopyIcon />
</IconButton>
</CopyToClipboard>
</StyledCodeBlock>
);
};
const ChangeSdk = styled('div')(({ theme }) => ({
display: 'inline-flex',
gap: theme.spacing(3),
padding: theme.spacing(1, 2),
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius,
marginBottom: theme.spacing(3),
}));
const CodeRenderer: CodeComponent = ({ inline = false, children }) => {
if (!inline && typeof children?.[0] === 'string') {
return <CopyBlock code={children[0]} title='Copy code' />;
}
return <code>{children}</code>;
};
interface ISdkConnectedProps {
sdk: Sdk;
}
export const SdkConnected: FC<ISdkConnectedProps> = ({ sdk }) => {
const { uiConfig } = useUiConfig();
const clientApiUrl = `${uiConfig.unleashUrl}/api/`;
const frontendApiUrl = `${uiConfig.unleashUrl}/api/frontend/`;
const apiUrl = sdk.type === 'client' ? clientApiUrl : frontendApiUrl;
const snippet = (snippets[sdk.name] || '').replace(
'<YOUR_API_URL>',
apiUrl,
);
return (
<SpacedContainer>
<Typography variant='h2'>Connect an SDK to Unleash</Typography>
<StepperBox>
<Stepper active={2} steps={3} />
<Badge color='secondary'>3/3 - Test connection</Badge>
</StepperBox>
<Box sx={{ mt: 2 }}>
<SectionHeader>Production settings</SectionHeader>
<Typography variant='body2'>
In order to validate the connection, we changed some
settings that you might want to revert. We recommend the
following default settings.
</Typography>
<Markdown components={{ code: CodeRenderer }}>
{snippet}
</Markdown>
</Box>
</SpacedContainer>
);
};
// Use a default export for lazy-loading
export default SdkConnected;

View File

@ -0,0 +1,34 @@
import { Suspense } from 'react';
import Loader from '../common/Loader/Loader';
import TestSdkConnection from './TestSdkConnection';
import type { Sdk } from './sharedTypes';
import { SdkConnected } from './SdkConnected';
interface ISdkConnectionProps {
sdk: Sdk;
apiKey: string;
feature?: string;
onSdkChange: () => void;
}
export const SdkConnection = ({
sdk,
apiKey,
feature,
onSdkChange,
}: ISdkConnectionProps) => {
return (
<Suspense fallback={<Loader />}>
{feature ? (
<TestSdkConnection
sdk={sdk}
apiKey={apiKey}
feature={feature}
onSdkChange={onSdkChange}
/>
) : (
<SdkConnected sdk={sdk} />
)}
</Suspense>
);
};

View File

@ -534,7 +534,6 @@ export const ProjectFeatureToggles = ({
</PageContent> </PageContent>
} }
/> />
{'feature' in project.onboardingStatus ? (
<ConnectSdkDialog <ConnectSdkDialog
open={connectSdkOpen} open={connectSdkOpen}
onClose={() => { onClose={() => {
@ -542,9 +541,12 @@ export const ProjectFeatureToggles = ({
}} }}
project={projectId} project={projectId}
environments={environments} environments={environments}
feature={project.onboardingStatus.feature} feature={
'feature' in project.onboardingStatus
? project.onboardingStatus.feature
: undefined
}
/> />
) : null}
<BatchSelectionActionsBar count={selectedData.length}> <BatchSelectionActionsBar count={selectedData.length}>
<ProjectFeaturesBatchActions <ProjectFeaturesBatchActions
selectedIds={Object.keys(rowSelection)} selectedIds={Object.keys(rowSelection)}