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

feat: Change Request on Reorder UI (#4249)

<!-- Thanks for creating a PR! To make it easier for reviewers and
everyone else to understand what your changes relate to, please add some
relevant content to the headings below. Feel free to ignore or delete
sections that you don't think are relevant. Thank you! ❤️ -->
Change request UI for reordering strategies with variants
## About the changes
<!-- Describe the changes introduced. What are they and why are they
being introduced? Feel free to also add screenshots or steps to view the
changes if they're visual. -->

<!-- Does it close an issue? Multiple? -->
Closes #

<!-- (For internal contributors): Does it relate to an issue on public
roadmap? -->
<!--
Relates to [roadmap](https://github.com/orgs/Unleash/projects/10) item:
#
-->

### Important files
<!-- PRs can contain a lot of changes, but not all changes are equally
important. Where should a reviewer start looking to get an overview of
the changes? Are any files particularly important? -->


## Discussion points
<!-- Anything about the PR you'd like to discuss before it gets merged?
Got any questions or doubts? -->

---------

Signed-off-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
andreas-unleash 2023-07-25 14:12:35 +03:00 committed by GitHub
parent 36bde1b24b
commit 988a3a57e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 211 additions and 8 deletions

View File

@ -10,6 +10,7 @@ import { Alert, Box, styled } from '@mui/material';
import { ToggleStatusChange } from './ToggleStatusChange';
import { StrategyChange } from './StrategyChange';
import { VariantPatch } from './VariantPatch/VariantPatch';
import { EnvironmentStrategyExecutionOrder } from './EnvironmentStrategyExecutionOrder/EnvironmentStrategyExecutionOrder';
const StyledSingleChangeBox = styled(Box, {
shouldForwardProp: (prop: string) => !prop.startsWith('$'),
@ -108,6 +109,15 @@ export const Change: FC<{
discard={discard}
/>
)}
{change.action === 'reorderStrategy' && (
<EnvironmentStrategyExecutionOrder
feature={feature.name}
project={changeRequest.project}
environment={changeRequest.environment}
change={change}
discard={discard}
/>
)}
</Box>
</StyledSingleChangeBox>
);

View File

@ -0,0 +1,114 @@
import { IChangeRequestReorderStrategy } from '../../../../changeRequest.types';
import { ReactNode } from 'react';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
import { Box, styled } from '@mui/material';
import { EnvironmentStrategyOrderDiff } from './EnvironmentStrategyOrderDiff';
import { StrategyExecution } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution';
import { formatStrategyName } from '../../../../../../utils/strategyNames';
const ChangeItemInfo = styled(Box)({
display: 'flex',
flexDirection: 'column',
});
const StyledChangeHeader = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
flexDirection: 'row',
alignItems: 'start',
marginBottom: theme.spacing(2),
lineHeight: theme.spacing(3),
}));
const StyledStrategyExecutionWrapper = styled(Box)(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: theme.spacing(2),
marginTop: theme.spacing(2),
lineHeight: theme.spacing(3),
gap: theme.spacing(1),
}));
const StyledStrategyContainer = styled('div')(({ theme }) => ({
flexDirection: 'row',
marginBottom: theme.spacing(2),
}));
interface IEnvironmentStrategyExecutionOrderProps {
feature: string;
project: string;
environment: string;
change: IChangeRequestReorderStrategy;
discard?: ReactNode;
}
export const EnvironmentStrategyExecutionOrder = ({
feature,
environment,
change,
project,
discard,
}: IEnvironmentStrategyExecutionOrderProps) => {
const { feature: featureData } = useFeature(project, feature);
const featureEnvironment = featureData.environments.find(
({ name }) => environment === name
);
const environmentStrategies = featureEnvironment?.strategies || [];
const preData = {
strategyIds:
environmentStrategies
.sort((strategy1, strategy2) => {
if (
typeof strategy1.sortOrder === 'number' &&
typeof strategy2.sortOrder === 'number'
) {
return strategy1.sortOrder - strategy2.sortOrder;
}
return 0;
})
.map(strategy => strategy.id) ?? [],
};
const updatedStrategies = change.payload.map(({ id }) => {
return environmentStrategies.find(s => s.id === id);
});
const data = {
strategyIds: updatedStrategies.map(strategy => strategy!.id),
};
return (
<ChangeItemInfo>
<StyledChangeHeader>
<TooltipLink
tooltip={
<EnvironmentStrategyOrderDiff
preData={preData}
data={data}
/>
}
tooltipProps={{
maxWidth: 500,
maxHeight: 600,
}}
>
Updating strategy execution order to:
</TooltipLink>
{discard}
</StyledChangeHeader>
<StyledStrategyExecutionWrapper>
{updatedStrategies.map((strategy, index) => (
<StyledStrategyContainer>
{`${index + 1}: `}
{formatStrategyName(strategy?.name || '')}
{strategy?.title && ` - ${strategy.title}`}
<StrategyExecution strategy={strategy!} />
</StyledStrategyContainer>
))}
</StyledStrategyExecutionWrapper>
</ChangeItemInfo>
);
};

View File

@ -0,0 +1,30 @@
import { styled } from '@mui/material';
import EventDiff from 'component/events/EventDiff/EventDiff';
const StyledCodeSection = styled('div')(({ theme }) => ({
overflowX: 'auto',
'& code': {
wordWrap: 'break-word',
whiteSpace: 'pre-wrap',
fontFamily: 'monospace',
lineHeight: 1.5,
fontSize: theme.fontSizes.smallBody,
},
}));
type StrategyIds = { strategyIds: string[] };
interface IDiffProps {
preData: StrategyIds;
data: StrategyIds;
}
export const EnvironmentStrategyOrderDiff = ({ preData, data }: IDiffProps) => (
<StyledCodeSection>
<EventDiff
entry={{
preData: preData.strategyIds,
data: data.strategyIds,
}}
sort={(a, b) => a.index - b.index}
/>
</StyledCodeSection>
);

View File

@ -24,7 +24,7 @@ const variantsArrayToObject = (variants: IFeatureVariant[]) =>
{}
);
export const Diff = ({ preData, data }: IDiffProps) => (
export const VariantDiff = ({ preData, data }: IDiffProps) => (
<StyledCodeSection>
<EventDiff
entry={{

View File

@ -6,7 +6,7 @@ import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
import { EnvironmentVariantsTable } from 'component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsCard/EnvironmentVariantsTable/EnvironmentVariantsTable';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import { ReactNode } from 'react';
import { Diff } from './Diff';
import { VariantDiff } from './VariantDiff';
const ChangeItemInfo = styled(Box)({
display: 'flex',
@ -56,7 +56,7 @@ export const VariantPatch = ({
<StyledChangeHeader>
<TooltipLink
tooltip={
<Diff
<VariantDiff
preData={preData}
data={change.payload.variants}
/>

View File

@ -1,6 +1,7 @@
import { IFeatureVariant } from 'interfaces/featureToggle';
import { IFeatureStrategy } from '../../interfaces/strategy';
import { IUser } from '../../interfaces/user';
import { SetStrategySortOrderSchema } from '../../openapi';
export interface IChangeRequest {
id: number;
@ -64,7 +65,8 @@ type ChangeRequestPayload =
| ChangeRequestAddStrategy
| ChangeRequestEditStrategy
| ChangeRequestDeleteStrategy
| ChangeRequestVariantPatch;
| ChangeRequestVariantPatch
| SetStrategySortOrderSchema;
export interface IChangeRequestAddStrategy extends IChangeRequestBase {
action: 'addStrategy';
@ -91,12 +93,18 @@ export interface IChangeRequestPatchVariant extends IChangeRequestBase {
payload: ChangeRequestVariantPatch;
}
export interface IChangeRequestReorderStrategy extends IChangeRequestBase {
action: 'reorderStrategy';
payload: SetStrategySortOrderSchema;
}
export type IChange =
| IChangeRequestAddStrategy
| IChangeRequestDeleteStrategy
| IChangeRequestUpdateStrategy
| IChangeRequestEnabled
| IChangeRequestPatchVariant;
| IChangeRequestPatchVariant
| IChangeRequestReorderStrategy;
type ChangeRequestVariantPatch = {
variants: IFeatureVariant[];
@ -123,4 +131,5 @@ export type ChangeRequestAction =
| 'addStrategy'
| 'updateStrategy'
| 'deleteStrategy'
| 'patchVariant';
| 'patchVariant'
| 'reorderStrategy';

View File

@ -9,6 +9,9 @@ import { IFeatureEnvironment } from 'interfaces/featureToggle';
import { FeatureStrategyEmpty } from 'component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
interface IEnvironmentAccordionBodyProps {
isDisabled: boolean;
@ -36,6 +39,10 @@ const EnvironmentAccordionBody = ({
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const { setStrategiesSortOrder } = useFeatureStrategyApi();
const { addChange } = useChangeRequestApi();
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
const { refetch: refetchChangeRequests } =
usePendingChangeRequests(projectId);
const { setToastData, setToastApiError } = useToast();
const { refetchFeature } = useFeature(projectId, featureId);
const [strategies, setStrategies] = useState(
@ -73,6 +80,37 @@ const EnvironmentAccordionBody = ({
}
};
const onChangeRequestReorder = async (
payload: { id: string; sortOrder: number }[]
) => {
await addChange(projectId, featureEnvironment.name, {
action: 'reorderStrategy',
feature: featureId,
payload,
});
setToastData({
title: 'Strategy execution order added to draft',
type: 'success',
confetti: true,
});
refetchChangeRequests();
};
const onStrategyReorder = async (
payload: { id: string; sortOrder: number }[]
) => {
try {
if (isChangeRequestConfigured(featureEnvironment.name)) {
await onChangeRequestReorder(payload);
} else {
await onReorder(payload);
}
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
};
const onDragStartRef =
(
ref: RefObject<HTMLDivElement>,
@ -129,7 +167,7 @@ const EnvironmentAccordionBody = ({
const onDragEnd = () => {
setDragItem(null);
onReorder(
onStrategyReorder(
strategies.map((strategy, sortOrder) => ({
id: strategy.id,
sortOrder,

View File

@ -8,7 +8,8 @@ export interface IChangeSchema {
| 'addStrategy'
| 'updateStrategy'
| 'deleteStrategy'
| 'patchVariant';
| 'patchVariant'
| 'reorderStrategy';
payload: string | boolean | object | number;
}

View File

@ -14,6 +14,7 @@ export interface IFeatureStrategy {
environment?: string;
segments?: number[];
disabled?: boolean;
sortOrder?: number;
}
export interface IFeatureStrategyParameters {