mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-10 17:53:36 +02:00
feat: toggleable variants per env
This commit is contained in:
parent
8a8cd1bf27
commit
070fedf83f
@ -152,6 +152,7 @@ interface IEnvironmentVariantModalProps {
|
|||||||
newVariants: IFeatureVariant[]
|
newVariants: IFeatureVariant[]
|
||||||
) => { patch: Operation[]; error?: string };
|
) => { patch: Operation[]; error?: string };
|
||||||
onConfirm: (updatedVariants: IFeatureVariant[]) => void;
|
onConfirm: (updatedVariants: IFeatureVariant[]) => void;
|
||||||
|
global?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EnvironmentVariantModal = ({
|
export const EnvironmentVariantModal = ({
|
||||||
@ -161,6 +162,7 @@ export const EnvironmentVariantModal = ({
|
|||||||
setOpen,
|
setOpen,
|
||||||
getApiPayload,
|
getApiPayload,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
|
global,
|
||||||
}: IEnvironmentVariantModalProps) => {
|
}: IEnvironmentVariantModalProps) => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
const featureId = useRequiredPathParam('featureId');
|
const featureId = useRequiredPathParam('featureId');
|
||||||
@ -259,11 +261,19 @@ export const EnvironmentVariantModal = ({
|
|||||||
onConfirm(getUpdatedVariants());
|
onConfirm(getUpdatedVariants());
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatApiCode = () => `curl --location --request PATCH '${
|
const formatApiCode = () =>
|
||||||
uiConfig.unleashUrl
|
global
|
||||||
}/api/admin/projects/${projectId}/features/${featureId}/environments/${
|
? `curl --location --request PATCH '${
|
||||||
environment?.name
|
uiConfig.unleashUrl
|
||||||
}/variants' \\
|
}/api/admin/projects/${projectId}/features/${featureId}/variants' \\
|
||||||
|
--header 'Authorization: INSERT_API_KEY' \\
|
||||||
|
--header 'Content-Type: application/json' \\
|
||||||
|
--data-raw '${JSON.stringify(apiPayload.patch, undefined, 2)}'`
|
||||||
|
: `curl --location --request PATCH '${
|
||||||
|
uiConfig.unleashUrl
|
||||||
|
}/api/admin/projects/${projectId}/features/${featureId}/environments/${
|
||||||
|
environment?.name
|
||||||
|
}/variants' \\
|
||||||
--header 'Authorization: INSERT_API_KEY' \\
|
--header 'Authorization: INSERT_API_KEY' \\
|
||||||
--header 'Content-Type: application/json' \\
|
--header 'Content-Type: application/json' \\
|
||||||
--data-raw '${JSON.stringify(apiPayload.patch, undefined, 2)}'`;
|
--data-raw '${JSON.stringify(apiPayload.patch, undefined, 2)}'`;
|
||||||
@ -300,7 +310,9 @@ export const EnvironmentVariantModal = ({
|
|||||||
if (!isNameUnique(name)) {
|
if (!isNameUnique(name)) {
|
||||||
setError(
|
setError(
|
||||||
ErrorField.NAME,
|
ErrorField.NAME,
|
||||||
'A variant with that name already exists for this environment.'
|
global
|
||||||
|
? 'A variant with that name already exists.'
|
||||||
|
: 'A variant with that name already exists for this environment.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
setName(name);
|
setName(name);
|
||||||
@ -345,10 +357,20 @@ export const EnvironmentVariantModal = ({
|
|||||||
loading={!open}
|
loading={!open}
|
||||||
>
|
>
|
||||||
<StyledFormSubtitle>
|
<StyledFormSubtitle>
|
||||||
<StyledCloudCircle deprecated={!environment?.enabled} />
|
<ConditionallyRender
|
||||||
<StyledName deprecated={!environment?.enabled}>
|
condition={Boolean(global)}
|
||||||
{environment?.name}
|
show={<span>All environments</span>}
|
||||||
</StyledName>
|
elseShow={
|
||||||
|
<>
|
||||||
|
<StyledCloudCircle
|
||||||
|
deprecated={!environment?.enabled}
|
||||||
|
/>
|
||||||
|
<StyledName deprecated={!environment?.enabled}>
|
||||||
|
{environment?.name}
|
||||||
|
</StyledName>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</StyledFormSubtitle>
|
</StyledFormSubtitle>
|
||||||
<StyledForm onSubmit={handleSubmit}>
|
<StyledForm onSubmit={handleSubmit}>
|
||||||
<div>
|
<div>
|
||||||
|
@ -59,6 +59,7 @@ interface IEnvironmentVariantsCardProps {
|
|||||||
onEditVariant: (variant: IFeatureVariant) => void;
|
onEditVariant: (variant: IFeatureVariant) => void;
|
||||||
onDeleteVariant: (variant: IFeatureVariant) => void;
|
onDeleteVariant: (variant: IFeatureVariant) => void;
|
||||||
onUpdateStickiness: (variant: IFeatureVariant[]) => void;
|
onUpdateStickiness: (variant: IFeatureVariant[]) => void;
|
||||||
|
global?: boolean;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +69,7 @@ export const EnvironmentVariantsCard = ({
|
|||||||
onEditVariant,
|
onEditVariant,
|
||||||
onDeleteVariant,
|
onDeleteVariant,
|
||||||
onUpdateStickiness,
|
onUpdateStickiness,
|
||||||
|
global,
|
||||||
children,
|
children,
|
||||||
}: IEnvironmentVariantsCardProps) => {
|
}: IEnvironmentVariantsCardProps) => {
|
||||||
const { context } = useUnleashContext();
|
const { context } = useUnleashContext();
|
||||||
@ -104,10 +106,20 @@ export const EnvironmentVariantsCard = ({
|
|||||||
<StyledCard>
|
<StyledCard>
|
||||||
<StyledHeader>
|
<StyledHeader>
|
||||||
<div>
|
<div>
|
||||||
<StyledCloudCircle deprecated={!environment.enabled} />
|
<ConditionallyRender
|
||||||
<StyledName deprecated={!environment.enabled}>
|
condition={Boolean(global)}
|
||||||
{environment.name}
|
show={<StyledName>All environments</StyledName>}
|
||||||
</StyledName>
|
elseShow={
|
||||||
|
<>
|
||||||
|
<StyledCloudCircle
|
||||||
|
deprecated={!environment.enabled}
|
||||||
|
/>
|
||||||
|
<StyledName deprecated={!environment.enabled}>
|
||||||
|
{environment.name}
|
||||||
|
</StyledName>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
</StyledHeader>
|
</StyledHeader>
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
import * as jsonpatch from 'fast-json-patch';
|
import * as jsonpatch from 'fast-json-patch';
|
||||||
|
|
||||||
import { Alert, styled, useMediaQuery, useTheme } from '@mui/material';
|
import {
|
||||||
|
Alert,
|
||||||
|
FormControlLabel,
|
||||||
|
styled,
|
||||||
|
Switch,
|
||||||
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
|
} from '@mui/material';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||||
@ -11,7 +18,7 @@ import { UPDATE_FEATURE_ENVIRONMENT_VARIANTS } from 'component/providers/AccessP
|
|||||||
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 { IFeatureEnvironment, IFeatureVariant } from 'interfaces/featureToggle';
|
||||||
import { useState } from 'react';
|
import { useEffect, 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';
|
||||||
@ -19,6 +26,7 @@ import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
|
|||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
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 { dequal } from 'dequal';
|
||||||
|
|
||||||
const StyledAlert = styled(Alert)(({ theme }) => ({
|
const StyledAlert = styled(Alert)(({ theme }) => ({
|
||||||
marginBottom: theme.spacing(4),
|
marginBottom: theme.spacing(4),
|
||||||
@ -52,6 +60,20 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||||
|
|
||||||
|
const [perEnvironment, setPerEnvironment] = useState(false);
|
||||||
|
|
||||||
|
const envSpecificVariants = !feature.environments.reduce(
|
||||||
|
(acc, { variants }) =>
|
||||||
|
acc && dequal(feature.environments[0].variants, variants),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (envSpecificVariants) {
|
||||||
|
setPerEnvironment(envSpecificVariants);
|
||||||
|
}
|
||||||
|
}, [envSpecificVariants]);
|
||||||
|
|
||||||
const createPatch = (
|
const createPatch = (
|
||||||
variants: IFeatureVariant[],
|
variants: IFeatureVariant[],
|
||||||
newVariants: IFeatureVariant[]
|
newVariants: IFeatureVariant[]
|
||||||
@ -75,10 +97,13 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateVariants = async (
|
const updateVariants = async (
|
||||||
environment: IFeatureEnvironment,
|
variants: IFeatureVariant[],
|
||||||
variants: IFeatureVariant[]
|
environment?: IFeatureEnvironment
|
||||||
) => {
|
) => {
|
||||||
const environmentVariants = environment.variants ?? [];
|
const environmentVariants =
|
||||||
|
(environment
|
||||||
|
? environment.variants
|
||||||
|
: feature.environments[0].variants) ?? [];
|
||||||
const { patch } = getApiPayload(environmentVariants, variants);
|
const { patch } = getApiPayload(environmentVariants, variants);
|
||||||
|
|
||||||
if (patch.length === 0) return;
|
if (patch.length === 0) return;
|
||||||
@ -86,21 +111,21 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
await patchFeatureEnvironmentVariants(
|
await patchFeatureEnvironmentVariants(
|
||||||
projectId,
|
projectId,
|
||||||
featureId,
|
featureId,
|
||||||
environment.name,
|
patch,
|
||||||
patch
|
environment?.name
|
||||||
);
|
);
|
||||||
refetchFeature();
|
refetchFeature();
|
||||||
};
|
};
|
||||||
|
|
||||||
const addVariant = (environment: IFeatureEnvironment) => {
|
const addVariant = (environment?: IFeatureEnvironment) => {
|
||||||
setSelectedEnvironment(environment);
|
setSelectedEnvironment(environment);
|
||||||
setSelectedVariant(undefined);
|
setSelectedVariant(undefined);
|
||||||
setModalOpen(true);
|
setModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const editVariant = (
|
const editVariant = (
|
||||||
environment: IFeatureEnvironment,
|
variant: IFeatureVariant,
|
||||||
variant: IFeatureVariant
|
environment?: IFeatureEnvironment
|
||||||
) => {
|
) => {
|
||||||
setSelectedEnvironment(environment);
|
setSelectedEnvironment(environment);
|
||||||
setSelectedVariant(variant);
|
setSelectedVariant(variant);
|
||||||
@ -108,8 +133,8 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const deleteVariant = (
|
const deleteVariant = (
|
||||||
environment: IFeatureEnvironment,
|
variant: IFeatureVariant,
|
||||||
variant: IFeatureVariant
|
environment?: IFeatureEnvironment
|
||||||
) => {
|
) => {
|
||||||
setSelectedEnvironment(environment);
|
setSelectedEnvironment(environment);
|
||||||
setSelectedVariant(variant);
|
setSelectedVariant(variant);
|
||||||
@ -117,15 +142,18 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onDeleteConfirm = async () => {
|
const onDeleteConfirm = async () => {
|
||||||
if (selectedEnvironment && selectedVariant) {
|
if (selectedVariant) {
|
||||||
const variants = selectedEnvironment.variants ?? [];
|
const variants =
|
||||||
|
(selectedEnvironment
|
||||||
|
? selectedEnvironment.variants
|
||||||
|
: feature.environments[0].variants) ?? [];
|
||||||
|
|
||||||
const updatedVariants = variants.filter(
|
const updatedVariants = variants.filter(
|
||||||
({ name }) => name !== selectedVariant.name
|
({ name }) => name !== selectedVariant.name
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateVariants(selectedEnvironment, updatedVariants);
|
await updateVariants(updatedVariants, selectedEnvironment);
|
||||||
setDeleteOpen(false);
|
setDeleteOpen(false);
|
||||||
setToastData({
|
setToastData({
|
||||||
title: `Variant deleted successfully`,
|
title: `Variant deleted successfully`,
|
||||||
@ -138,19 +166,17 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onVariantConfirm = async (updatedVariants: IFeatureVariant[]) => {
|
const onVariantConfirm = async (updatedVariants: IFeatureVariant[]) => {
|
||||||
if (selectedEnvironment) {
|
try {
|
||||||
try {
|
await updateVariants(updatedVariants, selectedEnvironment);
|
||||||
await updateVariants(selectedEnvironment, updatedVariants);
|
setModalOpen(false);
|
||||||
setModalOpen(false);
|
setToastData({
|
||||||
setToastData({
|
title: `Variant ${
|
||||||
title: `Variant ${
|
selectedVariant ? 'updated' : 'added'
|
||||||
selectedVariant ? 'updated' : 'added'
|
} successfully`,
|
||||||
} successfully`,
|
type: 'success',
|
||||||
type: 'success',
|
});
|
||||||
});
|
} catch (error: unknown) {
|
||||||
} catch (error: unknown) {
|
setToastApiError(formatUnknownError(error));
|
||||||
setToastApiError(formatUnknownError(error));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -160,7 +186,7 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const variants = fromEnvironment.variants ?? [];
|
const variants = fromEnvironment.variants ?? [];
|
||||||
await updateVariants(toEnvironment, variants);
|
await updateVariants(variants, toEnvironment);
|
||||||
setToastData({
|
setToastData({
|
||||||
title: 'Variants copied successfully',
|
title: 'Variants copied successfully',
|
||||||
type: 'success',
|
type: 'success',
|
||||||
@ -171,11 +197,11 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onUpdateStickiness = async (
|
const onUpdateStickiness = async (
|
||||||
environment: IFeatureEnvironment,
|
updatedVariants: IFeatureVariant[],
|
||||||
updatedVariants: IFeatureVariant[]
|
environment?: IFeatureEnvironment
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
await updateVariants(environment, updatedVariants);
|
await updateVariants(updatedVariants, environment);
|
||||||
setToastData({
|
setToastData({
|
||||||
title: 'Variant stickiness updated successfully',
|
title: 'Variant stickiness updated successfully',
|
||||||
type: 'success',
|
type: 'success',
|
||||||
@ -192,17 +218,32 @@ export const FeatureEnvironmentVariants = () => {
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
title="Variants"
|
title="Variants"
|
||||||
actions={
|
actions={
|
||||||
<ConditionallyRender
|
<>
|
||||||
condition={!isSmallScreen}
|
<ConditionallyRender
|
||||||
show={
|
condition={!isSmallScreen}
|
||||||
<>
|
show={
|
||||||
<Search
|
<Search
|
||||||
initialValue={searchValue}
|
initialValue={searchValue}
|
||||||
onChange={setSearchValue}
|
onChange={setSearchValue}
|
||||||
/>
|
/>
|
||||||
</>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
<FormControlLabel
|
||||||
|
data-loading
|
||||||
|
label="Per environment"
|
||||||
|
labelPlacement="start"
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
checked={perEnvironment}
|
||||||
|
onChange={() =>
|
||||||
|
setPerEnvironment(!perEnvironment)
|
||||||
|
}
|
||||||
|
color="primary"
|
||||||
|
disabled={envSpecificVariants}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
@ -223,51 +264,114 @@ 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 => {
|
<ConditionallyRender
|
||||||
const otherEnvsWithVariants = feature.environments.filter(
|
condition={Boolean(feature.environments.length)}
|
||||||
({ name, variants }) =>
|
show={
|
||||||
name !== environment.name && variants?.length
|
<ConditionallyRender
|
||||||
);
|
condition={perEnvironment}
|
||||||
|
show={feature.environments.map(environment => {
|
||||||
|
const otherEnvsWithVariants =
|
||||||
|
feature.environments.filter(
|
||||||
|
({ name, variants }) =>
|
||||||
|
name !== environment.name &&
|
||||||
|
variants?.length
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EnvironmentVariantsCard
|
<EnvironmentVariantsCard
|
||||||
key={environment.name}
|
key={environment.name}
|
||||||
environment={environment}
|
environment={environment}
|
||||||
searchValue={searchValue}
|
searchValue={searchValue}
|
||||||
onEditVariant={(variant: IFeatureVariant) =>
|
onEditVariant={(variant: IFeatureVariant) =>
|
||||||
editVariant(environment, variant)
|
editVariant(variant, environment)
|
||||||
}
|
}
|
||||||
onDeleteVariant={(variant: IFeatureVariant) =>
|
onDeleteVariant={(
|
||||||
deleteVariant(environment, variant)
|
variant: IFeatureVariant
|
||||||
}
|
) => deleteVariant(variant, environment)}
|
||||||
onUpdateStickiness={(variants: IFeatureVariant[]) =>
|
onUpdateStickiness={(
|
||||||
onUpdateStickiness(environment, variants)
|
variants: IFeatureVariant[]
|
||||||
}
|
) =>
|
||||||
>
|
onUpdateStickiness(
|
||||||
<StyledButtonContainer>
|
variants,
|
||||||
<EnvironmentVariantsCopyFrom
|
environment
|
||||||
environment={environment}
|
)
|
||||||
permission={UPDATE_FEATURE_ENVIRONMENT_VARIANTS}
|
}
|
||||||
projectId={projectId}
|
>
|
||||||
environmentId={environment.name}
|
<StyledButtonContainer>
|
||||||
onCopyVariantsFrom={onCopyVariantsFrom}
|
<EnvironmentVariantsCopyFrom
|
||||||
otherEnvsWithVariants={otherEnvsWithVariants}
|
environment={environment}
|
||||||
/>
|
permission={
|
||||||
<PermissionButton
|
UPDATE_FEATURE_ENVIRONMENT_VARIANTS
|
||||||
onClick={() => addVariant(environment)}
|
}
|
||||||
variant="outlined"
|
projectId={projectId}
|
||||||
permission={UPDATE_FEATURE_ENVIRONMENT_VARIANTS}
|
environmentId={environment.name}
|
||||||
projectId={projectId}
|
onCopyVariantsFrom={
|
||||||
environmentId={environment.name}
|
onCopyVariantsFrom
|
||||||
|
}
|
||||||
|
otherEnvsWithVariants={
|
||||||
|
otherEnvsWithVariants
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<PermissionButton
|
||||||
|
onClick={() =>
|
||||||
|
addVariant(environment)
|
||||||
|
}
|
||||||
|
variant="outlined"
|
||||||
|
permission={
|
||||||
|
UPDATE_FEATURE_ENVIRONMENT_VARIANTS
|
||||||
|
}
|
||||||
|
projectId={projectId}
|
||||||
|
environmentId={environment.name}
|
||||||
|
>
|
||||||
|
Add variant
|
||||||
|
</PermissionButton>
|
||||||
|
</StyledButtonContainer>
|
||||||
|
</EnvironmentVariantsCard>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
elseShow={
|
||||||
|
<EnvironmentVariantsCard
|
||||||
|
global
|
||||||
|
environment={feature.environments[0]}
|
||||||
|
searchValue={searchValue}
|
||||||
|
onEditVariant={(variant: IFeatureVariant) =>
|
||||||
|
editVariant(variant)
|
||||||
|
}
|
||||||
|
onDeleteVariant={(variant: IFeatureVariant) =>
|
||||||
|
deleteVariant(variant)
|
||||||
|
}
|
||||||
|
onUpdateStickiness={(
|
||||||
|
variants: IFeatureVariant[]
|
||||||
|
) => onUpdateStickiness(variants)}
|
||||||
>
|
>
|
||||||
Add variant
|
<StyledButtonContainer>
|
||||||
</PermissionButton>
|
<PermissionButton
|
||||||
</StyledButtonContainer>
|
onClick={() => addVariant()}
|
||||||
</EnvironmentVariantsCard>
|
variant="outlined"
|
||||||
);
|
permission={
|
||||||
})}
|
UPDATE_FEATURE_ENVIRONMENT_VARIANTS
|
||||||
|
}
|
||||||
|
projectId={projectId}
|
||||||
|
environmentId={
|
||||||
|
feature.environments[0]?.name
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Add variant
|
||||||
|
</PermissionButton>
|
||||||
|
</StyledButtonContainer>
|
||||||
|
</EnvironmentVariantsCard>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
elseShow={
|
||||||
|
<StyledAlert severity="info" data-loading>
|
||||||
|
Variants needs at least one environment.
|
||||||
|
</StyledAlert>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<EnvironmentVariantModal
|
<EnvironmentVariantModal
|
||||||
environment={selectedEnvironment}
|
global={!Boolean(selectedEnvironment)}
|
||||||
|
environment={selectedEnvironment ?? feature.environments[0]}
|
||||||
variant={selectedVariant}
|
variant={selectedVariant}
|
||||||
open={modalOpen}
|
open={modalOpen}
|
||||||
setOpen={setModalOpen}
|
setOpen={setModalOpen}
|
||||||
|
@ -205,10 +205,12 @@ const useFeatureApi = () => {
|
|||||||
const patchFeatureEnvironmentVariants = async (
|
const patchFeatureEnvironmentVariants = async (
|
||||||
projectId: string,
|
projectId: string,
|
||||||
featureId: string,
|
featureId: string,
|
||||||
environmentName: string,
|
patchPayload: Operation[],
|
||||||
patchPayload: Operation[]
|
environmentName?: string
|
||||||
) => {
|
) => {
|
||||||
const path = `api/admin/projects/${projectId}/features/${featureId}/environments/${environmentName}/variants`;
|
const path = environmentName
|
||||||
|
? `api/admin/projects/${projectId}/features/${featureId}/environments/${environmentName}/variants`
|
||||||
|
: `api/admin/projects/${projectId}/features/${featureId}/variants`;
|
||||||
const req = createRequest(path, {
|
const req = createRequest(path, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: JSON.stringify(patchPayload),
|
body: JSON.stringify(patchPayload),
|
||||||
|
Loading…
Reference in New Issue
Block a user