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:
parent
36bde1b24b
commit
988a3a57e8
@ -10,6 +10,7 @@ import { Alert, Box, styled } from '@mui/material';
|
|||||||
import { ToggleStatusChange } from './ToggleStatusChange';
|
import { ToggleStatusChange } from './ToggleStatusChange';
|
||||||
import { StrategyChange } from './StrategyChange';
|
import { StrategyChange } from './StrategyChange';
|
||||||
import { VariantPatch } from './VariantPatch/VariantPatch';
|
import { VariantPatch } from './VariantPatch/VariantPatch';
|
||||||
|
import { EnvironmentStrategyExecutionOrder } from './EnvironmentStrategyExecutionOrder/EnvironmentStrategyExecutionOrder';
|
||||||
|
|
||||||
const StyledSingleChangeBox = styled(Box, {
|
const StyledSingleChangeBox = styled(Box, {
|
||||||
shouldForwardProp: (prop: string) => !prop.startsWith('$'),
|
shouldForwardProp: (prop: string) => !prop.startsWith('$'),
|
||||||
@ -108,6 +109,15 @@ export const Change: FC<{
|
|||||||
discard={discard}
|
discard={discard}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{change.action === 'reorderStrategy' && (
|
||||||
|
<EnvironmentStrategyExecutionOrder
|
||||||
|
feature={feature.name}
|
||||||
|
project={changeRequest.project}
|
||||||
|
environment={changeRequest.environment}
|
||||||
|
change={change}
|
||||||
|
discard={discard}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</StyledSingleChangeBox>
|
</StyledSingleChangeBox>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
@ -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>
|
||||||
|
);
|
@ -24,7 +24,7 @@ const variantsArrayToObject = (variants: IFeatureVariant[]) =>
|
|||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const Diff = ({ preData, data }: IDiffProps) => (
|
export const VariantDiff = ({ preData, data }: IDiffProps) => (
|
||||||
<StyledCodeSection>
|
<StyledCodeSection>
|
||||||
<EventDiff
|
<EventDiff
|
||||||
entry={{
|
entry={{
|
@ -6,7 +6,7 @@ import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
|
|||||||
import { EnvironmentVariantsTable } from 'component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsCard/EnvironmentVariantsTable/EnvironmentVariantsTable';
|
import { EnvironmentVariantsTable } from 'component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsCard/EnvironmentVariantsTable/EnvironmentVariantsTable';
|
||||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Diff } from './Diff';
|
import { VariantDiff } from './VariantDiff';
|
||||||
|
|
||||||
const ChangeItemInfo = styled(Box)({
|
const ChangeItemInfo = styled(Box)({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -56,7 +56,7 @@ export const VariantPatch = ({
|
|||||||
<StyledChangeHeader>
|
<StyledChangeHeader>
|
||||||
<TooltipLink
|
<TooltipLink
|
||||||
tooltip={
|
tooltip={
|
||||||
<Diff
|
<VariantDiff
|
||||||
preData={preData}
|
preData={preData}
|
||||||
data={change.payload.variants}
|
data={change.payload.variants}
|
||||||
/>
|
/>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { IFeatureVariant } from 'interfaces/featureToggle';
|
import { IFeatureVariant } from 'interfaces/featureToggle';
|
||||||
import { IFeatureStrategy } from '../../interfaces/strategy';
|
import { IFeatureStrategy } from '../../interfaces/strategy';
|
||||||
import { IUser } from '../../interfaces/user';
|
import { IUser } from '../../interfaces/user';
|
||||||
|
import { SetStrategySortOrderSchema } from '../../openapi';
|
||||||
|
|
||||||
export interface IChangeRequest {
|
export interface IChangeRequest {
|
||||||
id: number;
|
id: number;
|
||||||
@ -64,7 +65,8 @@ type ChangeRequestPayload =
|
|||||||
| ChangeRequestAddStrategy
|
| ChangeRequestAddStrategy
|
||||||
| ChangeRequestEditStrategy
|
| ChangeRequestEditStrategy
|
||||||
| ChangeRequestDeleteStrategy
|
| ChangeRequestDeleteStrategy
|
||||||
| ChangeRequestVariantPatch;
|
| ChangeRequestVariantPatch
|
||||||
|
| SetStrategySortOrderSchema;
|
||||||
|
|
||||||
export interface IChangeRequestAddStrategy extends IChangeRequestBase {
|
export interface IChangeRequestAddStrategy extends IChangeRequestBase {
|
||||||
action: 'addStrategy';
|
action: 'addStrategy';
|
||||||
@ -91,12 +93,18 @@ export interface IChangeRequestPatchVariant extends IChangeRequestBase {
|
|||||||
payload: ChangeRequestVariantPatch;
|
payload: ChangeRequestVariantPatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IChangeRequestReorderStrategy extends IChangeRequestBase {
|
||||||
|
action: 'reorderStrategy';
|
||||||
|
payload: SetStrategySortOrderSchema;
|
||||||
|
}
|
||||||
|
|
||||||
export type IChange =
|
export type IChange =
|
||||||
| IChangeRequestAddStrategy
|
| IChangeRequestAddStrategy
|
||||||
| IChangeRequestDeleteStrategy
|
| IChangeRequestDeleteStrategy
|
||||||
| IChangeRequestUpdateStrategy
|
| IChangeRequestUpdateStrategy
|
||||||
| IChangeRequestEnabled
|
| IChangeRequestEnabled
|
||||||
| IChangeRequestPatchVariant;
|
| IChangeRequestPatchVariant
|
||||||
|
| IChangeRequestReorderStrategy;
|
||||||
|
|
||||||
type ChangeRequestVariantPatch = {
|
type ChangeRequestVariantPatch = {
|
||||||
variants: IFeatureVariant[];
|
variants: IFeatureVariant[];
|
||||||
@ -123,4 +131,5 @@ export type ChangeRequestAction =
|
|||||||
| 'addStrategy'
|
| 'addStrategy'
|
||||||
| 'updateStrategy'
|
| 'updateStrategy'
|
||||||
| 'deleteStrategy'
|
| 'deleteStrategy'
|
||||||
| 'patchVariant';
|
| 'patchVariant'
|
||||||
|
| 'reorderStrategy';
|
||||||
|
@ -9,6 +9,9 @@ import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
|||||||
import { FeatureStrategyEmpty } from 'component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty';
|
import { FeatureStrategyEmpty } from 'component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
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 {
|
interface IEnvironmentAccordionBodyProps {
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
@ -36,6 +39,10 @@ const EnvironmentAccordionBody = ({
|
|||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
const featureId = useRequiredPathParam('featureId');
|
const featureId = useRequiredPathParam('featureId');
|
||||||
const { setStrategiesSortOrder } = useFeatureStrategyApi();
|
const { setStrategiesSortOrder } = useFeatureStrategyApi();
|
||||||
|
const { addChange } = useChangeRequestApi();
|
||||||
|
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
|
||||||
|
const { refetch: refetchChangeRequests } =
|
||||||
|
usePendingChangeRequests(projectId);
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
const { refetchFeature } = useFeature(projectId, featureId);
|
const { refetchFeature } = useFeature(projectId, featureId);
|
||||||
const [strategies, setStrategies] = useState(
|
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 =
|
const onDragStartRef =
|
||||||
(
|
(
|
||||||
ref: RefObject<HTMLDivElement>,
|
ref: RefObject<HTMLDivElement>,
|
||||||
@ -129,7 +167,7 @@ const EnvironmentAccordionBody = ({
|
|||||||
|
|
||||||
const onDragEnd = () => {
|
const onDragEnd = () => {
|
||||||
setDragItem(null);
|
setDragItem(null);
|
||||||
onReorder(
|
onStrategyReorder(
|
||||||
strategies.map((strategy, sortOrder) => ({
|
strategies.map((strategy, sortOrder) => ({
|
||||||
id: strategy.id,
|
id: strategy.id,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
|
@ -8,7 +8,8 @@ export interface IChangeSchema {
|
|||||||
| 'addStrategy'
|
| 'addStrategy'
|
||||||
| 'updateStrategy'
|
| 'updateStrategy'
|
||||||
| 'deleteStrategy'
|
| 'deleteStrategy'
|
||||||
| 'patchVariant';
|
| 'patchVariant'
|
||||||
|
| 'reorderStrategy';
|
||||||
payload: string | boolean | object | number;
|
payload: string | boolean | object | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ export interface IFeatureStrategy {
|
|||||||
environment?: string;
|
environment?: string;
|
||||||
segments?: number[];
|
segments?: number[];
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
sortOrder?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFeatureStrategyParameters {
|
export interface IFeatureStrategyParameters {
|
||||||
|
Loading…
Reference in New Issue
Block a user