mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-23 00:16:25 +01:00
feat: adds CR to variants per env UI (#2989)
https://linear.app/unleash/issue/2-585/add-cr-to-variants-per-environment-ui Adds CR to the variants per environment UI. This is basically the point where we have CRs integrated but can e.g. only update the weight once per CR. Adapting the UI to better fit CR logic will come next. 
This commit is contained in:
parent
247f751fea
commit
4d1a004b5d
@ -54,7 +54,7 @@ export const ChangeRequestDialogue: FC<IChangeRequestDialogueProps> = ({
|
|||||||
show={
|
show={
|
||||||
<Alert severity="info" sx={{ mb: 2 }}>
|
<Alert severity="info" sx={{ mb: 2 }}>
|
||||||
Change requests feature is enabled for {environment}.
|
Change requests feature is enabled for {environment}.
|
||||||
Your changes needs to be approved before they will be
|
Your changes need to be approved before they will be
|
||||||
live. All the changes you do now will be added into a
|
live. All the changes you do now will be added into a
|
||||||
draft that you can submit for review.
|
draft that you can submit for review.
|
||||||
</Alert>
|
</Alert>
|
||||||
|
@ -28,6 +28,9 @@ import { CloudCircle } from '@mui/icons-material';
|
|||||||
import PermissionSwitch from 'component/common/PermissionSwitch/PermissionSwitch';
|
import PermissionSwitch from 'component/common/PermissionSwitch/PermissionSwitch';
|
||||||
import { UPDATE_FEATURE_VARIANTS } from 'component/providers/AccessProvider/permissions';
|
import { UPDATE_FEATURE_VARIANTS } from 'component/providers/AccessProvider/permissions';
|
||||||
import { WeightType } from 'constants/variantTypes';
|
import { WeightType } from 'constants/variantTypes';
|
||||||
|
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
|
||||||
|
import { useChangeRequestInReviewWarning } from 'hooks/useChangeRequestInReviewWarning';
|
||||||
|
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||||
|
|
||||||
const StyledFormSubtitle = styled('div')(({ theme }) => ({
|
const StyledFormSubtitle = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -99,6 +102,10 @@ const StyledSelectMenu = styled(SelectMenu)(({ theme }) => ({
|
|||||||
marginRight: theme.spacing(10),
|
marginRight: theme.spacing(10),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const StyledCRAlert = styled(Alert)(({ theme }) => ({
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
const StyledAlert = styled(Alert)(({ theme }) => ({
|
const StyledAlert = styled(Alert)(({ theme }) => ({
|
||||||
marginTop: theme.spacing(4),
|
marginTop: theme.spacing(4),
|
||||||
}));
|
}));
|
||||||
@ -147,6 +154,11 @@ interface IEnvironmentVariantModalProps {
|
|||||||
variants: IFeatureVariant[],
|
variants: IFeatureVariant[],
|
||||||
newVariants: IFeatureVariant[]
|
newVariants: IFeatureVariant[]
|
||||||
) => { patch: Operation[]; error?: string };
|
) => { patch: Operation[]; error?: string };
|
||||||
|
getCrPayload: (variants: IFeatureVariant[]) => {
|
||||||
|
feature: string;
|
||||||
|
action: 'patchVariant';
|
||||||
|
payload: { variants: IFeatureVariant[] };
|
||||||
|
};
|
||||||
onConfirm: (updatedVariants: IFeatureVariant[]) => void;
|
onConfirm: (updatedVariants: IFeatureVariant[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,6 +168,7 @@ export const EnvironmentVariantModal = ({
|
|||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
getApiPayload,
|
getApiPayload,
|
||||||
|
getCrPayload,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
}: IEnvironmentVariantModalProps) => {
|
}: IEnvironmentVariantModalProps) => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
@ -163,6 +176,11 @@ export const EnvironmentVariantModal = ({
|
|||||||
|
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
|
|
||||||
|
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
|
||||||
|
const { data } = usePendingChangeRequests(projectId);
|
||||||
|
const { changeRequestInReviewOrApproved, alert } =
|
||||||
|
useChangeRequestInReviewWarning(data);
|
||||||
|
|
||||||
const [name, setName] = useState('');
|
const [name, setName] = useState('');
|
||||||
const [customPercentage, setCustomPercentage] = useState(false);
|
const [customPercentage, setCustomPercentage] = useState(false);
|
||||||
const [percentage, setPercentage] = useState('');
|
const [percentage, setPercentage] = useState('');
|
||||||
@ -237,6 +255,7 @@ export const EnvironmentVariantModal = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const apiPayload = getApiPayload(variants, getUpdatedVariants());
|
const apiPayload = getApiPayload(variants, getUpdatedVariants());
|
||||||
|
const crPayload = getCrPayload(getUpdatedVariants());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
clearError(ErrorField.PERCENTAGE);
|
clearError(ErrorField.PERCENTAGE);
|
||||||
@ -255,7 +274,17 @@ export const EnvironmentVariantModal = ({
|
|||||||
onConfirm(getUpdatedVariants());
|
onConfirm(getUpdatedVariants());
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatApiCode = () => `curl --location --request PATCH '${
|
const formatApiCode = () =>
|
||||||
|
isChangeRequest
|
||||||
|
? `curl --location --request POST '${
|
||||||
|
uiConfig.unleashUrl
|
||||||
|
}/api/admin/projects/${projectId}/environments/${
|
||||||
|
environment?.name
|
||||||
|
}/change-requests' \\
|
||||||
|
--header 'Authorization: INSERT_API_KEY' \\
|
||||||
|
--header 'Content-Type: application/json' \\
|
||||||
|
--data-raw '${JSON.stringify(crPayload, undefined, 2)}'`
|
||||||
|
: `curl --location --request PATCH '${
|
||||||
uiConfig.unleashUrl
|
uiConfig.unleashUrl
|
||||||
}/api/admin/projects/${projectId}/features/${featureId}/environments/${
|
}/api/admin/projects/${projectId}/features/${featureId}/environments/${
|
||||||
environment?.name
|
environment?.name
|
||||||
@ -324,6 +353,15 @@ export const EnvironmentVariantModal = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasChangeRequestInReviewForEnvironment =
|
||||||
|
changeRequestInReviewOrApproved(environment?.name || '');
|
||||||
|
|
||||||
|
const changeRequestButtonText = hasChangeRequestInReviewForEnvironment
|
||||||
|
? 'Add to existing change request'
|
||||||
|
: 'Add change to draft';
|
||||||
|
|
||||||
|
const isChangeRequest = isChangeRequestConfigured(environment?.name || '');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarModal
|
<SidebarModal
|
||||||
open={open}
|
open={open}
|
||||||
@ -349,6 +387,29 @@ export const EnvironmentVariantModal = ({
|
|||||||
</StyledFormSubtitle>
|
</StyledFormSubtitle>
|
||||||
<StyledForm onSubmit={handleSubmit}>
|
<StyledForm onSubmit={handleSubmit}>
|
||||||
<div>
|
<div>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={hasChangeRequestInReviewForEnvironment}
|
||||||
|
show={alert}
|
||||||
|
elseShow={
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(isChangeRequest)}
|
||||||
|
show={
|
||||||
|
<StyledCRAlert severity="info">
|
||||||
|
<strong>Change requests</strong> are
|
||||||
|
enabled
|
||||||
|
{environment
|
||||||
|
? ` for ${environment.name}`
|
||||||
|
: ''}
|
||||||
|
. Your changes need to be approved
|
||||||
|
before they will be live. All the
|
||||||
|
changes you do now will be added
|
||||||
|
into a draft that you can submit for
|
||||||
|
review.
|
||||||
|
</StyledCRAlert>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<StyledInputDescription>
|
<StyledInputDescription>
|
||||||
Variant name
|
Variant name
|
||||||
</StyledInputDescription>
|
</StyledInputDescription>
|
||||||
@ -486,7 +547,11 @@ export const EnvironmentVariantModal = ({
|
|||||||
color="primary"
|
color="primary"
|
||||||
disabled={!isValid}
|
disabled={!isValid}
|
||||||
>
|
>
|
||||||
{editing ? 'Save' : 'Add'} variant
|
{isChangeRequest
|
||||||
|
? changeRequestButtonText
|
||||||
|
: editing
|
||||||
|
? 'Save variant'
|
||||||
|
: 'Add variant'}
|
||||||
</Button>
|
</Button>
|
||||||
<StyledCancelButton
|
<StyledCancelButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -10,8 +10,11 @@ import { updateWeight } from 'component/common/util';
|
|||||||
import { UPDATE_FEATURE_ENVIRONMENT_VARIANTS } from 'component/providers/AccessProvider/permissions';
|
import { UPDATE_FEATURE_ENVIRONMENT_VARIANTS } from 'component/providers/AccessProvider/permissions';
|
||||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { IFeatureEnvironment, IFeatureVariant } from 'interfaces/featureToggle';
|
import {
|
||||||
import { useState } from 'react';
|
IFeatureEnvironmentWithCrEnabled,
|
||||||
|
IFeatureVariant,
|
||||||
|
} from 'interfaces/featureToggle';
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
import { EnvironmentVariantModal } from './EnvironmentVariantModal/EnvironmentVariantModal';
|
import { EnvironmentVariantModal } from './EnvironmentVariantModal/EnvironmentVariantModal';
|
||||||
import { EnvironmentVariantsCard } from './EnvironmentVariantsCard/EnvironmentVariantsCard';
|
import { EnvironmentVariantsCard } from './EnvironmentVariantsCard/EnvironmentVariantsCard';
|
||||||
import { VariantDeleteDialog } from './VariantDeleteDialog/VariantDeleteDialog';
|
import { VariantDeleteDialog } from './VariantDeleteDialog/VariantDeleteDialog';
|
||||||
@ -20,6 +23,10 @@ import { formatUnknownError } from 'utils/formatUnknownError';
|
|||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import { EnvironmentVariantsCopyFrom } from './EnvironmentVariantsCopyFrom/EnvironmentVariantsCopyFrom';
|
import { EnvironmentVariantsCopyFrom } from './EnvironmentVariantsCopyFrom/EnvironmentVariantsCopyFrom';
|
||||||
import { PushVariantsButton } from './PushVariantsButton/PushVariantsButton';
|
import { PushVariantsButton } from './PushVariantsButton/PushVariantsButton';
|
||||||
|
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||||
|
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
|
||||||
|
|
||||||
const StyledAlert = styled(Alert)(({ theme }) => ({
|
const StyledAlert = styled(Alert)(({ theme }) => ({
|
||||||
marginBottom: theme.spacing(4),
|
marginBottom: theme.spacing(4),
|
||||||
@ -34,6 +41,7 @@ const StyledButtonContainer = styled('div')(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
export const FeatureEnvironmentVariants = () => {
|
export const FeatureEnvironmentVariants = () => {
|
||||||
|
const { uiConfig } = useUiConfig();
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
@ -46,14 +54,29 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
);
|
);
|
||||||
const { patchFeatureEnvironmentVariants, overrideVariantsInEnvironments } =
|
const { patchFeatureEnvironmentVariants, overrideVariantsInEnvironments } =
|
||||||
useFeatureApi();
|
useFeatureApi();
|
||||||
|
const { refetch: refetchChangeRequests } =
|
||||||
|
usePendingChangeRequests(projectId);
|
||||||
|
const { addChange } = useChangeRequestApi();
|
||||||
|
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
|
||||||
|
|
||||||
const [searchValue, setSearchValue] = useState('');
|
const [searchValue, setSearchValue] = useState('');
|
||||||
const [selectedEnvironment, setSelectedEnvironment] =
|
const [selectedEnvironment, setSelectedEnvironment] =
|
||||||
useState<IFeatureEnvironment>();
|
useState<IFeatureEnvironmentWithCrEnabled>();
|
||||||
const [selectedVariant, setSelectedVariant] = useState<IFeatureVariant>();
|
const [selectedVariant, setSelectedVariant] = useState<IFeatureVariant>();
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||||
|
|
||||||
|
const environments: IFeatureEnvironmentWithCrEnabled[] = useMemo(
|
||||||
|
() =>
|
||||||
|
feature?.environments?.map(environment => ({
|
||||||
|
...environment,
|
||||||
|
crEnabled:
|
||||||
|
uiConfig.flags.crOnVariants &&
|
||||||
|
isChangeRequestConfigured(environment.name),
|
||||||
|
})) || [],
|
||||||
|
[feature.environments, uiConfig.flags.crOnVariants]
|
||||||
|
);
|
||||||
|
|
||||||
const createPatch = (
|
const createPatch = (
|
||||||
variants: IFeatureVariant[],
|
variants: IFeatureVariant[],
|
||||||
newVariants: IFeatureVariant[]
|
newVariants: IFeatureVariant[]
|
||||||
@ -72,10 +95,24 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
return { patch: createPatch(variants, updatedNewVariants) };
|
return { patch: createPatch(variants, updatedNewVariants) };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getCrPayload = (variants: IFeatureVariant[]) => ({
|
||||||
|
feature: featureId,
|
||||||
|
action: 'patchVariant' as const,
|
||||||
|
payload: { variants },
|
||||||
|
});
|
||||||
|
|
||||||
const updateVariants = async (
|
const updateVariants = async (
|
||||||
environment: IFeatureEnvironment,
|
environment: IFeatureEnvironmentWithCrEnabled,
|
||||||
variants: IFeatureVariant[]
|
variants: IFeatureVariant[]
|
||||||
) => {
|
) => {
|
||||||
|
if (environment.crEnabled) {
|
||||||
|
await addChange(
|
||||||
|
projectId,
|
||||||
|
environment.name,
|
||||||
|
getCrPayload(variants)
|
||||||
|
);
|
||||||
|
refetchChangeRequests();
|
||||||
|
} else {
|
||||||
const environmentVariants = environment.variants ?? [];
|
const environmentVariants = environment.variants ?? [];
|
||||||
const { patch } = getApiPayload(environmentVariants, variants);
|
const { patch } = getApiPayload(environmentVariants, variants);
|
||||||
|
|
||||||
@ -87,23 +124,62 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
environment.name,
|
environment.name,
|
||||||
patch
|
patch
|
||||||
);
|
);
|
||||||
|
}
|
||||||
refetchFeature();
|
refetchFeature();
|
||||||
};
|
};
|
||||||
|
|
||||||
const pushToEnvironments = async (
|
const pushToEnvironments = async (
|
||||||
variants: IFeatureVariant[],
|
variants: IFeatureVariant[],
|
||||||
selected: string[]
|
selected: IFeatureEnvironmentWithCrEnabled[]
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
|
const selectedWithCrEnabled = selected.filter(
|
||||||
|
({ crEnabled }) => crEnabled
|
||||||
|
);
|
||||||
|
const selectedWithCrDisabled = selected.filter(
|
||||||
|
({ crEnabled }) => !crEnabled
|
||||||
|
);
|
||||||
|
|
||||||
|
if (selectedWithCrEnabled.length) {
|
||||||
|
await Promise.all(
|
||||||
|
selectedWithCrEnabled.map(environment =>
|
||||||
|
addChange(
|
||||||
|
projectId,
|
||||||
|
environment.name,
|
||||||
|
getCrPayload(variants)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (selectedWithCrDisabled.length) {
|
||||||
await overrideVariantsInEnvironments(
|
await overrideVariantsInEnvironments(
|
||||||
projectId,
|
projectId,
|
||||||
featureId,
|
featureId,
|
||||||
variants,
|
variants,
|
||||||
selected
|
selectedWithCrDisabled.map(({ name }) => name)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
refetchChangeRequests();
|
||||||
refetchFeature();
|
refetchFeature();
|
||||||
|
const pushTitle = selectedWithCrDisabled.length
|
||||||
|
? `Variants pushed to ${
|
||||||
|
selectedWithCrDisabled.length === 1
|
||||||
|
? selectedWithCrDisabled[0].name
|
||||||
|
: `${selectedWithCrDisabled.length} environments`
|
||||||
|
}`
|
||||||
|
: '';
|
||||||
|
const draftTitle = selectedWithCrEnabled.length
|
||||||
|
? `Variants push added to ${
|
||||||
|
selectedWithCrEnabled.length === 1
|
||||||
|
? `${selectedWithCrEnabled[0].name} draft`
|
||||||
|
: `${selectedWithCrEnabled.length} drafts`
|
||||||
|
}`
|
||||||
|
: '';
|
||||||
|
const title = `${pushTitle}${
|
||||||
|
pushTitle && draftTitle ? '. ' : ''
|
||||||
|
}${draftTitle}`;
|
||||||
setToastData({
|
setToastData({
|
||||||
title: `Variants pushed successfully`,
|
title,
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
@ -111,14 +187,14 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const addVariant = (environment: IFeatureEnvironment) => {
|
const addVariant = (environment: IFeatureEnvironmentWithCrEnabled) => {
|
||||||
setSelectedEnvironment(environment);
|
setSelectedEnvironment(environment);
|
||||||
setSelectedVariant(undefined);
|
setSelectedVariant(undefined);
|
||||||
setModalOpen(true);
|
setModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const editVariant = (
|
const editVariant = (
|
||||||
environment: IFeatureEnvironment,
|
environment: IFeatureEnvironmentWithCrEnabled,
|
||||||
variant: IFeatureVariant
|
variant: IFeatureVariant
|
||||||
) => {
|
) => {
|
||||||
setSelectedEnvironment(environment);
|
setSelectedEnvironment(environment);
|
||||||
@ -127,7 +203,7 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const deleteVariant = (
|
const deleteVariant = (
|
||||||
environment: IFeatureEnvironment,
|
environment: IFeatureEnvironmentWithCrEnabled,
|
||||||
variant: IFeatureVariant
|
variant: IFeatureVariant
|
||||||
) => {
|
) => {
|
||||||
setSelectedEnvironment(environment);
|
setSelectedEnvironment(environment);
|
||||||
@ -147,7 +223,9 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
await updateVariants(selectedEnvironment, updatedVariants);
|
await updateVariants(selectedEnvironment, updatedVariants);
|
||||||
setDeleteOpen(false);
|
setDeleteOpen(false);
|
||||||
setToastData({
|
setToastData({
|
||||||
title: `Variant deleted successfully`,
|
title: selectedEnvironment.crEnabled
|
||||||
|
? 'Variant deletion added to draft'
|
||||||
|
: 'Variant deleted successfully',
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
@ -162,7 +240,11 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
await updateVariants(selectedEnvironment, updatedVariants);
|
await updateVariants(selectedEnvironment, updatedVariants);
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
setToastData({
|
setToastData({
|
||||||
title: `Variant ${
|
title: selectedEnvironment.crEnabled
|
||||||
|
? `Variant ${
|
||||||
|
selectedVariant ? 'changes' : ''
|
||||||
|
} added to draft`
|
||||||
|
: `Variant ${
|
||||||
selectedVariant ? 'updated' : 'added'
|
selectedVariant ? 'updated' : 'added'
|
||||||
} successfully`,
|
} successfully`,
|
||||||
type: 'success',
|
type: 'success',
|
||||||
@ -174,14 +256,16 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onCopyVariantsFrom = async (
|
const onCopyVariantsFrom = async (
|
||||||
fromEnvironment: IFeatureEnvironment,
|
fromEnvironment: IFeatureEnvironmentWithCrEnabled,
|
||||||
toEnvironment: IFeatureEnvironment
|
toEnvironment: IFeatureEnvironmentWithCrEnabled
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const variants = fromEnvironment.variants ?? [];
|
const variants = fromEnvironment.variants ?? [];
|
||||||
await updateVariants(toEnvironment, variants);
|
await updateVariants(toEnvironment, variants);
|
||||||
setToastData({
|
setToastData({
|
||||||
title: 'Variants copied successfully',
|
title: toEnvironment.crEnabled
|
||||||
|
? 'Variants copy added to draft'
|
||||||
|
: 'Variants copied successfully',
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
@ -190,13 +274,15 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onUpdateStickiness = async (
|
const onUpdateStickiness = async (
|
||||||
environment: IFeatureEnvironment,
|
environment: IFeatureEnvironmentWithCrEnabled,
|
||||||
updatedVariants: IFeatureVariant[]
|
updatedVariants: IFeatureVariant[]
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
await updateVariants(environment, updatedVariants);
|
await updateVariants(environment, updatedVariants);
|
||||||
setToastData({
|
setToastData({
|
||||||
title: 'Variant stickiness updated successfully',
|
title: environment.crEnabled
|
||||||
|
? 'Variant stickiness update added to draft'
|
||||||
|
: 'Variant stickiness updated successfully',
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
@ -242,8 +328,8 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
variants you should use the <code>getVariant()</code> method in
|
variants you should use the <code>getVariant()</code> method in
|
||||||
the Client SDK.
|
the Client SDK.
|
||||||
</StyledAlert>
|
</StyledAlert>
|
||||||
{feature.environments.map(environment => {
|
{environments.map(environment => {
|
||||||
const otherEnvsWithVariants = feature.environments.filter(
|
const otherEnvsWithVariants = environments.filter(
|
||||||
({ name, variants }) =>
|
({ name, variants }) =>
|
||||||
name !== environment.name && variants?.length
|
name !== environment.name && variants?.length
|
||||||
);
|
);
|
||||||
@ -266,7 +352,7 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
<StyledButtonContainer>
|
<StyledButtonContainer>
|
||||||
<PushVariantsButton
|
<PushVariantsButton
|
||||||
current={environment.name}
|
current={environment.name}
|
||||||
environments={feature.environments}
|
environments={environments}
|
||||||
permission={UPDATE_FEATURE_ENVIRONMENT_VARIANTS}
|
permission={UPDATE_FEATURE_ENVIRONMENT_VARIANTS}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
onSubmit={selected =>
|
onSubmit={selected =>
|
||||||
@ -303,6 +389,7 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
open={modalOpen}
|
open={modalOpen}
|
||||||
setOpen={setModalOpen}
|
setOpen={setModalOpen}
|
||||||
getApiPayload={getApiPayload}
|
getApiPayload={getApiPayload}
|
||||||
|
getCrPayload={getCrPayload}
|
||||||
onConfirm={onVariantConfirm}
|
onConfirm={onVariantConfirm}
|
||||||
/>
|
/>
|
||||||
<VariantDeleteDialog
|
<VariantDeleteDialog
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
styled,
|
styled,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
import { IFeatureEnvironmentWithCrEnabled } from 'interfaces/featureToggle';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useCheckProjectAccess } from 'hooks/useHasAccess';
|
import { useCheckProjectAccess } from 'hooks/useHasAccess';
|
||||||
|
|
||||||
@ -30,10 +30,10 @@ const StyledButton = styled(Button)(({ theme }) => ({
|
|||||||
|
|
||||||
interface IPushVariantsButtonProps {
|
interface IPushVariantsButtonProps {
|
||||||
current: string;
|
current: string;
|
||||||
environments: IFeatureEnvironment[];
|
environments: IFeatureEnvironmentWithCrEnabled[];
|
||||||
permission: string;
|
permission: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
onSubmit: (selected: string[]) => void;
|
onSubmit: (selected: IFeatureEnvironmentWithCrEnabled[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PushVariantsButton = ({
|
export const PushVariantsButton = ({
|
||||||
@ -48,9 +48,9 @@ export const PushVariantsButton = ({
|
|||||||
);
|
);
|
||||||
const pushToOpen = Boolean(pushToAnchorEl);
|
const pushToOpen = Boolean(pushToAnchorEl);
|
||||||
|
|
||||||
const [selectedEnvironments, setSelectedEnvironments] = useState<string[]>(
|
const [selectedEnvironments, setSelectedEnvironments] = useState<
|
||||||
[]
|
IFeatureEnvironmentWithCrEnabled[]
|
||||||
);
|
>([]);
|
||||||
|
|
||||||
const hasAccess = useCheckProjectAccess(projectId);
|
const hasAccess = useCheckProjectAccess(projectId);
|
||||||
const hasAccessTo = environments.reduce((acc, env) => {
|
const hasAccessTo = environments.reduce((acc, env) => {
|
||||||
@ -58,16 +58,22 @@ export const PushVariantsButton = ({
|
|||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<string, boolean>);
|
}, {} as Record<string, boolean>);
|
||||||
|
|
||||||
const addSelectedEnvironment = (name: string) => {
|
const addSelectedEnvironment = (
|
||||||
|
environment: IFeatureEnvironmentWithCrEnabled
|
||||||
|
) => {
|
||||||
setSelectedEnvironments(prevSelectedEnvironments => [
|
setSelectedEnvironments(prevSelectedEnvironments => [
|
||||||
...prevSelectedEnvironments,
|
...prevSelectedEnvironments,
|
||||||
name,
|
environment,
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeSelectedEnvironment = (name: string) => {
|
const removeSelectedEnvironment = (
|
||||||
|
environment: IFeatureEnvironmentWithCrEnabled
|
||||||
|
) => {
|
||||||
setSelectedEnvironments(prevSelectedEnvironments =>
|
setSelectedEnvironments(prevSelectedEnvironments =>
|
||||||
prevSelectedEnvironments.filter(env => env !== name)
|
prevSelectedEnvironments.filter(
|
||||||
|
({ name }) => name !== environment.name
|
||||||
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -121,16 +127,16 @@ export const PushVariantsButton = ({
|
|||||||
onChange={event => {
|
onChange={event => {
|
||||||
if (event.target.checked) {
|
if (event.target.checked) {
|
||||||
addSelectedEnvironment(
|
addSelectedEnvironment(
|
||||||
otherEnvironment.name
|
otherEnvironment
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
removeSelectedEnvironment(
|
removeSelectedEnvironment(
|
||||||
otherEnvironment.name
|
otherEnvironment
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
checked={selectedEnvironments.includes(
|
checked={selectedEnvironments.includes(
|
||||||
otherEnvironment.name
|
otherEnvironment
|
||||||
)}
|
)}
|
||||||
value={otherEnvironment.name}
|
value={otherEnvironment.name}
|
||||||
/>
|
/>
|
||||||
|
@ -7,7 +7,8 @@ export interface IChangeSchema {
|
|||||||
| 'updateEnabled'
|
| 'updateEnabled'
|
||||||
| 'addStrategy'
|
| 'addStrategy'
|
||||||
| 'updateStrategy'
|
| 'updateStrategy'
|
||||||
| 'deleteStrategy';
|
| 'deleteStrategy'
|
||||||
|
| 'patchVariant';
|
||||||
payload: string | boolean | object | number;
|
payload: string | boolean | object | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,10 @@ export interface IFeatureEnvironment {
|
|||||||
variants?: IFeatureVariant[];
|
variants?: IFeatureVariant[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IFeatureEnvironmentWithCrEnabled extends IFeatureEnvironment {
|
||||||
|
crEnabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IFeatureVariant {
|
export interface IFeatureVariant {
|
||||||
name: string;
|
name: string;
|
||||||
stickiness: string;
|
stickiness: string;
|
||||||
|
@ -47,6 +47,7 @@ export interface IFlags {
|
|||||||
featuresExportImport?: boolean;
|
featuresExportImport?: boolean;
|
||||||
newProjectOverview?: boolean;
|
newProjectOverview?: boolean;
|
||||||
caseInsensitiveInOperators?: boolean;
|
caseInsensitiveInOperators?: boolean;
|
||||||
|
crOnVariants?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IVersionInfo {
|
export interface IVersionInfo {
|
||||||
|
Loading…
Reference in New Issue
Block a user