1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-15 01:16:22 +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 apiKey={apiKey}
sdk={sdk} sdk={sdk}
apiKey={apiKey} feature={feature}
feature={feature} onSdkChange={() => {
onSdkChange={() => { setStage('select-sdk');
setStage('select-sdk'); }}
}} />
/>
</Suspense>
) : null} ) : null}
{stage === 'generate-api-key' ? ( {stage === 'generate-api-key' ? (
@ -152,22 +154,25 @@ export const ConnectSdkDialog = ({
{isTestConnectionStage ? ( {isTestConnectionStage ? (
<Navigation> <Navigation>
<NextStepSectionSpacedContainer> <NextStepSectionSpacedContainer>
<Button {!onboarded ? (
variant='text' <Button
color='inherit' variant='text'
onClick={() => { color='inherit'
setStage('generate-api-key'); onClick={() => {
}} setStage('generate-api-key');
> }}
Back >
</Button> Back
</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,28 +86,38 @@ export const ConnectionInformation = ({
<Typography variant='body2'>{sdk}</Typography> <Typography variant='body2'>{sdk}</Typography>
</Info> </Info>
</SdkInfo> </SdkInfo>
<ConnectionStatus> {onboarded ? (
<Typography fontWeight='bold' variant='body2'> <ConnectionStatus>
Connection status <Typography fontWeight='bold' variant='body2'>
</Typography> Connection status
<Typography sx={{ mb: theme.spacing(4) }} variant='body2'> </Typography>
Waiting for SDK data... <Typography sx={{ mb: theme.spacing(4) }} variant='body2'>
</Typography> Connected
<ConditionallyRender </Typography>
condition={true} <StyledCheck />
show={ <Typography sx={{ mb: theme.spacing(4) }} variant='body2'>
<WhitePulsingAvatar We received metrics from your application!
sx={{ </Typography>
width: 80, </ConnectionStatus>
height: 80, ) : (
}} <ConnectionStatus>
active={true} <Typography fontWeight='bold' variant='body2'>
> Connection status
<Pending fontSize='large' /> </Typography>
</WhitePulsingAvatar> <Typography sx={{ mb: theme.spacing(4) }} variant='body2'>
} Waiting for SDK data...
/> </Typography>
</ConnectionStatus> <WhitePulsingAvatar
sx={{
width: 80,
height: 80,
}}
active={true}
>
<Pending fontSize='large' />
</WhitePulsingAvatar>
</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,17 +534,19 @@ export const ProjectFeatureToggles = ({
</PageContent> </PageContent>
} }
/> />
{'feature' in project.onboardingStatus ? ( <ConnectSdkDialog
<ConnectSdkDialog open={connectSdkOpen}
open={connectSdkOpen} onClose={() => {
onClose={() => { setConnectSdkOpen(false);
setConnectSdkOpen(false); }}
}} project={projectId}
project={projectId} environments={environments}
environments={environments} feature={
feature={project.onboardingStatus.feature} 'feature' in project.onboardingStatus
/> ? project.onboardingStatus.feature
) : null} : undefined
}
/>
<BatchSelectionActionsBar count={selectedData.length}> <BatchSelectionActionsBar count={selectedData.length}>
<ProjectFeaturesBatchActions <ProjectFeaturesBatchActions
selectedIds={Object.keys(rowSelection)} selectedIds={Object.keys(rowSelection)}