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

feat: add disabled state handling on slow network (#6165)

This commit is contained in:
Mateusz Kwasniewski 2024-02-08 10:27:51 +01:00 committed by GitHub
parent bc7d4b8edb
commit 7e66a79f9f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 91 additions and 26 deletions

View File

@ -13,10 +13,12 @@ interface IChangeRequestDialogueProps {
environment?: string; environment?: string;
showBanner?: boolean; showBanner?: boolean;
messageComponent: JSX.Element; messageComponent: JSX.Element;
disabled?: boolean;
} }
export const ChangeRequestDialogue: FC<IChangeRequestDialogueProps> = ({ export const ChangeRequestDialogue: FC<IChangeRequestDialogueProps> = ({
isOpen, isOpen,
disabled = false,
onConfirm, onConfirm,
onClose, onClose,
showBanner, showBanner,
@ -40,6 +42,7 @@ export const ChangeRequestDialogue: FC<IChangeRequestDialogueProps> = ({
open={isOpen} open={isOpen}
primaryButtonText={primaryButtonText} primaryButtonText={primaryButtonText}
secondaryButtonText='Cancel' secondaryButtonText='Cancel'
disabledPrimaryButton={disabled}
onClick={onConfirm} onClick={onConfirm}
onClose={onClose} onClose={onClose}
title='Request changes' title='Request changes'

View File

@ -97,13 +97,14 @@ export const ChangeRequestOverview: FC = () => {
projectId, projectId,
id, id,
); );
const { changeState, addComment, loading } = useChangeRequestApi(); const { changeState, addComment } = useChangeRequestApi();
const { refetch: refetchChangeRequestOpen } = const { refetch: refetchChangeRequestOpen } =
usePendingChangeRequests(projectId); usePendingChangeRequests(projectId);
const { setToastData, setToastApiError } = useToast(); const { setToastData, setToastApiError } = useToast();
const { isChangeRequestConfiguredForReview } = const { isChangeRequestConfiguredForReview } =
useChangeRequestsEnabled(projectId); useChangeRequestsEnabled(projectId);
const scheduleChangeRequests = useUiFlag('scheduledConfigurationChanges'); const scheduleChangeRequests = useUiFlag('scheduledConfigurationChanges');
const [disabled, setDisabled] = useState(false);
if (!changeRequest) { if (!changeRequest) {
return null; return null;
@ -124,11 +125,12 @@ export const ChangeRequestOverview: FC = () => {
const onApplyChanges = async () => { const onApplyChanges = async () => {
try { try {
setDisabled(true);
await changeState(projectId, Number(id), getCurrentState(), { await changeState(projectId, Number(id), getCurrentState(), {
state: 'Applied', state: 'Applied',
}); });
setShowApplyScheduledDialog(false); setShowApplyScheduledDialog(false);
refetchChangeRequest(); await refetchChangeRequest();
refetchChangeRequestOpen(); refetchChangeRequestOpen();
setToastData({ setToastData({
type: 'success', type: 'success',
@ -137,11 +139,14 @@ export const ChangeRequestOverview: FC = () => {
}); });
} catch (error: unknown) { } catch (error: unknown) {
setToastApiError(formatUnknownError(error)); setToastApiError(formatUnknownError(error));
} finally {
setDisabled(false);
} }
}; };
const onScheduleChangeRequest = async (scheduledDate: Date) => { const onScheduleChangeRequest = async (scheduledDate: Date) => {
try { try {
setDisabled(true);
await changeState(projectId, Number(id), getCurrentState(), { await changeState(projectId, Number(id), getCurrentState(), {
state: 'Scheduled', state: 'Scheduled',
scheduledAt: scheduledDate.toISOString(), scheduledAt: scheduledDate.toISOString(),
@ -156,14 +161,17 @@ export const ChangeRequestOverview: FC = () => {
}); });
} catch (error: unknown) { } catch (error: unknown) {
setToastApiError(formatUnknownError(error)); setToastApiError(formatUnknownError(error));
} finally {
setDisabled(false);
} }
}; };
const onAddComment = async () => { const onAddComment = async () => {
try { try {
setDisabled(true);
await addComment(projectId, id, commentText); await addComment(projectId, id, commentText);
setCommentText(''); setCommentText('');
refetchChangeRequest(); await refetchChangeRequest();
setToastData({ setToastData({
type: 'success', type: 'success',
title: 'Success', title: 'Success',
@ -171,16 +179,19 @@ export const ChangeRequestOverview: FC = () => {
}); });
} catch (error: unknown) { } catch (error: unknown) {
setToastApiError(formatUnknownError(error)); setToastApiError(formatUnknownError(error));
} finally {
setDisabled(false);
} }
}; };
const onCancelChanges = async () => { const onCancelChanges = async () => {
try { try {
setDisabled(true);
await changeState(projectId, Number(id), getCurrentState(), { await changeState(projectId, Number(id), getCurrentState(), {
state: 'Cancelled', state: 'Cancelled',
}); });
setShowCancelDialog(false); setShowCancelDialog(false);
refetchChangeRequest(); await refetchChangeRequest();
refetchChangeRequestOpen(); refetchChangeRequestOpen();
setToastData({ setToastData({
type: 'success', type: 'success',
@ -189,34 +200,41 @@ export const ChangeRequestOverview: FC = () => {
}); });
} catch (error: unknown) { } catch (error: unknown) {
setToastApiError(formatUnknownError(error)); setToastApiError(formatUnknownError(error));
} finally {
setDisabled(false);
} }
}; };
const onReject = async (comment?: string) => { const onReject = async (comment?: string) => {
try { try {
setDisabled(true);
await changeState(projectId, Number(id), getCurrentState(), { await changeState(projectId, Number(id), getCurrentState(), {
state: 'Rejected', state: 'Rejected',
comment, comment,
}); });
setShowRejectDialog(false); setShowRejectDialog(false);
await refetchChangeRequest();
setToastData({ setToastData({
type: 'success', type: 'success',
title: 'Success', title: 'Success',
text: 'Changes rejected', text: 'Changes rejected',
}); });
refetchChangeRequest();
refetchChangeRequestOpen(); refetchChangeRequestOpen();
} catch (error: unknown) { } catch (error: unknown) {
setToastApiError(formatUnknownError(error)); setToastApiError(formatUnknownError(error));
} finally {
setDisabled(false);
} }
}; };
const onApprove = async () => { const onApprove = async () => {
try { try {
setDisabled(true);
await changeState(projectId, Number(id), getCurrentState(), { await changeState(projectId, Number(id), getCurrentState(), {
state: 'Approved', state: 'Approved',
}); });
refetchChangeRequest(); await refetchChangeRequest();
refetchChangeRequestOpen(); refetchChangeRequestOpen();
setToastData({ setToastData({
type: 'success', type: 'success',
@ -225,6 +243,8 @@ export const ChangeRequestOverview: FC = () => {
}); });
} catch (error: unknown) { } catch (error: unknown) {
setToastApiError(formatUnknownError(error)); setToastApiError(formatUnknownError(error));
} finally {
setDisabled(false);
} }
}; };
@ -313,7 +333,8 @@ export const ChangeRequestOverview: FC = () => {
disabled={ disabled={
!allowChangeRequestActions || !allowChangeRequestActions ||
commentText.trim().length === 0 || commentText.trim().length === 0 ||
commentText.trim().length > 1000 commentText.trim().length > 1000 ||
disabled
} }
> >
Comment Comment
@ -350,7 +371,10 @@ export const ChangeRequestOverview: FC = () => {
setShowRejectDialog(true) setShowRejectDialog(true)
} }
onApprove={onApprove} onApprove={onApprove}
disabled={!allowChangeRequestActions} disabled={
!allowChangeRequestActions ||
disabled
}
> >
Review changes ({countOfChanges}) Review changes ({countOfChanges})
</ReviewButton> </ReviewButton>
@ -366,7 +390,7 @@ export const ChangeRequestOverview: FC = () => {
onApply={onApplyChanges} onApply={onApplyChanges}
disabled={ disabled={
!allowChangeRequestActions || !allowChangeRequestActions ||
loading disabled
} }
onSchedule={() => onSchedule={() =>
setShowScheduleChangeDialog( setShowScheduleChangeDialog(
@ -390,7 +414,7 @@ export const ChangeRequestOverview: FC = () => {
} }
disabled={ disabled={
!allowChangeRequestActions || !allowChangeRequestActions ||
loading disabled
} }
> >
Apply changes Apply changes
@ -411,7 +435,7 @@ export const ChangeRequestOverview: FC = () => {
} }
disabled={ disabled={
!allowChangeRequestActions || !allowChangeRequestActions ||
loading disabled
} }
onSchedule={() => onSchedule={() =>
setShowScheduleChangeDialog(true) setShowScheduleChangeDialog(true)
@ -445,6 +469,7 @@ export const ChangeRequestOverview: FC = () => {
true, true,
) )
} }
disabled={disabled}
> >
Reject changes Reject changes
</StyledButton> </StyledButton>
@ -453,6 +478,7 @@ export const ChangeRequestOverview: FC = () => {
<StyledButton <StyledButton
variant='outlined' variant='outlined'
onClick={onCancel} onClick={onCancel}
disabled={disabled}
> >
Cancel changes Cancel changes
</StyledButton> </StyledButton>
@ -485,6 +511,7 @@ export const ChangeRequestOverview: FC = () => {
open={showRejectDialog} open={showRejectDialog}
onConfirm={onReject} onConfirm={onReject}
onClose={onCancelReject} onClose={onCancelReject}
disabled={disabled}
/> />
<ConditionallyRender <ConditionallyRender
condition={scheduleChangeRequests} condition={scheduleChangeRequests}
@ -494,7 +521,9 @@ export const ChangeRequestOverview: FC = () => {
open={showScheduleChangesDialog} open={showScheduleChangesDialog}
onConfirm={onScheduleChangeRequest} onConfirm={onScheduleChangeRequest}
onClose={onScheduleChangeAbort} onClose={onScheduleChangeAbort}
disabled={!allowChangeRequestActions || loading} disabled={
!allowChangeRequestActions || disabled
}
projectId={projectId} projectId={projectId}
environment={changeRequest.environment} environment={changeRequest.environment}
primaryButtonText={ primaryButtonText={
@ -514,7 +543,9 @@ export const ChangeRequestOverview: FC = () => {
onConfirm={onApplyChanges} onConfirm={onApplyChanges}
onClose={onApplyScheduledAbort} onClose={onApplyScheduledAbort}
scheduledTime={scheduledAt} scheduledTime={scheduledAt}
disabled={!allowChangeRequestActions || loading} disabled={
!allowChangeRequestActions || disabled
}
projectId={projectId} projectId={projectId}
environment={changeRequest.environment} environment={changeRequest.environment}
/> />
@ -523,6 +554,7 @@ export const ChangeRequestOverview: FC = () => {
onConfirm={onReject} onConfirm={onReject}
onClose={onRejectScheduledAbort} onClose={onRejectScheduledAbort}
scheduledTime={scheduledAt} scheduledTime={scheduledAt}
disabled={disabled}
/> />
</> </>
} }

View File

@ -6,12 +6,14 @@ interface IChangeRequestDialogueProps {
open: boolean; open: boolean;
onConfirm: (comment?: string) => void; onConfirm: (comment?: string) => void;
onClose: () => void; onClose: () => void;
disabled?: boolean;
} }
export const ChangeRequestRejectDialogue: FC<IChangeRequestDialogueProps> = ({ export const ChangeRequestRejectDialogue: FC<IChangeRequestDialogueProps> = ({
open, open,
onConfirm, onConfirm,
onClose, onClose,
disabled = false,
}) => { }) => {
const [commentText, setCommentText] = useState(''); const [commentText, setCommentText] = useState('');
@ -21,6 +23,7 @@ export const ChangeRequestRejectDialogue: FC<IChangeRequestDialogueProps> = ({
primaryButtonText='Reject changes' primaryButtonText='Reject changes'
secondaryButtonText='Cancel' secondaryButtonText='Cancel'
onClick={() => onConfirm(commentText)} onClick={() => onConfirm(commentText)}
disabledPrimaryButton={disabled}
onClose={onClose} onClose={onClose}
title='Reject changes' title='Reject changes'
fullWidth fullWidth

View File

@ -29,6 +29,7 @@ export const ChangeRequestScheduledDialog: FC<
onClose, onClose,
title, title,
primaryButtonText, primaryButtonText,
disabled,
message, message,
scheduledTime, scheduledTime,
permissionButton, permissionButton,
@ -39,6 +40,7 @@ export const ChangeRequestScheduledDialog: FC<
<Dialogue <Dialogue
title={title} title={title}
primaryButtonText={primaryButtonText} primaryButtonText={primaryButtonText}
disabledPrimaryButton={disabled}
secondaryButtonText='Cancel' secondaryButtonText='Cancel'
open={open} open={open}
onClose={onClose} onClose={onClose}

View File

@ -48,6 +48,7 @@ export const ScheduleChangeRequestDialog: FC<ScheduleChangeRequestDialogProps> =
<Dialogue <Dialogue
title={title} title={title}
primaryButtonText={primaryButtonText} primaryButtonText={primaryButtonText}
disabledPrimaryButton={disabled}
secondaryButtonText='Cancel' secondaryButtonText='Cancel'
open={open} open={open}
onClose={() => onClose()} onClose={() => onClose()}

View File

@ -102,6 +102,7 @@ export const MultiActionButton: FC<{
{actions.map( {actions.map(
({ label, onSelect, icon }) => ( ({ label, onSelect, icon }) => (
<MenuItem <MenuItem
disabled={disabled}
onClick={onSelect} onClick={onSelect}
key={`MenuItem-${label}`} key={`MenuItem-${label}`}
> >

View File

@ -25,11 +25,17 @@ import { ChangeRequestTitle } from './ChangeRequestTitle';
import { UpdateCount } from 'component/changeRequest/UpdateCount'; import { UpdateCount } from 'component/changeRequest/UpdateCount';
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
const SubmitChangeRequestButton: FC<{ onClick: () => void; count: number }> = ({ const SubmitChangeRequestButton: FC<{
onClick, onClick: () => void;
count, count: number;
}) => ( disabled?: boolean;
<Button sx={{ ml: 'auto' }} variant='contained' onClick={onClick}> }> = ({ onClick, count, disabled = false }) => (
<Button
sx={{ ml: 'auto' }}
variant='contained'
onClick={onClick}
disabled={disabled}
>
Submit change request ({count}) Submit change request ({count})
</Button> </Button>
); );
@ -66,11 +72,18 @@ export const EnvironmentChangeRequest: FC<{
const { user } = useAuthUser(); const { user } = useAuthUser();
const [title, setTitle] = useState(environmentChangeRequest.title); const [title, setTitle] = useState(environmentChangeRequest.title);
const { changeState } = useChangeRequestApi(); const { changeState } = useChangeRequestApi();
const sendToReview = async (project: string) => const [disabled, setDisabled] = useState(false);
changeState(project, environmentChangeRequest.id, 'Draft', { const sendToReview = async (project: string) => {
state: 'In review', setDisabled(true);
comment: commentText, try {
}); await changeState(project, environmentChangeRequest.id, 'Draft', {
state: 'In review',
comment: commentText,
});
} catch (e) {
setDisabled(false);
}
};
return ( return (
<Box key={environmentChangeRequest.id}> <Box key={environmentChangeRequest.id}>
@ -152,14 +165,17 @@ export const EnvironmentChangeRequest: FC<{
count={changesCount( count={changesCount(
environmentChangeRequest, environmentChangeRequest,
)} )}
disabled={disabled}
/> />
<Button <Button
sx={{ ml: 2 }} sx={{ ml: 2 }}
variant='outlined' variant='outlined'
onClick={() => disabled={disabled}
onDiscard(environmentChangeRequest.id) onClick={() => {
} setDisabled(true);
onDiscard(environmentChangeRequest.id);
}}
> >
Discard changes Discard changes
</Button> </Button>

View File

@ -53,6 +53,7 @@ export const useFeatureToggleSwitch: UseFeatureToggleSwitchType = (
onAddDefaultStrategy: () => {}, onAddDefaultStrategy: () => {},
}); });
const { const {
pending,
onChangeRequestToggle, onChangeRequestToggle,
onChangeRequestToggleClose, onChangeRequestToggleClose,
onChangeRequestToggleConfirm, onChangeRequestToggleConfirm,
@ -233,6 +234,7 @@ export const useFeatureToggleSwitch: UseFeatureToggleSwitchType = (
onChangeRequestToggleClose(); onChangeRequestToggleClose();
}} }}
environment={changeRequestDialogDetails?.environment} environment={changeRequestDialogDetails?.environment}
disabled={pending}
onConfirm={() => { onConfirm={() => {
changeRequestDialogCallback?.(); changeRequestDialogCallback?.();
onChangeRequestToggleConfirm(); onChangeRequestToggleConfirm();

View File

@ -9,6 +9,7 @@ export const useChangeRequestToggle = (project: string) => {
const { addChange } = useChangeRequestApi(); const { addChange } = useChangeRequestApi();
const { refetch: refetchChangeRequests } = const { refetch: refetchChangeRequests } =
usePendingChangeRequests(project); usePendingChangeRequests(project);
const [pending, setPending] = useState(false);
const [changeRequestDialogDetails, setChangeRequestDialogDetails] = const [changeRequestDialogDetails, setChangeRequestDialogDetails] =
useState<{ useState<{
@ -43,6 +44,7 @@ export const useChangeRequestToggle = (project: string) => {
const onChangeRequestToggleConfirm = useCallback(async () => { const onChangeRequestToggleConfirm = useCallback(async () => {
try { try {
setPending(true);
await addChange(project, changeRequestDialogDetails.environment!, { await addChange(project, changeRequestDialogDetails.environment!, {
feature: changeRequestDialogDetails.featureName!, feature: changeRequestDialogDetails.featureName!,
action: 'updateEnabled', action: 'updateEnabled',
@ -68,10 +70,13 @@ export const useChangeRequestToggle = (project: string) => {
...prev, ...prev,
isOpen: false, isOpen: false,
})); }));
} finally {
setPending(false);
} }
}, [addChange]); }, [addChange]);
return { return {
pending,
onChangeRequestToggle, onChangeRequestToggle,
onChangeRequestToggleClose, onChangeRequestToggleClose,
onChangeRequestToggleConfirm, onChangeRequestToggleConfirm,