mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +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. ![image](https://user-images.githubusercontent.com/14320932/214563512-664a432f-f2eb-49f7-9721-cbd6785a9320.png)
This commit is contained in:
parent
247f751fea
commit
4d1a004b5d
@ -54,7 +54,7 @@ export const ChangeRequestDialogue: FC<IChangeRequestDialogueProps> = ({
|
||||
show={
|
||||
<Alert severity="info" sx={{ mb: 2 }}>
|
||||
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
|
||||
draft that you can submit for review.
|
||||
</Alert>
|
||||
|
@ -28,6 +28,9 @@ import { CloudCircle } from '@mui/icons-material';
|
||||
import PermissionSwitch from 'component/common/PermissionSwitch/PermissionSwitch';
|
||||
import { UPDATE_FEATURE_VARIANTS } from 'component/providers/AccessProvider/permissions';
|
||||
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 }) => ({
|
||||
display: 'flex',
|
||||
@ -99,6 +102,10 @@ const StyledSelectMenu = styled(SelectMenu)(({ theme }) => ({
|
||||
marginRight: theme.spacing(10),
|
||||
}));
|
||||
|
||||
const StyledCRAlert = styled(Alert)(({ theme }) => ({
|
||||
marginBottom: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const StyledAlert = styled(Alert)(({ theme }) => ({
|
||||
marginTop: theme.spacing(4),
|
||||
}));
|
||||
@ -147,6 +154,11 @@ interface IEnvironmentVariantModalProps {
|
||||
variants: IFeatureVariant[],
|
||||
newVariants: IFeatureVariant[]
|
||||
) => { patch: Operation[]; error?: string };
|
||||
getCrPayload: (variants: IFeatureVariant[]) => {
|
||||
feature: string;
|
||||
action: 'patchVariant';
|
||||
payload: { variants: IFeatureVariant[] };
|
||||
};
|
||||
onConfirm: (updatedVariants: IFeatureVariant[]) => void;
|
||||
}
|
||||
|
||||
@ -156,6 +168,7 @@ export const EnvironmentVariantModal = ({
|
||||
open,
|
||||
setOpen,
|
||||
getApiPayload,
|
||||
getCrPayload,
|
||||
onConfirm,
|
||||
}: IEnvironmentVariantModalProps) => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
@ -163,6 +176,11 @@ export const EnvironmentVariantModal = ({
|
||||
|
||||
const { uiConfig } = useUiConfig();
|
||||
|
||||
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
|
||||
const { data } = usePendingChangeRequests(projectId);
|
||||
const { changeRequestInReviewOrApproved, alert } =
|
||||
useChangeRequestInReviewWarning(data);
|
||||
|
||||
const [name, setName] = useState('');
|
||||
const [customPercentage, setCustomPercentage] = useState(false);
|
||||
const [percentage, setPercentage] = useState('');
|
||||
@ -237,6 +255,7 @@ export const EnvironmentVariantModal = ({
|
||||
};
|
||||
|
||||
const apiPayload = getApiPayload(variants, getUpdatedVariants());
|
||||
const crPayload = getCrPayload(getUpdatedVariants());
|
||||
|
||||
useEffect(() => {
|
||||
clearError(ErrorField.PERCENTAGE);
|
||||
@ -255,11 +274,21 @@ export const EnvironmentVariantModal = ({
|
||||
onConfirm(getUpdatedVariants());
|
||||
};
|
||||
|
||||
const formatApiCode = () => `curl --location --request PATCH '${
|
||||
uiConfig.unleashUrl
|
||||
}/api/admin/projects/${projectId}/features/${featureId}/environments/${
|
||||
environment?.name
|
||||
}/variants' \\
|
||||
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
|
||||
}/api/admin/projects/${projectId}/features/${featureId}/environments/${
|
||||
environment?.name
|
||||
}/variants' \\
|
||||
--header 'Authorization: INSERT_API_KEY' \\
|
||||
--header 'Content-Type: application/json' \\
|
||||
--data-raw '${JSON.stringify(apiPayload.patch, undefined, 2)}'`;
|
||||
@ -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 (
|
||||
<SidebarModal
|
||||
open={open}
|
||||
@ -349,6 +387,29 @@ export const EnvironmentVariantModal = ({
|
||||
</StyledFormSubtitle>
|
||||
<StyledForm onSubmit={handleSubmit}>
|
||||
<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>
|
||||
Variant name
|
||||
</StyledInputDescription>
|
||||
@ -486,7 +547,11 @@ export const EnvironmentVariantModal = ({
|
||||
color="primary"
|
||||
disabled={!isValid}
|
||||
>
|
||||
{editing ? 'Save' : 'Add'} variant
|
||||
{isChangeRequest
|
||||
? changeRequestButtonText
|
||||
: editing
|
||||
? 'Save variant'
|
||||
: 'Add variant'}
|
||||
</Button>
|
||||
<StyledCancelButton
|
||||
onClick={() => {
|
||||
|
@ -10,8 +10,11 @@ import { updateWeight } from 'component/common/util';
|
||||
import { UPDATE_FEATURE_ENVIRONMENT_VARIANTS } from 'component/providers/AccessProvider/permissions';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { IFeatureEnvironment, IFeatureVariant } from 'interfaces/featureToggle';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
IFeatureEnvironmentWithCrEnabled,
|
||||
IFeatureVariant,
|
||||
} from 'interfaces/featureToggle';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { EnvironmentVariantModal } from './EnvironmentVariantModal/EnvironmentVariantModal';
|
||||
import { EnvironmentVariantsCard } from './EnvironmentVariantsCard/EnvironmentVariantsCard';
|
||||
import { VariantDeleteDialog } from './VariantDeleteDialog/VariantDeleteDialog';
|
||||
@ -20,6 +23,10 @@ import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { EnvironmentVariantsCopyFrom } from './EnvironmentVariantsCopyFrom/EnvironmentVariantsCopyFrom';
|
||||
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 }) => ({
|
||||
marginBottom: theme.spacing(4),
|
||||
@ -34,6 +41,7 @@ const StyledButtonContainer = styled('div')(({ theme }) => ({
|
||||
}));
|
||||
|
||||
export const FeatureEnvironmentVariants = () => {
|
||||
const { uiConfig } = useUiConfig();
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const theme = useTheme();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||
@ -46,14 +54,29 @@ export const FeatureEnvironmentVariants = () => {
|
||||
);
|
||||
const { patchFeatureEnvironmentVariants, overrideVariantsInEnvironments } =
|
||||
useFeatureApi();
|
||||
const { refetch: refetchChangeRequests } =
|
||||
usePendingChangeRequests(projectId);
|
||||
const { addChange } = useChangeRequestApi();
|
||||
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
|
||||
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [selectedEnvironment, setSelectedEnvironment] =
|
||||
useState<IFeatureEnvironment>();
|
||||
useState<IFeatureEnvironmentWithCrEnabled>();
|
||||
const [selectedVariant, setSelectedVariant] = useState<IFeatureVariant>();
|
||||
const [modalOpen, setModalOpen] = 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 = (
|
||||
variants: IFeatureVariant[],
|
||||
newVariants: IFeatureVariant[]
|
||||
@ -72,38 +95,91 @@ export const FeatureEnvironmentVariants = () => {
|
||||
return { patch: createPatch(variants, updatedNewVariants) };
|
||||
};
|
||||
|
||||
const getCrPayload = (variants: IFeatureVariant[]) => ({
|
||||
feature: featureId,
|
||||
action: 'patchVariant' as const,
|
||||
payload: { variants },
|
||||
});
|
||||
|
||||
const updateVariants = async (
|
||||
environment: IFeatureEnvironment,
|
||||
environment: IFeatureEnvironmentWithCrEnabled,
|
||||
variants: IFeatureVariant[]
|
||||
) => {
|
||||
const environmentVariants = environment.variants ?? [];
|
||||
const { patch } = getApiPayload(environmentVariants, variants);
|
||||
if (environment.crEnabled) {
|
||||
await addChange(
|
||||
projectId,
|
||||
environment.name,
|
||||
getCrPayload(variants)
|
||||
);
|
||||
refetchChangeRequests();
|
||||
} else {
|
||||
const environmentVariants = environment.variants ?? [];
|
||||
const { patch } = getApiPayload(environmentVariants, variants);
|
||||
|
||||
if (patch.length === 0) return;
|
||||
if (patch.length === 0) return;
|
||||
|
||||
await patchFeatureEnvironmentVariants(
|
||||
projectId,
|
||||
featureId,
|
||||
environment.name,
|
||||
patch
|
||||
);
|
||||
await patchFeatureEnvironmentVariants(
|
||||
projectId,
|
||||
featureId,
|
||||
environment.name,
|
||||
patch
|
||||
);
|
||||
}
|
||||
refetchFeature();
|
||||
};
|
||||
|
||||
const pushToEnvironments = async (
|
||||
variants: IFeatureVariant[],
|
||||
selected: string[]
|
||||
selected: IFeatureEnvironmentWithCrEnabled[]
|
||||
) => {
|
||||
try {
|
||||
await overrideVariantsInEnvironments(
|
||||
projectId,
|
||||
featureId,
|
||||
variants,
|
||||
selected
|
||||
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(
|
||||
projectId,
|
||||
featureId,
|
||||
variants,
|
||||
selectedWithCrDisabled.map(({ name }) => name)
|
||||
);
|
||||
}
|
||||
refetchChangeRequests();
|
||||
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({
|
||||
title: `Variants pushed successfully`,
|
||||
title,
|
||||
type: 'success',
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
@ -111,14 +187,14 @@ export const FeatureEnvironmentVariants = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const addVariant = (environment: IFeatureEnvironment) => {
|
||||
const addVariant = (environment: IFeatureEnvironmentWithCrEnabled) => {
|
||||
setSelectedEnvironment(environment);
|
||||
setSelectedVariant(undefined);
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
const editVariant = (
|
||||
environment: IFeatureEnvironment,
|
||||
environment: IFeatureEnvironmentWithCrEnabled,
|
||||
variant: IFeatureVariant
|
||||
) => {
|
||||
setSelectedEnvironment(environment);
|
||||
@ -127,7 +203,7 @@ export const FeatureEnvironmentVariants = () => {
|
||||
};
|
||||
|
||||
const deleteVariant = (
|
||||
environment: IFeatureEnvironment,
|
||||
environment: IFeatureEnvironmentWithCrEnabled,
|
||||
variant: IFeatureVariant
|
||||
) => {
|
||||
setSelectedEnvironment(environment);
|
||||
@ -147,7 +223,9 @@ export const FeatureEnvironmentVariants = () => {
|
||||
await updateVariants(selectedEnvironment, updatedVariants);
|
||||
setDeleteOpen(false);
|
||||
setToastData({
|
||||
title: `Variant deleted successfully`,
|
||||
title: selectedEnvironment.crEnabled
|
||||
? 'Variant deletion added to draft'
|
||||
: 'Variant deleted successfully',
|
||||
type: 'success',
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
@ -162,9 +240,13 @@ export const FeatureEnvironmentVariants = () => {
|
||||
await updateVariants(selectedEnvironment, updatedVariants);
|
||||
setModalOpen(false);
|
||||
setToastData({
|
||||
title: `Variant ${
|
||||
selectedVariant ? 'updated' : 'added'
|
||||
} successfully`,
|
||||
title: selectedEnvironment.crEnabled
|
||||
? `Variant ${
|
||||
selectedVariant ? 'changes' : ''
|
||||
} added to draft`
|
||||
: `Variant ${
|
||||
selectedVariant ? 'updated' : 'added'
|
||||
} successfully`,
|
||||
type: 'success',
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
@ -174,14 +256,16 @@ export const FeatureEnvironmentVariants = () => {
|
||||
};
|
||||
|
||||
const onCopyVariantsFrom = async (
|
||||
fromEnvironment: IFeatureEnvironment,
|
||||
toEnvironment: IFeatureEnvironment
|
||||
fromEnvironment: IFeatureEnvironmentWithCrEnabled,
|
||||
toEnvironment: IFeatureEnvironmentWithCrEnabled
|
||||
) => {
|
||||
try {
|
||||
const variants = fromEnvironment.variants ?? [];
|
||||
await updateVariants(toEnvironment, variants);
|
||||
setToastData({
|
||||
title: 'Variants copied successfully',
|
||||
title: toEnvironment.crEnabled
|
||||
? 'Variants copy added to draft'
|
||||
: 'Variants copied successfully',
|
||||
type: 'success',
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
@ -190,13 +274,15 @@ export const FeatureEnvironmentVariants = () => {
|
||||
};
|
||||
|
||||
const onUpdateStickiness = async (
|
||||
environment: IFeatureEnvironment,
|
||||
environment: IFeatureEnvironmentWithCrEnabled,
|
||||
updatedVariants: IFeatureVariant[]
|
||||
) => {
|
||||
try {
|
||||
await updateVariants(environment, updatedVariants);
|
||||
setToastData({
|
||||
title: 'Variant stickiness updated successfully',
|
||||
title: environment.crEnabled
|
||||
? 'Variant stickiness update added to draft'
|
||||
: 'Variant stickiness updated successfully',
|
||||
type: 'success',
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
@ -242,8 +328,8 @@ export const FeatureEnvironmentVariants = () => {
|
||||
variants you should use the <code>getVariant()</code> method in
|
||||
the Client SDK.
|
||||
</StyledAlert>
|
||||
{feature.environments.map(environment => {
|
||||
const otherEnvsWithVariants = feature.environments.filter(
|
||||
{environments.map(environment => {
|
||||
const otherEnvsWithVariants = environments.filter(
|
||||
({ name, variants }) =>
|
||||
name !== environment.name && variants?.length
|
||||
);
|
||||
@ -266,7 +352,7 @@ export const FeatureEnvironmentVariants = () => {
|
||||
<StyledButtonContainer>
|
||||
<PushVariantsButton
|
||||
current={environment.name}
|
||||
environments={feature.environments}
|
||||
environments={environments}
|
||||
permission={UPDATE_FEATURE_ENVIRONMENT_VARIANTS}
|
||||
projectId={projectId}
|
||||
onSubmit={selected =>
|
||||
@ -303,6 +389,7 @@ export const FeatureEnvironmentVariants = () => {
|
||||
open={modalOpen}
|
||||
setOpen={setModalOpen}
|
||||
getApiPayload={getApiPayload}
|
||||
getCrPayload={getCrPayload}
|
||||
onConfirm={onVariantConfirm}
|
||||
/>
|
||||
<VariantDeleteDialog
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
styled,
|
||||
} from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||
import { IFeatureEnvironmentWithCrEnabled } from 'interfaces/featureToggle';
|
||||
import { useState } from 'react';
|
||||
import { useCheckProjectAccess } from 'hooks/useHasAccess';
|
||||
|
||||
@ -30,10 +30,10 @@ const StyledButton = styled(Button)(({ theme }) => ({
|
||||
|
||||
interface IPushVariantsButtonProps {
|
||||
current: string;
|
||||
environments: IFeatureEnvironment[];
|
||||
environments: IFeatureEnvironmentWithCrEnabled[];
|
||||
permission: string;
|
||||
projectId: string;
|
||||
onSubmit: (selected: string[]) => void;
|
||||
onSubmit: (selected: IFeatureEnvironmentWithCrEnabled[]) => void;
|
||||
}
|
||||
|
||||
export const PushVariantsButton = ({
|
||||
@ -48,9 +48,9 @@ export const PushVariantsButton = ({
|
||||
);
|
||||
const pushToOpen = Boolean(pushToAnchorEl);
|
||||
|
||||
const [selectedEnvironments, setSelectedEnvironments] = useState<string[]>(
|
||||
[]
|
||||
);
|
||||
const [selectedEnvironments, setSelectedEnvironments] = useState<
|
||||
IFeatureEnvironmentWithCrEnabled[]
|
||||
>([]);
|
||||
|
||||
const hasAccess = useCheckProjectAccess(projectId);
|
||||
const hasAccessTo = environments.reduce((acc, env) => {
|
||||
@ -58,16 +58,22 @@ export const PushVariantsButton = ({
|
||||
return acc;
|
||||
}, {} as Record<string, boolean>);
|
||||
|
||||
const addSelectedEnvironment = (name: string) => {
|
||||
const addSelectedEnvironment = (
|
||||
environment: IFeatureEnvironmentWithCrEnabled
|
||||
) => {
|
||||
setSelectedEnvironments(prevSelectedEnvironments => [
|
||||
...prevSelectedEnvironments,
|
||||
name,
|
||||
environment,
|
||||
]);
|
||||
};
|
||||
|
||||
const removeSelectedEnvironment = (name: string) => {
|
||||
const removeSelectedEnvironment = (
|
||||
environment: IFeatureEnvironmentWithCrEnabled
|
||||
) => {
|
||||
setSelectedEnvironments(prevSelectedEnvironments =>
|
||||
prevSelectedEnvironments.filter(env => env !== name)
|
||||
prevSelectedEnvironments.filter(
|
||||
({ name }) => name !== environment.name
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
@ -121,16 +127,16 @@ export const PushVariantsButton = ({
|
||||
onChange={event => {
|
||||
if (event.target.checked) {
|
||||
addSelectedEnvironment(
|
||||
otherEnvironment.name
|
||||
otherEnvironment
|
||||
);
|
||||
} else {
|
||||
removeSelectedEnvironment(
|
||||
otherEnvironment.name
|
||||
otherEnvironment
|
||||
);
|
||||
}
|
||||
}}
|
||||
checked={selectedEnvironments.includes(
|
||||
otherEnvironment.name
|
||||
otherEnvironment
|
||||
)}
|
||||
value={otherEnvironment.name}
|
||||
/>
|
||||
|
@ -7,7 +7,8 @@ export interface IChangeSchema {
|
||||
| 'updateEnabled'
|
||||
| 'addStrategy'
|
||||
| 'updateStrategy'
|
||||
| 'deleteStrategy';
|
||||
| 'deleteStrategy'
|
||||
| 'patchVariant';
|
||||
payload: string | boolean | object | number;
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,10 @@ export interface IFeatureEnvironment {
|
||||
variants?: IFeatureVariant[];
|
||||
}
|
||||
|
||||
export interface IFeatureEnvironmentWithCrEnabled extends IFeatureEnvironment {
|
||||
crEnabled?: boolean;
|
||||
}
|
||||
|
||||
export interface IFeatureVariant {
|
||||
name: string;
|
||||
stickiness: string;
|
||||
|
@ -47,6 +47,7 @@ export interface IFlags {
|
||||
featuresExportImport?: boolean;
|
||||
newProjectOverview?: boolean;
|
||||
caseInsensitiveInOperators?: boolean;
|
||||
crOnVariants?: boolean;
|
||||
}
|
||||
|
||||
export interface IVersionInfo {
|
||||
|
Loading…
Reference in New Issue
Block a user