diff --git a/frontend/src/component/common/FormTemplate/FormTemplate.tsx b/frontend/src/component/common/FormTemplate/FormTemplate.tsx
index 4cf226892e..ec397b671a 100644
--- a/frontend/src/component/common/FormTemplate/FormTemplate.tsx
+++ b/frontend/src/component/common/FormTemplate/FormTemplate.tsx
@@ -34,6 +34,7 @@ const StyledContainer = styled('section', {
minHeight: modal ? '100vh' : '80vh',
borderRadius: modal ? 0 : theme.spacing(2),
width: '100%',
+ height: '100%',
display: 'flex',
margin: '0 auto',
[theme.breakpoints.down(1100)]: {
diff --git a/frontend/src/component/common/PageHeader/PageHeader.tsx b/frontend/src/component/common/PageHeader/PageHeader.tsx
index 17555e5272..1afa647e20 100644
--- a/frontend/src/component/common/PageHeader/PageHeader.tsx
+++ b/frontend/src/component/common/PageHeader/PageHeader.tsx
@@ -46,6 +46,7 @@ const StyledHeader = styled('div')(({ theme }) => ({
const StyledHeaderTitle = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.mainHeader,
fontWeight: 'normal',
+ lineHeight: theme.spacing(5),
}));
const StyledHeaderActions = styled('div')(({ theme }) => ({
diff --git a/frontend/src/component/common/PremiumFeature/PremiumFeature.tsx b/frontend/src/component/common/PremiumFeature/PremiumFeature.tsx
index f356b47f0d..f8291ce08b 100644
--- a/frontend/src/component/common/PremiumFeature/PremiumFeature.tsx
+++ b/frontend/src/component/common/PremiumFeature/PremiumFeature.tsx
@@ -63,6 +63,11 @@ const PremiumFeatures = {
url: 'https://docs.getunleash.io/reference/change-requests',
label: 'Change Requests',
},
+ segments: {
+ plan: FeaturePlan.PRO,
+ url: 'https://docs.getunleash.io/reference/segments',
+ label: 'Segments',
+ },
};
type PremiumFeatureType = keyof typeof PremiumFeatures;
diff --git a/frontend/src/component/project/Project/ProjectSettings/ProjectSegments/ProjectSegments.tsx b/frontend/src/component/project/Project/ProjectSettings/ProjectSegments/ProjectSegments.tsx
new file mode 100644
index 0000000000..30cc0c570b
--- /dev/null
+++ b/frontend/src/component/project/Project/ProjectSettings/ProjectSegments/ProjectSegments.tsx
@@ -0,0 +1,63 @@
+import { PageContent } from 'component/common/PageContent/PageContent';
+import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
+import { PageHeader } from 'component/common/PageHeader/PageHeader';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
+import { usePageTitle } from 'hooks/usePageTitle';
+import { useProjectNameOrId } from 'hooks/api/getters/useProject/useProject';
+import { SegmentTable } from 'component/segments/SegmentTable';
+import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
+import { Route, Routes, useNavigate } from 'react-router-dom';
+import { CreateSegment } from 'component/segments/CreateSegment/CreateSegment';
+import { EditSegment } from 'component/segments/EditSegment/EditSegment';
+import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
+import { GO_BACK } from 'constants/navigate';
+
+export const ProjectSegments = () => {
+ const projectId = useRequiredPathParam('projectId');
+ const projectName = useProjectNameOrId(projectId);
+ const { isOss } = useUiConfig();
+ const navigate = useNavigate();
+
+ usePageTitle(`Project segments – ${projectName}`);
+
+ if (isOss()) {
+ return (
+ }
+ sx={{ justifyContent: 'center' }}
+ >
+
+
+ );
+ }
+
+ return (
+
+ navigate(GO_BACK)}
+ label="Create segment"
+ >
+
+
+ }
+ />
+ navigate(GO_BACK)}
+ label="Edit segment"
+ >
+
+
+ }
+ />
+ } />
+
+ );
+};
diff --git a/frontend/src/component/project/Project/ProjectSettings/ProjectSettings.tsx b/frontend/src/component/project/Project/ProjectSettings/ProjectSettings.tsx
index 0bd2d1347a..9ef51c0523 100644
--- a/frontend/src/component/project/Project/ProjectSettings/ProjectSettings.tsx
+++ b/frontend/src/component/project/Project/ProjectSettings/ProjectSettings.tsx
@@ -11,12 +11,13 @@ import ProjectEnvironmentList from 'component/project/ProjectEnvironment/Project
import { ChangeRequestConfiguration } from './ChangeRequestConfiguration/ChangeRequestConfiguration';
import { ProjectApiAccess } from 'component/project/Project/ProjectSettings/ProjectApiAccess/ProjectApiAccess';
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
+import { ProjectSegments } from './ProjectSegments/ProjectSegments';
export const ProjectSettings = () => {
const location = useLocation();
const navigate = useNavigate();
const { uiConfig } = useUiConfig();
- const { showProjectApiAccess } = uiConfig.flags;
+ const { showProjectApiAccess, projectScopedSegments } = uiConfig.flags;
const tabs: ITab[] = [
{
@@ -27,6 +28,11 @@ export const ProjectSettings = () => {
id: 'access',
label: 'Access',
},
+ {
+ id: 'segments',
+ label: 'Segments',
+ hidden: !Boolean(projectScopedSegments),
+ },
{
id: 'change-requests',
label: 'Change request configuration',
@@ -60,6 +66,7 @@ export const ProjectSettings = () => {
element={}
/>
} />
+ } />
}
diff --git a/frontend/src/component/segments/CreateSegment/CreateSegment.tsx b/frontend/src/component/segments/CreateSegment/CreateSegment.tsx
index a65241b6d2..388ce66523 100644
--- a/frontend/src/component/segments/CreateSegment/CreateSegment.tsx
+++ b/frontend/src/component/segments/CreateSegment/CreateSegment.tsx
@@ -16,8 +16,10 @@ import { segmentsDocsLink } from 'component/segments/SegmentDocs';
import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount';
import { SEGMENT_CREATE_BTN_ID } from 'utils/testIds';
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
+import { useOptionalPathParam } from 'hooks/useOptionalPathParam';
export const CreateSegment = () => {
+ const projectId = useOptionalPathParam('projectId');
const { uiConfig } = useUiConfig();
const { setToastData, setToastApiError } = useToast();
const { showFeedbackCES } = useContext(feedbackCESContext);
@@ -37,7 +39,7 @@ export const CreateSegment = () => {
getSegmentPayload,
errors,
clearErrors,
- } = useSegmentForm();
+ } = useSegmentForm('', '', projectId);
const hasValidConstraints = useConstraintsValidation(constraints);
const { segmentValuesLimit } = useSegmentLimits();
@@ -62,7 +64,11 @@ export const CreateSegment = () => {
try {
await createSegment(getSegmentPayload());
await refetchSegments();
- navigate('/segments/');
+ if (projectId) {
+ navigate(`/projects/${projectId}/settings/segments/`);
+ } else {
+ navigate('/segments/');
+ }
setToastData({
title: 'Segment created',
confetti: true,
diff --git a/frontend/src/component/segments/CreateSegmentButton/CreateSegmentButton.tsx b/frontend/src/component/segments/CreateSegmentButton/CreateSegmentButton.tsx
index 192a44fda8..57c885a3cd 100644
--- a/frontend/src/component/segments/CreateSegmentButton/CreateSegmentButton.tsx
+++ b/frontend/src/component/segments/CreateSegmentButton/CreateSegmentButton.tsx
@@ -2,13 +2,21 @@ import { CREATE_SEGMENT } from 'component/providers/AccessProvider/permissions';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import { NAVIGATE_TO_CREATE_SEGMENT } from 'utils/testIds';
import { useNavigate } from 'react-router-dom';
+import { useOptionalPathParam } from 'hooks/useOptionalPathParam';
export const CreateSegmentButton = () => {
+ const projectId = useOptionalPathParam('projectId');
const navigate = useNavigate();
return (
navigate('/segments/create')}
+ onClick={() => {
+ if (projectId) {
+ navigate(`/projects/${projectId}/settings/segments/create`);
+ } else {
+ navigate('/segments/create');
+ }
+ }}
permission={CREATE_SEGMENT}
data-testid={NAVIGATE_TO_CREATE_SEGMENT}
>
diff --git a/frontend/src/component/segments/EditSegment/EditSegment.tsx b/frontend/src/component/segments/EditSegment/EditSegment.tsx
index dbf440470b..68bcdaea1f 100644
--- a/frontend/src/component/segments/EditSegment/EditSegment.tsx
+++ b/frontend/src/component/segments/EditSegment/EditSegment.tsx
@@ -18,8 +18,10 @@ import { segmentsDocsLink } from 'component/segments/SegmentDocs';
import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount';
import { SEGMENT_SAVE_BTN_ID } from 'utils/testIds';
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
+import { useOptionalPathParam } from 'hooks/useOptionalPathParam';
export const EditSegment = () => {
+ const projectId = useOptionalPathParam('projectId');
const segmentId = useRequiredPathParam('segmentId');
const { segment } = useSegment(Number(segmentId));
const { uiConfig } = useUiConfig();
@@ -71,7 +73,11 @@ export const EditSegment = () => {
try {
await updateSegment(segment.id, getSegmentPayload());
await refetchSegments();
- navigate('/segments/');
+ if (projectId) {
+ navigate(`/projects/${projectId}/settings/segments/`);
+ } else {
+ navigate('/segments/');
+ }
setToastData({
title: 'Segment updated',
type: 'success',
diff --git a/frontend/src/component/segments/EditSegmentButton/EditSegmentButton.tsx b/frontend/src/component/segments/EditSegmentButton/EditSegmentButton.tsx
index 98a2cb822e..f1eb10a55a 100644
--- a/frontend/src/component/segments/EditSegmentButton/EditSegmentButton.tsx
+++ b/frontend/src/component/segments/EditSegmentButton/EditSegmentButton.tsx
@@ -3,17 +3,27 @@ import { Edit } from '@mui/icons-material';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import { UPDATE_SEGMENT } from 'component/providers/AccessProvider/permissions';
import { useNavigate } from 'react-router-dom';
+import { useOptionalPathParam } from 'hooks/useOptionalPathParam';
interface IEditSegmentButtonProps {
segment: ISegment;
}
export const EditSegmentButton = ({ segment }: IEditSegmentButtonProps) => {
+ const projectId = useOptionalPathParam('projectId');
const navigate = useNavigate();
return (
navigate(`/segments/edit/${segment.id}`)}
+ onClick={() => {
+ if (projectId) {
+ navigate(
+ `/projects/${projectId}/settings/segments/edit/${segment.id}`
+ );
+ } else {
+ navigate(`/segments/edit/${segment.id}`);
+ }
+ }}
permission={UPDATE_SEGMENT}
tooltipProps={{ title: 'Edit segment' }}
>
diff --git a/frontend/src/component/segments/SegmentFormStepOne.tsx b/frontend/src/component/segments/SegmentFormStepOne.tsx
index a5fd0507e3..4c41bdc170 100644
--- a/frontend/src/component/segments/SegmentFormStepOne.tsx
+++ b/frontend/src/component/segments/SegmentFormStepOne.tsx
@@ -11,6 +11,8 @@ import {
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useProjects from 'hooks/api/getters/useProjects/useProjects';
+import { useOptionalPathParam } from 'hooks/useOptionalPathParam';
+import { GO_BACK } from 'constants/navigate';
interface ISegmentFormPartOneProps {
name: string;
@@ -65,6 +67,7 @@ export const SegmentFormStepOne: React.FC = ({
clearErrors,
setCurrentStep,
}) => {
+ const projectId = useOptionalPathParam('projectId');
const { uiConfig } = useUiConfig();
const navigate = useNavigate();
const { projects } = useProjects();
@@ -105,7 +108,10 @@ export const SegmentFormStepOne: React.FC = ({
data-testid={SEGMENT_DESC_ID}
/>
@@ -141,7 +147,7 @@ export const SegmentFormStepOne: React.FC = ({
{
- navigate('/segments');
+ navigate(GO_BACK);
}}
>
Cancel
diff --git a/frontend/src/component/segments/SegmentFormStepTwo.tsx b/frontend/src/component/segments/SegmentFormStepTwo.tsx
index c52cc32fea..9e35a8e4c9 100644
--- a/frontend/src/component/segments/SegmentFormStepTwo.tsx
+++ b/frontend/src/component/segments/SegmentFormStepTwo.tsx
@@ -29,6 +29,7 @@ import {
import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount';
import AccessContext from 'contexts/AccessContext';
import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentLimits';
+import { GO_BACK } from 'constants/navigate';
interface ISegmentFormPartTwoProps {
constraints: IConstraint[];
@@ -214,7 +215,7 @@ export const SegmentFormStepTwo: React.FC = ({
{
- navigate('/segments');
+ navigate(GO_BACK);
}}
>
Cancel
diff --git a/frontend/src/component/segments/SegmentTable.tsx b/frontend/src/component/segments/SegmentTable.tsx
index db1404b334..c312002376 100644
--- a/frontend/src/component/segments/SegmentTable.tsx
+++ b/frontend/src/component/segments/SegmentTable.tsx
@@ -28,8 +28,10 @@ import { Search } from 'component/common/Search/Search';
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
+import { useOptionalPathParam } from 'hooks/useOptionalPathParam';
export const SegmentTable = () => {
+ const projectId = useOptionalPathParam('projectId');
const { segments, loading } = useSegments();
const { uiConfig } = useUiConfig();
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
@@ -39,17 +41,22 @@ export const SegmentTable = () => {
});
const data = useMemo(() => {
- return (
- segments ??
- Array(5).fill({
+ if (!segments) {
+ return Array(5).fill({
name: 'Segment name',
description: 'Segment descripton',
createdAt: new Date().toISOString(),
createdBy: 'user',
projectId: 'Project',
- })
- );
- }, [segments]);
+ });
+ }
+
+ if (projectId) {
+ return segments.filter(({ project }) => project === projectId);
+ }
+
+ return segments;
+ }, [segments, projectId]);
const {
getTableProps,
@@ -85,7 +92,9 @@ export const SegmentTable = () => {
columns: ['createdAt', 'createdBy'],
},
{
- condition: !Boolean(uiConfig.flags.projectScopedSegments),
+ condition:
+ Boolean(projectId) ||
+ !Boolean(uiConfig.flags.projectScopedSegments),
columns: ['project'],
},
],
diff --git a/frontend/src/component/tags/TagTypeList/__tests__/__snapshots__/TagTypeList.test.tsx.snap b/frontend/src/component/tags/TagTypeList/__tests__/__snapshots__/TagTypeList.test.tsx.snap
index 540bd86bec..7c7a02e385 100644
--- a/frontend/src/component/tags/TagTypeList/__tests__/__snapshots__/TagTypeList.test.tsx.snap
+++ b/frontend/src/component/tags/TagTypeList/__tests__/__snapshots__/TagTypeList.test.tsx.snap
@@ -23,7 +23,7 @@ exports[`renders an empty list correctly 1`] = `
data-loading={true}
>
Tag types (5)