1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

feat: new onboarding welcome screen logic (#8110)

1. We will not show grid until 2 flags exist
2. Now new feature creation button will be always displayed on top with
different style
3. Moved some text around


![image](https://github.com/user-attachments/assets/6cfc2152-b52d-479c-8a2e-988c9e8b79ad)
This commit is contained in:
Jaanus Sellin 2024-09-06 13:15:28 +03:00 committed by GitHub
parent f0ba4e5180
commit b6e22d6178
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 178 additions and 122 deletions

View File

@ -4,6 +4,8 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import type { ITooltipResolverProps } from '../TooltipResolver/TooltipResolver';
import type { OverridableStringUnion } from '@mui/types';
import type { ButtonPropsVariantOverrides } from '@mui/material/Button/Button';
interface IResponsiveButtonProps {
Icon: React.ElementType;
@ -15,6 +17,10 @@ interface IResponsiveButtonProps {
projectId?: string;
environmentId?: string;
maxWidth: string;
variant?: OverridableStringUnion<
'text' | 'outlined' | 'contained',
ButtonPropsVariantOverrides
>;
className?: string;
children?: React.ReactNode;
}
@ -29,6 +35,7 @@ const ResponsiveButton: React.FC<IResponsiveButtonProps> = ({
environmentId,
projectId,
endIcon,
variant,
...rest
}) => {
const smallScreen = useMediaQuery(`(max-width:${maxWidth})`);
@ -55,7 +62,7 @@ const ResponsiveButton: React.FC<IResponsiveButtonProps> = ({
permission={permission}
projectId={projectId}
color='primary'
variant='contained'
variant={variant}
disabled={disabled}
environmentId={environmentId}
endIcon={endIcon}

View File

@ -114,6 +114,11 @@ export const ProjectFeatureToggles = ({
const isPlaceholder = Boolean(initialLoad || (loading && total));
const onboardingStarted =
onboardingUIEnabled && project.onboardingStatus.status !== 'onboarded';
const hasMultipleFeaturesOrNotOnboarding =
(total !== undefined && total > 1) || !onboardingStarted;
const columns = useMemo(
() => [
columnHelper.display({
@ -399,10 +404,7 @@ export const ProjectFeatureToggles = ({
return (
<Container>
<ConditionallyRender
condition={
onboardingUIEnabled &&
project.onboardingStatus.status !== 'onboarded'
}
condition={onboardingStarted}
show={
<ProjectOnboarding
projectId={projectId}
@ -410,111 +412,126 @@ export const ProjectFeatureToggles = ({
/>
}
/>
<PageContent
disableLoading
disablePadding
header={
<ProjectFeatureTogglesHeader
isLoading={initialLoad}
totalItems={total}
searchQuery={tableState.query || ''}
onChangeSearchQuery={(query) => {
setTableState({ query });
}}
dataToExport={data}
environmentsToExport={environments}
actions={
<ColumnsMenu
columns={[
{
header: 'Name',
id: 'name',
isVisible: columnVisibility.name,
isStatic: true,
},
{
header: 'Created',
id: 'createdAt',
isVisible: columnVisibility.createdAt,
},
{
header: 'By',
id: 'createdBy',
isVisible: columnVisibility.createdBy,
},
{
header: 'Last seen',
id: 'lastSeenAt',
isVisible: columnVisibility.lastSeenAt,
},
{
header: 'Lifecycle',
id: 'lifecycle',
isVisible: columnVisibility.lifecycle,
},
{
id: 'divider',
},
...environments.map((environment) => ({
header: environment,
id: formatEnvironmentColumnId(
environment,
),
isVisible:
columnVisibility[
formatEnvironmentColumnId(
environment,
)
],
})),
]}
onToggle={onToggleColumnVisibility}
<ConditionallyRender
condition={hasMultipleFeaturesOrNotOnboarding}
show={
<PageContent
disableLoading
disablePadding
header={
<ProjectFeatureTogglesHeader
isLoading={initialLoad}
totalItems={total}
searchQuery={tableState.query || ''}
onChangeSearchQuery={(query) => {
setTableState({ query });
}}
dataToExport={data}
environmentsToExport={environments}
actions={
<ColumnsMenu
columns={[
{
header: 'Name',
id: 'name',
isVisible:
columnVisibility.name,
isStatic: true,
},
{
header: 'Created',
id: 'createdAt',
isVisible:
columnVisibility.createdAt,
},
{
header: 'By',
id: 'createdBy',
isVisible:
columnVisibility.createdBy,
},
{
header: 'Last seen',
id: 'lastSeenAt',
isVisible:
columnVisibility.lastSeenAt,
},
{
header: 'Lifecycle',
id: 'lifecycle',
isVisible:
columnVisibility.lifecycle,
},
{
id: 'divider',
},
...environments.map(
(environment) => ({
header: environment,
id: formatEnvironmentColumnId(
environment,
),
isVisible:
columnVisibility[
formatEnvironmentColumnId(
environment,
)
],
}),
),
]}
onToggle={onToggleColumnVisibility}
/>
}
/>
}
/>
bodyClass='noop'
style={{ cursor: 'inherit' }}
>
<div
ref={bodyLoadingRef}
aria-busy={isPlaceholder}
aria-live='polite'
>
<ProjectOverviewFilters
project={projectId}
onChange={setTableState}
state={filterState}
/>
<SearchHighlightProvider
value={tableState.query || ''}
>
<PaginatedTable
tableInstance={table}
totalItems={total}
/>
</SearchHighlightProvider>
<ConditionallyRender
condition={!data.length && !isPlaceholder}
show={
<TableEmptyState
query={tableState.query || ''}
/>
}
/>
{rowActionsDialogs}
{featureToggleModals}
</div>
</PageContent>
}
bodyClass='noop'
style={{ cursor: 'inherit' }}
>
<div
ref={bodyLoadingRef}
aria-busy={isPlaceholder}
aria-live='polite'
>
<ProjectOverviewFilters
project={projectId}
onChange={setTableState}
state={filterState}
/>
<SearchHighlightProvider value={tableState.query || ''}>
<PaginatedTable
tableInstance={table}
totalItems={total}
/>
</SearchHighlightProvider>
<ConditionallyRender
condition={!data.length && !isPlaceholder}
show={
<TableEmptyState query={tableState.query || ''} />
}
/>
{rowActionsDialogs}
{featureToggleModals}
{'feature' in project.onboardingStatus ? (
<ConnectSdkDialog
open={connectSdkOpen}
onClose={() => {
setConnectSdkOpen(false);
}}
project={projectId}
environments={environments}
feature={project.onboardingStatus.feature}
/>
) : null}
</div>
</PageContent>
/>
{'feature' in project.onboardingStatus ? (
<ConnectSdkDialog
open={connectSdkOpen}
onClose={() => {
setConnectSdkOpen(false);
}}
project={projectId}
environments={environments}
feature={project.onboardingStatus.feature}
/>
) : null}
<BatchSelectionActionsBar count={selectedData.length}>
<ProjectFeaturesBatchActions
selectedIds={Object.keys(rowSelection)}

View File

@ -26,6 +26,8 @@ import { useFeedback } from 'component/feedbackNew/useFeedback';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { CreateFeatureDialog } from './CreateFeatureDialog';
import IosShare from '@mui/icons-material/IosShare';
import type { OverridableStringUnion } from '@mui/types';
import type { ButtonPropsVariantOverrides } from '@mui/material/Button/Button';
interface IProjectFeatureTogglesHeaderProps {
isLoading?: boolean;
@ -37,11 +39,22 @@ interface IProjectFeatureTogglesHeaderProps {
actions?: ReactNode;
}
interface IFlagCreationButtonProps {
text?: string;
variant?: OverridableStringUnion<
'text' | 'outlined' | 'contained',
ButtonPropsVariantOverrides
>;
}
const StyledResponsiveButton = styled(ResponsiveButton)(() => ({
whiteSpace: 'nowrap',
}));
export const FlagCreationButton: FC = () => {
export const FlagCreationButton = ({
variant,
text = 'New feature flag',
}: IFlagCreationButtonProps) => {
const [searchParams] = useSearchParams();
const projectId = useRequiredPathParam('projectId');
const showCreateDialog = Boolean(searchParams.get('create'));
@ -56,10 +69,11 @@ export const FlagCreationButton: FC = () => {
Icon={Add}
projectId={projectId}
disabled={loading}
variant={variant}
permission={CREATE_FEATURE}
data-testid='NAVIGATE_TO_CREATE_FEATURE'
>
New feature flag
{text}
</StyledResponsiveButton>
<CreateFeatureDialog
open={openCreateDialog}

View File

@ -1,4 +1,4 @@
import { styled, Typography, useTheme } from '@mui/material';
import { styled, Typography } from '@mui/material';
import Add from '@mui/icons-material/Add';
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
import { FlagCreationButton } from '../ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader';
@ -76,13 +76,24 @@ const TypeCircleContainer = styled(MainCircleContainer)(({ theme }) => ({
borderRadius: '20%',
}));
const StyledLink = styled(Link)({
const FlagLink = styled(Link)({
fontWeight: 'bold',
textDecoration: 'none',
display: 'flex',
justifyContent: 'center',
});
const ExistingFlagContainer = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(3),
height: '100%',
}));
const FlagCreationContainer = styled('div')(({ theme }) => ({
marginTop: 'auto',
}));
export const WelcomeToProject = ({
projectId,
setConnectSdkOpen,
@ -119,7 +130,9 @@ export const WelcomeToProject = ({
Connect an SDK
</TitleContainer>
<Typography>
We have not detected any connected SDKs on this project.
Your project is not yet connected to any SDK. In order
to start using your feature flag connect an SDK to the
project.
</Typography>
<ResponsiveButton
onClick={() => {
@ -150,13 +163,12 @@ const CreateFlag = () => {
<div>The project currently holds no feature toggles.</div>
<div>Create a feature flag to get started.</div>
</Typography>
<FlagCreationButton />
<FlagCreationButton text='Create flag' />
</>
);
};
const ExistingFlag = ({ featureId, projectId }: IExistingFlagsProps) => {
const theme = useTheme();
const { feature } = useFeature(projectId, featureId);
const { featureTypes } = useFeatureTypes();
const IconComponent = getFeatureTypeIcons(feature.type);
@ -166,7 +178,7 @@ const ExistingFlag = ({ featureId, projectId }: IExistingFlagsProps) => {
const typeTitle = `${typeName || feature.type} flag`;
return (
<>
<ExistingFlagContainer>
<TitleContainer>
<MainCircleContainer></MainCircleContainer>
Create a feature flag
@ -177,16 +189,21 @@ const ExistingFlag = ({ featureId, projectId }: IExistingFlagsProps) => {
<IconComponent />
</TypeCircleContainer>
</HtmlTooltip>
<StyledLink
<FlagLink
to={`/projects/${projectId}/features/${feature.name}`}
>
{feature.name}
</StyledLink>
</FlagLink>
<Link to={`/projects/${projectId}/features/${feature.name}`}>
view flag
</Link>
</TitleContainer>
<Typography>
Your project is not yet connected to any SDK. In order to start
using your feature flag connect an SDK to the project.
</Typography>
</>
<FlagCreationContainer>
<FlagCreationButton
variant='outlined'
text='Create a new flag'
/>
</FlagCreationContainer>
</ExistingFlagContainer>
);
};

View File

@ -86,6 +86,7 @@ export class OnboardingReadModel implements IOnboardingReadModel {
const feature = await this.db('features')
.select('name')
.where('project', projectId)
.where('archived_at', null)
.first();
if (!feature) {