mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
feat: make invite link more visible (#4984)
This commit is contained in:
parent
65f424156c
commit
69286339fc
@ -9,51 +9,9 @@ import { add, formatDistanceToNowStrict, isAfter, parseISO } from 'date-fns';
|
|||||||
import { formatDateYMD } from 'utils/formatDate';
|
import { formatDateYMD } from 'utils/formatDate';
|
||||||
import { useLocationSettings } from 'hooks/useLocationSettings';
|
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
|
import { InviteLinkBarContent } from './InviteLinkBarContent';
|
||||||
|
|
||||||
export const InviteLinkBar: VFC = () => {
|
export const InviteLinkBar: VFC = () => {
|
||||||
const navigate = useNavigate();
|
|
||||||
const { data, loading } = useInviteTokens();
|
|
||||||
const ref = useLoading(loading);
|
|
||||||
const { trackEvent } = usePlausibleTracker();
|
|
||||||
const inviteToken =
|
|
||||||
data?.tokens?.find((token) => token.name === 'default') ?? null;
|
|
||||||
const inviteLink = inviteToken?.url;
|
|
||||||
const createdAt = data?.tokens?.[0]?.createdAt ?? '';
|
|
||||||
const expiresAt = data?.tokens?.[0]?.expiresAt ?? '';
|
|
||||||
const expires = expiresAt || false;
|
|
||||||
const isExpired = Boolean(
|
|
||||||
expires && isAfter(new Date(), parseISO(expires)),
|
|
||||||
);
|
|
||||||
const willExpireSoon =
|
|
||||||
expires && isAfter(add(new Date(), { days: 14 }), parseISO(expires));
|
|
||||||
const expiresIn = expires
|
|
||||||
? formatDistanceToNowStrict(parseISO(expires))
|
|
||||||
: false;
|
|
||||||
const { locationSettings } = useLocationSettings();
|
|
||||||
|
|
||||||
const expireDateComponent = (
|
|
||||||
<Typography
|
|
||||||
component='span'
|
|
||||||
variant='body2'
|
|
||||||
color={willExpireSoon ? 'warning.dark' : 'inherit'}
|
|
||||||
fontWeight='bold'
|
|
||||||
>
|
|
||||||
{expiresIn}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
|
|
||||||
const onInviteLinkActionClick = () => {
|
|
||||||
trackEvent('invite', {
|
|
||||||
props: {
|
|
||||||
eventType: inviteLink
|
|
||||||
? 'link bar action: edit'
|
|
||||||
: 'link bar action: create',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
navigate('/admin/invite-link');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
@ -67,70 +25,8 @@ export const InviteLinkBar: VFC = () => {
|
|||||||
border: '2px solid',
|
border: '2px solid',
|
||||||
borderColor: theme.palette.background.alternative,
|
borderColor: theme.palette.background.alternative,
|
||||||
})}
|
})}
|
||||||
ref={ref}
|
|
||||||
>
|
>
|
||||||
<Box
|
<InviteLinkBarContent />
|
||||||
sx={{
|
|
||||||
mb: { xs: 1, md: 0 },
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
flexDirection: 'column',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={Boolean(inviteLink)}
|
|
||||||
show={
|
|
||||||
<>
|
|
||||||
<Typography variant='body2' sx={{ mb: 1 }}>
|
|
||||||
{`You have an invite link created on ${formatDateYMD(
|
|
||||||
createdAt,
|
|
||||||
locationSettings.locale,
|
|
||||||
)} `}
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={isExpired}
|
|
||||||
show={
|
|
||||||
<>
|
|
||||||
that expired {expireDateComponent}{' '}
|
|
||||||
ago
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
elseShow={
|
|
||||||
<>
|
|
||||||
that will expire in{' '}
|
|
||||||
{expireDateComponent}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Typography>
|
|
||||||
<LinkField small inviteLink={inviteLink!} />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
elseShow={
|
|
||||||
<Typography variant='body2' data-loading>
|
|
||||||
You can easily create an invite link here that you
|
|
||||||
can share and use to invite people from your company
|
|
||||||
to your Unleash setup.
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
minWidth: 200,
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: { xs: 'center', md: 'flex-end' },
|
|
||||||
alignItems: 'center',
|
|
||||||
flexGrow: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
variant='outlined'
|
|
||||||
onClick={onInviteLinkActionClick}
|
|
||||||
data-loading
|
|
||||||
>
|
|
||||||
{inviteLink ? 'Update' : 'Create'} invite link
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,135 @@
|
|||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { Box, Button, styled, Typography } from '@mui/material';
|
||||||
|
import useLoading from 'hooks/useLoading';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { useInviteTokens } from 'hooks/api/getters/useInviteTokens/useInviteTokens';
|
||||||
|
import { LinkField } from '../LinkField/LinkField';
|
||||||
|
import { add, formatDistanceToNowStrict, isAfter, parseISO } from 'date-fns';
|
||||||
|
import { formatDateYMD } from 'utils/formatDate';
|
||||||
|
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||||
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
|
|
||||||
|
interface IInviteLinkBarContentProps {
|
||||||
|
onActionClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StyledBox = styled(Box)(() => ({
|
||||||
|
mb: {
|
||||||
|
xs: 1,
|
||||||
|
md: 0,
|
||||||
|
},
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const StyledButtonBox = styled(Box)(() => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexGrow: 1,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const InviteLinkBarContent = ({
|
||||||
|
onActionClick,
|
||||||
|
}: IInviteLinkBarContentProps) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { data, loading } = useInviteTokens();
|
||||||
|
const ref = useLoading(loading);
|
||||||
|
const { trackEvent } = usePlausibleTracker();
|
||||||
|
const inviteToken =
|
||||||
|
data?.tokens?.find((token) => token.name === 'default') ?? null;
|
||||||
|
const inviteLink = inviteToken?.url;
|
||||||
|
const createdAt = data?.tokens?.[0]?.createdAt ?? '';
|
||||||
|
const expiresAt = data?.tokens?.[0]?.expiresAt ?? '';
|
||||||
|
const expires = expiresAt || false;
|
||||||
|
const isExpired = Boolean(
|
||||||
|
expires && isAfter(new Date(), parseISO(expires)),
|
||||||
|
);
|
||||||
|
const willExpireSoon =
|
||||||
|
expires && isAfter(add(new Date(), { days: 14 }), parseISO(expires));
|
||||||
|
const expiresIn = expires
|
||||||
|
? formatDistanceToNowStrict(parseISO(expires))
|
||||||
|
: false;
|
||||||
|
const { locationSettings } = useLocationSettings();
|
||||||
|
|
||||||
|
const expireDateComponent = (
|
||||||
|
<Typography
|
||||||
|
component='span'
|
||||||
|
variant='body2'
|
||||||
|
color={willExpireSoon ? 'warning.dark' : 'inherit'}
|
||||||
|
fontWeight='bold'
|
||||||
|
>
|
||||||
|
{expiresIn}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
|
||||||
|
const onInviteLinkActionClick = () => {
|
||||||
|
trackEvent('invite', {
|
||||||
|
props: {
|
||||||
|
eventType: inviteLink
|
||||||
|
? 'link bar action: edit'
|
||||||
|
: 'link bar action: create',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
navigate('/admin/invite-link');
|
||||||
|
onActionClick?.();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StyledBox ref={ref}>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(inviteLink)}
|
||||||
|
show={
|
||||||
|
<>
|
||||||
|
<Typography variant='body2' sx={{ mb: 1 }}>
|
||||||
|
{`You have an invite link created on ${formatDateYMD(
|
||||||
|
createdAt,
|
||||||
|
locationSettings.locale,
|
||||||
|
)} `}
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={isExpired}
|
||||||
|
show={
|
||||||
|
<>
|
||||||
|
that expired {expireDateComponent}{' '}
|
||||||
|
ago
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
elseShow={
|
||||||
|
<>
|
||||||
|
that will expire in{' '}
|
||||||
|
{expireDateComponent}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
<LinkField small inviteLink={inviteLink!} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
elseShow={
|
||||||
|
<Typography variant='body2' data-loading>
|
||||||
|
You can easily create an invite link here that you
|
||||||
|
can share and use to invite people from your company
|
||||||
|
to your Unleash setup.
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledBox>
|
||||||
|
<StyledButtonBox
|
||||||
|
sx={{
|
||||||
|
justifyContent: {
|
||||||
|
xs: 'center',
|
||||||
|
md: 'flex-end',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant='outlined'
|
||||||
|
onClick={onInviteLinkActionClick}
|
||||||
|
data-loading
|
||||||
|
>
|
||||||
|
{inviteLink ? 'Update' : 'Create'} invite link
|
||||||
|
</Button>
|
||||||
|
</StyledButtonBox>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -34,6 +34,8 @@ import { ThemeMode } from 'component/common/ThemeMode/ThemeMode';
|
|||||||
import { useThemeMode } from 'hooks/useThemeMode';
|
import { useThemeMode } from 'hooks/useThemeMode';
|
||||||
import { Notifications } from 'component/common/Notifications/Notifications';
|
import { Notifications } from 'component/common/Notifications/Notifications';
|
||||||
import { useAdminRoutes } from 'component/admin/useAdminRoutes';
|
import { useAdminRoutes } from 'component/admin/useAdminRoutes';
|
||||||
|
import InviteLinkButton from './InviteLink/InviteLinkButton/InviteLinkButton';
|
||||||
|
import { useUiFlag } from '../../../hooks/useUiFlag';
|
||||||
|
|
||||||
const StyledHeader = styled(AppBar)(({ theme }) => ({
|
const StyledHeader = styled(AppBar)(({ theme }) => ({
|
||||||
backgroundColor: theme.palette.background.paper,
|
backgroundColor: theme.palette.background.paper,
|
||||||
@ -43,7 +45,7 @@ const StyledHeader = styled(AppBar)(({ theme }) => ({
|
|||||||
zIndex: 300,
|
zIndex: 300,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledContainer = styled(Container)(({ theme }) => ({
|
const StyledContainer = styled(Container)(() => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
maxWidth: 1280,
|
maxWidth: 1280,
|
||||||
@ -111,6 +113,7 @@ const Header: VFC = () => {
|
|||||||
const [adminRef, setAdminRef] = useState<HTMLButtonElement | null>(null);
|
const [adminRef, setAdminRef] = useState<HTMLButtonElement | null>(null);
|
||||||
const [configRef, setConfigRef] = useState<HTMLButtonElement | null>(null);
|
const [configRef, setConfigRef] = useState<HTMLButtonElement | null>(null);
|
||||||
|
|
||||||
|
const disableNotifications = useUiFlag('disableNotifications');
|
||||||
const { uiConfig, isOss } = useUiConfig();
|
const { uiConfig, isOss } = useUiConfig();
|
||||||
const smallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
const smallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
const [openDrawer, setOpenDrawer] = useState(false);
|
const [openDrawer, setOpenDrawer] = useState(false);
|
||||||
@ -198,6 +201,7 @@ const Header: VFC = () => {
|
|||||||
/>
|
/>
|
||||||
</StyledLinks>
|
</StyledLinks>
|
||||||
<StyledUserContainer>
|
<StyledUserContainer>
|
||||||
|
<InviteLinkButton />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
themeMode === 'dark'
|
themeMode === 'dark'
|
||||||
@ -215,10 +219,7 @@ const Header: VFC = () => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>{' '}
|
</Tooltip>{' '}
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={
|
condition={!isOss() && !disableNotifications}
|
||||||
!isOss() &&
|
|
||||||
!uiConfig?.flags.disableNotifications
|
|
||||||
}
|
|
||||||
show={<Notifications />}
|
show={<Notifications />}
|
||||||
/>
|
/>
|
||||||
<Tooltip title='Documentation' arrow>
|
<Tooltip title='Documentation' arrow>
|
||||||
@ -250,7 +251,10 @@ const Header: VFC = () => {
|
|||||||
options={filteredMainRoutes.adminRoutes}
|
options={filteredMainRoutes.adminRoutes}
|
||||||
anchorEl={adminRef}
|
anchorEl={adminRef}
|
||||||
handleClose={onAdminClose}
|
handleClose={onAdminClose}
|
||||||
style={{ top: 5, left: -100 }}
|
style={{
|
||||||
|
top: 5,
|
||||||
|
left: -100,
|
||||||
|
}}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
<UserProfile />
|
<UserProfile />
|
||||||
</StyledUserContainer>
|
</StyledUserContainer>
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
import { render } from 'utils/testRenderer';
|
||||||
|
import { screen } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
import InviteLinkButton from './InviteLinkButton';
|
||||||
|
import { AccessProviderMock } from 'component/providers/AccessProvider/AccessProviderMock';
|
||||||
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
||||||
|
|
||||||
|
const server = testServerSetup();
|
||||||
|
|
||||||
|
const setupApi = () => {
|
||||||
|
testServerRoute(server, '/api/admin/ui-config', {
|
||||||
|
flags: {
|
||||||
|
newInviteLink: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
test('Do not show button to non admins', async () => {
|
||||||
|
setupApi();
|
||||||
|
render(
|
||||||
|
<AccessProviderMock permissions={[]}>
|
||||||
|
<InviteLinkButton />
|
||||||
|
</AccessProviderMock>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.queryByLabelText('Invite users')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Show button to non admins', async () => {
|
||||||
|
setupApi();
|
||||||
|
render(<InviteLinkButton />, { permissions: [{ permission: ADMIN }] });
|
||||||
|
|
||||||
|
await screen.findByLabelText('Invite users');
|
||||||
|
});
|
@ -0,0 +1,52 @@
|
|||||||
|
import React, { useContext, useState } from 'react';
|
||||||
|
import { ClickAwayListener, IconButton, styled, Tooltip } from '@mui/material';
|
||||||
|
import { useId } from 'hooks/useId';
|
||||||
|
import { focusable } from 'themes/themeStyles';
|
||||||
|
import AccessContext from 'contexts/AccessContext';
|
||||||
|
import { PersonAdd } from '@mui/icons-material';
|
||||||
|
import { InviteLinkContent } from '../InviteLinkContent';
|
||||||
|
import { useUiFlag } from '../../../../../hooks/useUiFlag';
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')(() => ({
|
||||||
|
position: 'relative',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledIconButton = styled(IconButton)(({ theme }) => ({
|
||||||
|
...focusable(theme),
|
||||||
|
borderRadius: 100,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const InviteLinkButton = () => {
|
||||||
|
const [showInviteLinkContent, setShowInviteLinkContent] = useState(false);
|
||||||
|
const newInviteLink = useUiFlag('newInviteLink');
|
||||||
|
const modalId = useId();
|
||||||
|
|
||||||
|
const { isAdmin } = useContext(AccessContext);
|
||||||
|
|
||||||
|
if (!isAdmin || !newInviteLink) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ClickAwayListener onClickAway={() => setShowInviteLinkContent(false)}>
|
||||||
|
<StyledContainer>
|
||||||
|
<Tooltip title='Invite users' arrow>
|
||||||
|
<StyledIconButton
|
||||||
|
onClick={() => setShowInviteLinkContent(true)}
|
||||||
|
size='large'
|
||||||
|
disableRipple
|
||||||
|
>
|
||||||
|
<PersonAdd />
|
||||||
|
</StyledIconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<InviteLinkContent
|
||||||
|
showInviteLinkContent={showInviteLinkContent}
|
||||||
|
setShowInviteLinkContent={setShowInviteLinkContent}
|
||||||
|
id={modalId}
|
||||||
|
/>
|
||||||
|
</StyledContainer>
|
||||||
|
</ClickAwayListener>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InviteLinkButton;
|
@ -0,0 +1,52 @@
|
|||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { Button, Paper, Typography, styled, Link } from '@mui/material';
|
||||||
|
import { basePath } from 'utils/formatPath';
|
||||||
|
import { IUser } from 'interfaces/user';
|
||||||
|
import OpenInNew from '@mui/icons-material/OpenInNew';
|
||||||
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
|
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
|
||||||
|
import { InviteLinkBar } from '../../../admin/users/InviteLinkBar/InviteLinkBar';
|
||||||
|
import { InviteLinkBarContent } from '../../../admin/users/InviteLinkBar/InviteLinkBarContent';
|
||||||
|
|
||||||
|
const StyledPaper = styled(Paper)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
padding: theme.spacing(3),
|
||||||
|
borderRadius: theme.shape.borderRadiusMedium,
|
||||||
|
boxShadow: theme.boxShadows.popup,
|
||||||
|
position: 'absolute',
|
||||||
|
zIndex: 5000,
|
||||||
|
right: -255,
|
||||||
|
minWidth: theme.spacing(80),
|
||||||
|
[theme.breakpoints.down('md')]: {
|
||||||
|
width: '100%',
|
||||||
|
padding: '1rem',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface IInviteLinkContentProps {
|
||||||
|
id: string;
|
||||||
|
showInviteLinkContent: boolean;
|
||||||
|
setShowInviteLinkContent: (showInviteLinkContent: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InviteLinkContent = ({
|
||||||
|
id,
|
||||||
|
showInviteLinkContent,
|
||||||
|
setShowInviteLinkContent,
|
||||||
|
}: IInviteLinkContentProps) => (
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={showInviteLinkContent}
|
||||||
|
show={
|
||||||
|
<StyledPaper className='dropdown-outline' id={id}>
|
||||||
|
<InviteLinkBarContent
|
||||||
|
onActionClick={() => {
|
||||||
|
setShowInviteLinkContent(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</StyledPaper>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
@ -212,7 +212,7 @@ const ProjectEnterpriseSettingsForm: React.FC<IProjectEnterpriseSettingsForm> =
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSetFeatureNamingExample = (example: string) => {
|
const onSetFeatureNamingExample = (example: string) => {
|
||||||
setFeatureNamingExample && setFeatureNamingExample(example);
|
setFeatureNamingExample?.(example);
|
||||||
updateNamingExampleError({
|
updateNamingExampleError({
|
||||||
pattern: featureNamingPattern || '',
|
pattern: featureNamingPattern || '',
|
||||||
example,
|
example,
|
||||||
|
@ -105,6 +105,7 @@ exports[`should create default config 1`] = `
|
|||||||
},
|
},
|
||||||
"migrationLock": true,
|
"migrationLock": true,
|
||||||
"multipleRoles": false,
|
"multipleRoles": false,
|
||||||
|
"newInviteLink": false,
|
||||||
"personalAccessTokensKillSwitch": false,
|
"personalAccessTokensKillSwitch": false,
|
||||||
"privateProjects": false,
|
"privateProjects": false,
|
||||||
"proPlanAutoCharge": false,
|
"proPlanAutoCharge": false,
|
||||||
@ -147,6 +148,7 @@ exports[`should create default config 1`] = `
|
|||||||
},
|
},
|
||||||
"migrationLock": true,
|
"migrationLock": true,
|
||||||
"multipleRoles": false,
|
"multipleRoles": false,
|
||||||
|
"newInviteLink": false,
|
||||||
"personalAccessTokensKillSwitch": false,
|
"personalAccessTokensKillSwitch": false,
|
||||||
"privateProjects": false,
|
"privateProjects": false,
|
||||||
"proPlanAutoCharge": false,
|
"proPlanAutoCharge": false,
|
||||||
|
@ -28,6 +28,7 @@ export type IFlagKey =
|
|||||||
| 'doraMetrics'
|
| 'doraMetrics'
|
||||||
| 'variantTypeNumber'
|
| 'variantTypeNumber'
|
||||||
| 'accessOverview'
|
| 'accessOverview'
|
||||||
|
| 'newInviteLink'
|
||||||
| 'privateProjects'
|
| 'privateProjects'
|
||||||
| 'dependentFeatures'
|
| 'dependentFeatures'
|
||||||
| 'datadogJsonTemplate'
|
| 'datadogJsonTemplate'
|
||||||
@ -138,6 +139,10 @@ const flags: IFlags = {
|
|||||||
process.env.UNLEASH_EXPERIMENTAL_PRIVATE_PROJECTS,
|
process.env.UNLEASH_EXPERIMENTAL_PRIVATE_PROJECTS,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
newInviteLink: parseEnvVarBoolean(
|
||||||
|
process.env.UNLEASH_EXPERIMENTAL_NEW_INVITE_LINK,
|
||||||
|
false,
|
||||||
|
),
|
||||||
accessOverview: parseEnvVarBoolean(
|
accessOverview: parseEnvVarBoolean(
|
||||||
process.env.UNLEASH_EXPERIMENTAL_ACCESS_OVERVIEW,
|
process.env.UNLEASH_EXPERIMENTAL_ACCESS_OVERVIEW,
|
||||||
false,
|
false,
|
||||||
|
@ -42,6 +42,7 @@ process.nextTick(async () => {
|
|||||||
doraMetrics: true,
|
doraMetrics: true,
|
||||||
variantTypeNumber: true,
|
variantTypeNumber: true,
|
||||||
privateProjects: true,
|
privateProjects: true,
|
||||||
|
newInviteLink: true,
|
||||||
accessOverview: true,
|
accessOverview: true,
|
||||||
datadogJsonTemplate: true,
|
datadogJsonTemplate: true,
|
||||||
dependentFeatures: true,
|
dependentFeatures: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user