1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-27 13:49:10 +02:00

Merge branch 'main' into fix/login-e2e-ci-test

This commit is contained in:
Tymoteusz Czech 2025-04-18 14:20:42 +02:00
commit 3ad83ad8e6
No known key found for this signature in database
GPG Key ID: 133555230D88D75F
8 changed files with 118 additions and 56 deletions

View File

@ -6,10 +6,12 @@ import Loader from './common/Loader/Loader';
import { useLocalStorageState } from 'hooks/useLocalStorageState';
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
const defaultPage = '/personal';
export const useLastViewedPage = (location?: Location) => {
const [state, setState] = useLocalStorageState<string>(
'lastViewedPage',
'/personal',
defaultPage,
7 * 24 * 60 * 60 * 1000, // 7 days, left to promote seeing Personal dashboard from time to time
);
@ -55,5 +57,9 @@ export const InitialRedirect = () => {
return <Navigate to={`/projects/${lastViewedProject}`} replace />;
}
return <Navigate to={lastViewedPage} replace />;
if (lastViewedPage && lastViewedPage !== '/') {
return <Navigate to={lastViewedPage} replace />;
}
return <Navigate to={defaultPage} replace />;
};

View File

@ -63,12 +63,18 @@ const StyledTooltipTitle = styled('div')(({ theme }) => ({
const StyledTooltipActions = styled('div')(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: theme.spacing(3),
'&&& button': {
fontSize: theme.fontSizes.smallBody,
},
}));
const StyledBackButton = styled(Button)({
padding: 0,
minWidth: 0,
});
// @ts-ignore
export interface IDemoStepTooltipProps extends TooltipRenderProps {
step: ITutorialTopicStep;
@ -92,7 +98,7 @@ export const DemoStepTooltip = ({
}: IDemoStepTooltipProps) => {
const nextLabel =
stepIndex === 0
? 'Start'
? 'Start tutorial'
: stepIndex === topics[topic].steps.length - 1
? 'Finish'
: 'Next';
@ -112,28 +118,23 @@ export const DemoStepTooltip = ({
<CloseIcon />
</StyledCloseButton>
<StyledTooltipTitle>
<ConditionallyRender
condition={Boolean(step.title)}
show={step.title}
elseShow={
<Typography fontWeight='bold'>
{topics[topic].title}
</Typography>
}
/>
<Typography fontWeight='bold'>
{step.title || topics[topic].title}
</Typography>
</StyledTooltipTitle>
{step.content}
<StyledTooltipActions>
<div>
<ConditionallyRender
condition={topic > 0 || stepIndex > 0}
condition={
!step.hideBackButton && stepIndex > 0
}
show={
<Button
variant='text'
<StyledBackButton
onClick={() => onBack(step)}
>
Back
</Button>
</StyledBackButton>
}
/>
</div>
@ -164,25 +165,19 @@ export const DemoStepTooltip = ({
<CloseIcon />
</StyledCloseButton>
<StyledTooltipTitle>
<ConditionallyRender
condition={Boolean(step.title)}
show={step.title}
elseShow={
<Typography fontWeight='bold'>
{topics[topic].title}
</Typography>
}
/>
<Typography fontWeight='bold'>
{step.title || topics[topic].title}
</Typography>
</StyledTooltipTitle>
{step.content}
<StyledTooltipActions>
<div>
<ConditionallyRender
condition={topic > 0 || stepIndex > 0}
condition={!step.hideBackButton && stepIndex > 0}
show={
<Button variant='text' onClick={() => onBack(step)}>
<StyledBackButton onClick={() => onBack(step)}>
Back
</Button>
</StyledBackButton>
}
/>
</div>

View File

@ -118,6 +118,8 @@ export const DemoSteps = ({
}
if (action === ACTIONS.UPDATE) {
if (step.target === 'body') return;
const el = document.querySelector(
step.target as string,
) as HTMLElement | null;

View File

@ -37,10 +37,10 @@ const ENVIRONMENT = 'dev';
export const TOPICS: ITutorialTopic[] = [
{
title: 'Enable/disable a feature flag',
title: 'How to enable/disable a feature flag',
steps: [
{
title: 'Enable/disable a feature flag',
title: 'How to enable/disable a feature flag',
href: `/projects/${PROJECT}?sort=name`,
target: 'body',
placement: 'center',
@ -91,11 +91,11 @@ export const TOPICS: ITutorialTopic[] = [
],
},
{
title: 'Enable for a specific user',
title: 'Next: How to enable for a specific user',
setup: specificUser,
steps: [
{
title: 'Enable for a specific user',
title: 'Next: How to enable for a specific user',
href: `/projects/${PROJECT}?sort=name`,
target: 'body',
placement: 'center',
@ -335,11 +335,11 @@ export const TOPICS: ITutorialTopic[] = [
],
},
{
title: 'Adjust gradual rollout',
title: 'Next: How to adjust gradual rollout',
setup: gradualRollout,
steps: [
{
title: 'Adjust gradual rollout',
title: 'Next: How to adjust gradual rollout',
href: `/projects/${PROJECT}?sort=name`,
target: 'body',
placement: 'center',
@ -468,11 +468,11 @@ export const TOPICS: ITutorialTopic[] = [
],
},
{
title: 'Adjust variants',
title: 'Next: How to adjust variants',
setup: variants,
steps: [
{
title: 'Adjust variants',
title: 'Next: How to adjust variants',
href: `/projects/${PROJECT}?sort=name`,
target: 'body',
placement: 'center',

View File

@ -59,6 +59,7 @@ test('render remove flag from code reminder', async () => {
});
await screen.findByText('Time to remove flag from code?');
await screen.findByText('Revert to production');
const reminder = await screen.findByText('Remind me later');
reminder.click();

View File

@ -18,6 +18,8 @@ import { FeatureArchiveNotAllowedDialog } from 'component/common/FeatureArchiveD
import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog';
import { useNavigate } from 'react-router-dom';
import { useFlagReminders } from './useFlagReminders';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { useUncomplete } from '../FeatureOverview/FeatureLifecycle/useUncomplete';
const StyledBox = styled(Box)(({ theme }) => ({
marginRight: theme.spacing(2),
@ -37,10 +39,16 @@ export const CleanupReminder: FC<{
onChange: () => void;
}> = ({ feature, onChange }) => {
const navigate = useNavigate();
const { trackEvent } = usePlausibleTracker();
const [markCompleteDialogueOpen, setMarkCompleteDialogueOpen] =
useState(false);
const [archiveDialogueOpen, setArchiveDialogueOpen] = useState(false);
const { onUncompleteHandler, loading } = useUncomplete({
feature: feature.name,
project: feature.project,
onChange,
});
const currentStage = populateCurrentStage(feature);
const isRelevantType =
@ -123,7 +131,14 @@ export const CleanupReminder: FC<{
<ActionsBox>
<Button
size='medium'
onClick={() => snoozeReminder(feature.name)}
onClick={() => {
snoozeReminder(feature.name);
trackEvent('feature-lifecycle', {
props: {
eventType: 'snoozeReminder',
},
});
}}
>
Remind me later
</Button>
@ -171,12 +186,31 @@ export const CleanupReminder: FC<{
severity='warning'
icon={<CleaningServicesIcon />}
action={
<Button
size='medium'
onClick={() => snoozeReminder(feature.name)}
>
Remind me later
</Button>
<ActionsBox>
<Button
size='medium'
onClick={() => {
snoozeReminder(feature.name);
trackEvent('feature-lifecycle', {
props: {
eventType: 'snoozeReminder',
},
});
}}
>
Remind me later
</Button>
<PermissionButton
variant='outlined'
permission={UPDATE_FEATURE}
size='medium'
onClick={onUncompleteHandler}
disabled={loading}
projectId={feature.project}
>
Revert to production
</PermissionButton>
</ActionsBox>
}
>
<b>Time to remove flag from code?</b>

View File

@ -1,12 +1,11 @@
import { FeatureLifecycleStageIcon } from 'component/common/FeatureLifecycle/FeatureLifecycleStageIcon';
import { FeatureLifecycleTooltip } from './FeatureLifecycleTooltip';
import useFeatureLifecycleApi from 'hooks/api/actions/useFeatureLifecycleApi/useFeatureLifecycleApi';
import { populateCurrentStage } from './populateCurrentStage';
import type { FC } from 'react';
import type { Lifecycle } from 'interfaces/featureToggle';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { getFeatureLifecycleName } from 'component/common/FeatureLifecycle/getFeatureLifecycleName';
import { Box } from '@mui/material';
import { useUncomplete } from './useUncomplete';
export interface LifecycleFeature {
lifecycle?: Lifecycle;
@ -28,18 +27,12 @@ export const FeatureLifecycle: FC<{
expanded?: boolean;
}> = ({ feature, expanded, onComplete, onUncomplete, onArchive }) => {
const currentStage = populateCurrentStage(feature);
const { markFeatureUncompleted, loading } = useFeatureLifecycleApi();
const { trackEvent } = usePlausibleTracker();
const onUncompleteHandler = async () => {
await markFeatureUncompleted(feature.name, feature.project);
onUncomplete?.();
trackEvent('feature-lifecycle', {
props: {
eventType: 'uncomplete',
},
});
};
const { onUncompleteHandler, loading } = useUncomplete({
feature: feature.name,
project: feature.project,
onChange: onUncomplete,
});
return currentStage ? (
<Box sx={(theme) => ({ display: 'flex', gap: theme.spacing(0.5) })}>

View File

@ -0,0 +1,31 @@
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import useToast from 'hooks/useToast';
import useFeatureLifecycleApi from 'hooks/api/actions/useFeatureLifecycleApi/useFeatureLifecycleApi';
import { formatUnknownError } from 'utils/formatUnknownError';
export const useUncomplete = ({
feature,
project,
onChange,
}: { feature: string; project: string; onChange?: () => void }) => {
const { trackEvent } = usePlausibleTracker();
const { setToastApiError } = useToast();
const { markFeatureUncompleted, loading } = useFeatureLifecycleApi();
const onUncompleteHandler = async () => {
try {
await markFeatureUncompleted(feature, project);
onChange?.();
trackEvent('feature-lifecycle', {
props: {
eventType: 'uncomplete',
},
});
} catch (e) {
setToastApiError(formatUnknownError(e));
}
};
return { onUncompleteHandler, loading };
};