mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-23 00:22:19 +01:00
feat: add initial setup for tabs (#5586)
This PR sets up the initial tab structure for the new strategy form
This commit is contained in:
parent
d11aedc12f
commit
9dbb7ea9a9
@ -22,6 +22,9 @@ import {
|
||||
} from 'component/changeRequest/changeRequest.types';
|
||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { NewFeatureStrategyForm } from 'component/feature/FeatureStrategy/FeatureStrategyForm/NewFeatureStrategyForm';
|
||||
|
||||
interface IEditChangeProps {
|
||||
change: IChangeRequestAddStrategy | IChangeRequestUpdateStrategy;
|
||||
@ -44,6 +47,8 @@ export const EditChange = ({
|
||||
}: IEditChangeProps) => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const { editChange } = useChangeRequestApi();
|
||||
const [tab, setTab] = useState(0);
|
||||
const newStrategyConfiguration = useUiFlag('newStrategyConfiguration');
|
||||
|
||||
const [strategy, setStrategy] = useState<Partial<IFeatureStrategy>>(
|
||||
change.payload,
|
||||
@ -146,6 +151,30 @@ export const EditChange = ({
|
||||
)
|
||||
}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={newStrategyConfiguration}
|
||||
show={
|
||||
<NewFeatureStrategyForm
|
||||
projectId={projectId}
|
||||
feature={data}
|
||||
strategy={strategy}
|
||||
setStrategy={setStrategy}
|
||||
segments={segments}
|
||||
setSegments={setSegments}
|
||||
environmentId={environment}
|
||||
onSubmit={onInternalSubmit}
|
||||
onCancel={onClose}
|
||||
loading={false}
|
||||
permission={UPDATE_FEATURE_STRATEGY}
|
||||
errors={errors}
|
||||
isChangeRequest={isChangeRequestConfigured(
|
||||
environment,
|
||||
)}
|
||||
tab={tab}
|
||||
setTab={setTab}
|
||||
/>
|
||||
}
|
||||
elseShow={
|
||||
<FeatureStrategyForm
|
||||
projectId={projectId}
|
||||
feature={data}
|
||||
@ -159,8 +188,13 @@ export const EditChange = ({
|
||||
loading={false}
|
||||
permission={UPDATE_FEATURE_STRATEGY}
|
||||
errors={errors}
|
||||
isChangeRequest={isChangeRequestConfigured(environment)}
|
||||
isChangeRequest={isChangeRequestConfigured(
|
||||
environment,
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
{staleDataNotification}
|
||||
</FormTemplate>
|
||||
</SidebarModal>
|
||||
|
@ -0,0 +1,349 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Alert, Button, styled, Tabs, Tab } from '@mui/material';
|
||||
import {
|
||||
IFeatureStrategy,
|
||||
IFeatureStrategyParameters,
|
||||
IStrategyParameter,
|
||||
} from 'interfaces/strategy';
|
||||
import { FeatureStrategyType } from '../FeatureStrategyType/FeatureStrategyType';
|
||||
import { FeatureStrategyEnabled } from './FeatureStrategyEnabled/FeatureStrategyEnabled';
|
||||
import { FeatureStrategyConstraints } from '../FeatureStrategyConstraints/FeatureStrategyConstraints';
|
||||
import { IFeatureToggle } from 'interfaces/featureToggle';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { STRATEGY_FORM_SUBMIT_ID } from 'utils/testIds';
|
||||
import { useConstraintsValidation } from 'hooks/api/getters/useConstraintsValidation/useConstraintsValidation';
|
||||
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||
import { FeatureStrategySegment } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment';
|
||||
import { ISegment } from 'interfaces/segment';
|
||||
import { IFormErrors } from 'hooks/useFormErrors';
|
||||
import { validateParameterValue } from 'utils/validateParameterValue';
|
||||
import { useStrategy } from 'hooks/api/getters/useStrategy/useStrategy';
|
||||
import { FeatureStrategyChangeRequestAlert } from './FeatureStrategyChangeRequestAlert/FeatureStrategyChangeRequestAlert';
|
||||
import {
|
||||
FeatureStrategyProdGuard,
|
||||
useFeatureStrategyProdGuard,
|
||||
} from '../FeatureStrategyProdGuard/FeatureStrategyProdGuard';
|
||||
import { formatFeaturePath } from '../FeatureStrategyEdit/FeatureStrategyEdit';
|
||||
import { useChangeRequestInReviewWarning } from 'hooks/useChangeRequestInReviewWarning';
|
||||
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
|
||||
import { useHasProjectEnvironmentAccess } from 'hooks/useHasAccess';
|
||||
import { FeatureStrategyTitle } from './FeatureStrategyTitle/FeatureStrategyTitle';
|
||||
import { FeatureStrategyEnabledDisabled } from './FeatureStrategyEnabledDisabled/FeatureStrategyEnabledDisabled';
|
||||
import { StrategyVariants } from 'component/feature/StrategyTypes/StrategyVariants';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
|
||||
interface IFeatureStrategyFormProps {
|
||||
feature: IFeatureToggle;
|
||||
projectId: string;
|
||||
environmentId: string;
|
||||
permission: string;
|
||||
onSubmit: () => void;
|
||||
onCancel?: () => void;
|
||||
loading: boolean;
|
||||
isChangeRequest?: boolean;
|
||||
strategy: Partial<IFeatureStrategy>;
|
||||
setStrategy: React.Dispatch<
|
||||
React.SetStateAction<Partial<IFeatureStrategy>>
|
||||
>;
|
||||
segments: ISegment[];
|
||||
setSegments: React.Dispatch<React.SetStateAction<ISegment[]>>;
|
||||
errors: IFormErrors;
|
||||
tab: number;
|
||||
setTab: React.Dispatch<React.SetStateAction<number>>;
|
||||
}
|
||||
|
||||
const StyledForm = styled('form')(({ theme }) => ({
|
||||
display: 'grid',
|
||||
gap: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const StyledHr = styled('hr')(({ theme }) => ({
|
||||
width: '100%',
|
||||
height: '1px',
|
||||
margin: theme.spacing(2, 0),
|
||||
border: 'none',
|
||||
background: theme.palette.background.elevation2,
|
||||
}));
|
||||
|
||||
const StyledButtons = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'end',
|
||||
gap: theme.spacing(2),
|
||||
paddingBottom: theme.spacing(10),
|
||||
}));
|
||||
|
||||
export const NewFeatureStrategyForm = ({
|
||||
projectId,
|
||||
feature,
|
||||
environmentId,
|
||||
permission,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
loading,
|
||||
strategy,
|
||||
setStrategy,
|
||||
segments,
|
||||
setSegments,
|
||||
errors,
|
||||
isChangeRequest,
|
||||
tab,
|
||||
setTab,
|
||||
}: IFeatureStrategyFormProps) => {
|
||||
const { trackEvent } = usePlausibleTracker();
|
||||
const [showProdGuard, setShowProdGuard] = useState(false);
|
||||
const hasValidConstraints = useConstraintsValidation(strategy.constraints);
|
||||
const enableProdGuard = useFeatureStrategyProdGuard(feature, environmentId);
|
||||
const access = useHasProjectEnvironmentAccess(
|
||||
permission,
|
||||
projectId,
|
||||
environmentId,
|
||||
);
|
||||
const { strategyDefinition } = useStrategy(strategy?.name);
|
||||
|
||||
const { data } = usePendingChangeRequests(feature.project);
|
||||
const { changeRequestInReviewOrApproved, alert } =
|
||||
useChangeRequestInReviewWarning(data);
|
||||
|
||||
const hasChangeRequestInReviewForEnvironment =
|
||||
changeRequestInReviewOrApproved(environmentId || '');
|
||||
|
||||
const changeRequestButtonText = hasChangeRequestInReviewForEnvironment
|
||||
? 'Add to existing change request'
|
||||
: 'Add change to draft';
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const {
|
||||
uiConfig,
|
||||
error: uiConfigError,
|
||||
loading: uiConfigLoading,
|
||||
} = useUiConfig();
|
||||
|
||||
if (uiConfigError) {
|
||||
throw uiConfigError;
|
||||
}
|
||||
|
||||
if (uiConfigLoading || !strategyDefinition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const findParameterDefinition = (name: string): IStrategyParameter => {
|
||||
return strategyDefinition.parameters.find((parameterDefinition) => {
|
||||
return parameterDefinition.name === name;
|
||||
})!;
|
||||
};
|
||||
|
||||
const validateParameter = (
|
||||
name: string,
|
||||
value: IFeatureStrategyParameters[string],
|
||||
): boolean => {
|
||||
const parameterValueError = validateParameterValue(
|
||||
findParameterDefinition(name),
|
||||
value,
|
||||
);
|
||||
if (parameterValueError) {
|
||||
errors.setFormError(name, parameterValueError);
|
||||
return false;
|
||||
} else {
|
||||
errors.removeFormError(name);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const validateAllParameters = (): boolean => {
|
||||
return strategyDefinition.parameters
|
||||
.map((parameter) => parameter.name)
|
||||
.map((name) => validateParameter(name, strategy.parameters?.[name]))
|
||||
.every(Boolean);
|
||||
};
|
||||
|
||||
const onDefaultCancel = () => {
|
||||
navigate(formatFeaturePath(feature.project, feature.name));
|
||||
};
|
||||
|
||||
const onSubmitWithValidation = async (event: React.FormEvent) => {
|
||||
if (Array.isArray(strategy.variants) && strategy.variants?.length > 0) {
|
||||
trackEvent('strategy-variants', {
|
||||
props: {
|
||||
eventType: 'submitted',
|
||||
},
|
||||
});
|
||||
}
|
||||
event.preventDefault();
|
||||
if (!validateAllParameters()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (enableProdGuard && !isChangeRequest) {
|
||||
setShowProdGuard(true);
|
||||
} else {
|
||||
onSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
|
||||
setTab(newValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledForm onSubmit={onSubmitWithValidation}>
|
||||
<Tabs value={tab} onChange={handleChange}>
|
||||
<Tab label='General' />
|
||||
<Tab label='Targeting' />
|
||||
<Tab label='Variants' />
|
||||
</Tabs>
|
||||
<ConditionallyRender
|
||||
condition={tab === 0}
|
||||
show={
|
||||
<>
|
||||
<ConditionallyRender
|
||||
condition={hasChangeRequestInReviewForEnvironment}
|
||||
show={alert}
|
||||
elseShow={
|
||||
<ConditionallyRender
|
||||
condition={Boolean(isChangeRequest)}
|
||||
show={
|
||||
<FeatureStrategyChangeRequestAlert
|
||||
environment={environmentId}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<FeatureStrategyEnabled
|
||||
projectId={feature.project}
|
||||
featureId={feature.name}
|
||||
environmentId={environmentId}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(isChangeRequest)}
|
||||
show={
|
||||
<Alert severity='success'>
|
||||
This feature toggle is currently enabled
|
||||
in the <strong>{environmentId}</strong>{' '}
|
||||
environment. Any changes made here will
|
||||
be available to users as soon as these
|
||||
changes are approved and applied.
|
||||
</Alert>
|
||||
}
|
||||
elseShow={
|
||||
<Alert severity='success'>
|
||||
This feature toggle is currently enabled
|
||||
in the <strong>{environmentId}</strong>{' '}
|
||||
environment. Any changes made here will
|
||||
be available to users as soon as you hit{' '}
|
||||
<strong>save</strong>.
|
||||
</Alert>
|
||||
}
|
||||
/>
|
||||
</FeatureStrategyEnabled>
|
||||
<StyledHr />
|
||||
<FeatureStrategyTitle
|
||||
title={strategy.title || ''}
|
||||
setTitle={(title) => {
|
||||
setStrategy((prev) => ({
|
||||
...prev,
|
||||
title,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
<FeatureStrategyType
|
||||
strategy={strategy}
|
||||
strategyDefinition={strategyDefinition}
|
||||
setStrategy={setStrategy}
|
||||
validateParameter={validateParameter}
|
||||
errors={errors}
|
||||
hasAccess={access}
|
||||
/>
|
||||
<FeatureStrategyEnabledDisabled
|
||||
enabled={!strategy?.disabled}
|
||||
onToggleEnabled={() =>
|
||||
setStrategy((strategyState) => ({
|
||||
...strategyState,
|
||||
disabled: !strategyState.disabled,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
<ConditionallyRender
|
||||
condition={tab === 1}
|
||||
show={
|
||||
<>
|
||||
<FeatureStrategySegment
|
||||
segments={segments}
|
||||
setSegments={setSegments}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<FeatureStrategyConstraints
|
||||
projectId={feature.project}
|
||||
environmentId={environmentId}
|
||||
strategy={strategy}
|
||||
setStrategy={setStrategy}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
<ConditionallyRender
|
||||
condition={tab === 2}
|
||||
show={
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
strategy.parameters != null &&
|
||||
'stickiness' in strategy.parameters
|
||||
}
|
||||
show={
|
||||
<StrategyVariants
|
||||
strategy={strategy}
|
||||
setStrategy={setStrategy}
|
||||
environment={environmentId}
|
||||
projectId={projectId}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<StyledButtons>
|
||||
<PermissionButton
|
||||
permission={permission}
|
||||
projectId={feature.project}
|
||||
environmentId={environmentId}
|
||||
variant='contained'
|
||||
color='primary'
|
||||
type='submit'
|
||||
disabled={
|
||||
loading ||
|
||||
!hasValidConstraints ||
|
||||
errors.hasFormErrors()
|
||||
}
|
||||
data-testid={STRATEGY_FORM_SUBMIT_ID}
|
||||
>
|
||||
{isChangeRequest
|
||||
? changeRequestButtonText
|
||||
: 'Save strategy'}
|
||||
</PermissionButton>
|
||||
<Button
|
||||
type='button'
|
||||
color='primary'
|
||||
onClick={onCancel ? onCancel : onDefaultCancel}
|
||||
disabled={loading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<FeatureStrategyProdGuard
|
||||
open={showProdGuard}
|
||||
onClose={() => setShowProdGuard(false)}
|
||||
onClick={onSubmit}
|
||||
loading={loading}
|
||||
label='Save strategy'
|
||||
/>
|
||||
</StyledButtons>
|
||||
</StyledForm>
|
||||
);
|
||||
};
|
@ -33,8 +33,10 @@ import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
import useQueryParams from 'hooks/useQueryParams';
|
||||
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
|
||||
import { useDefaultStrategy } from '../../../project/Project/ProjectSettings/ProjectDefaultStrategySettings/ProjectEnvironment/ProjectEnvironmentDefaultStrategy/EditDefaultStrategy';
|
||||
import { NewFeatureStrategyForm } from 'component/feature/FeatureStrategy/FeatureStrategyForm/NewFeatureStrategyForm';
|
||||
|
||||
export const NewFeatureStrategyCreate = () => {
|
||||
const [tab, setTab] = useState(0);
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const environmentId = useRequiredQueryParam('environmentId');
|
||||
@ -192,8 +194,7 @@ export const NewFeatureStrategyCreate = () => {
|
||||
)
|
||||
}
|
||||
>
|
||||
<h1>NEW CREATE FORM</h1>
|
||||
<FeatureStrategyForm
|
||||
<NewFeatureStrategyForm
|
||||
projectId={projectId}
|
||||
feature={data}
|
||||
strategy={strategy}
|
||||
@ -206,6 +207,8 @@ export const NewFeatureStrategyCreate = () => {
|
||||
permission={CREATE_FEATURE_STRATEGY}
|
||||
errors={errors}
|
||||
isChangeRequest={isChangeRequestConfigured(environmentId)}
|
||||
tab={tab}
|
||||
setTab={setTab}
|
||||
/>
|
||||
{staleDataNotification}
|
||||
</FormTemplate>
|
||||
|
@ -28,6 +28,7 @@ import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
||||
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
import { NewFeatureStrategyForm } from 'component/feature/FeatureStrategy/FeatureStrategyForm/NewFeatureStrategyForm';
|
||||
|
||||
const useTitleTracking = () => {
|
||||
const [previousTitle, setPreviousTitle] = useState<string>('');
|
||||
@ -80,6 +81,7 @@ export const NewFeatureStrategyEdit = () => {
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const environmentId = useRequiredQueryParam('environmentId');
|
||||
const strategyId = useRequiredQueryParam('strategyId');
|
||||
const [tab, setTab] = useState(0);
|
||||
|
||||
const [strategy, setStrategy] = useState<Partial<IFeatureStrategy>>({});
|
||||
const [segments, setSegments] = useState<ISegment[]>([]);
|
||||
@ -214,8 +216,7 @@ export const NewFeatureStrategyEdit = () => {
|
||||
)
|
||||
}
|
||||
>
|
||||
<h1>NEW EDIT FORM</h1>
|
||||
<FeatureStrategyForm
|
||||
<NewFeatureStrategyForm
|
||||
projectId={projectId}
|
||||
feature={data}
|
||||
strategy={strategy}
|
||||
@ -228,6 +229,8 @@ export const NewFeatureStrategyEdit = () => {
|
||||
permission={UPDATE_FEATURE_STRATEGY}
|
||||
errors={errors}
|
||||
isChangeRequest={isChangeRequestConfigured(environmentId)}
|
||||
tab={tab}
|
||||
setTab={setTab}
|
||||
/>
|
||||
{staleDataNotification}
|
||||
</FormTemplate>
|
||||
|
Loading…
Reference in New Issue
Block a user