mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +01:00
feat: segments (#776)
* feat: create segmentation structure and list * feat: remove unused deps and change route * feat: change header style and add renderNoSegments * fix: style table header * feat: create useSegments hook * feat: add segmentApi hook * fix: ts and style errors * feat: update PR based on feedback * feat: add flag * fix: test and formating * fix: update PR based on feedback * fix: add correct permission * fix: mobile view for segments Co-authored-by: Fredrik Strand Oseberg <fredrik.no@gmail.com>
This commit is contained in:
parent
99dce16149
commit
bee9fadbc9
@ -4,4 +4,5 @@ export const E = 'E';
|
|||||||
export const RBAC = 'RBAC';
|
export const RBAC = 'RBAC';
|
||||||
export const EEA = 'EEA';
|
export const EEA = 'EEA';
|
||||||
export const RE = 'RE';
|
export const RE = 'RE';
|
||||||
|
export const SE = 'SE';
|
||||||
export const PROJECTFILTERING = false;
|
export const PROJECTFILTERING = false;
|
||||||
|
@ -311,6 +311,19 @@ Array [
|
|||||||
"title": "Addons",
|
"title": "Addons",
|
||||||
"type": "protected",
|
"type": "protected",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"component": [Function],
|
||||||
|
"flag": "SE",
|
||||||
|
"hidden": false,
|
||||||
|
"layout": "main",
|
||||||
|
"menu": Object {
|
||||||
|
"advanced": true,
|
||||||
|
"mobile": true,
|
||||||
|
},
|
||||||
|
"path": "/segments",
|
||||||
|
"title": "Segments",
|
||||||
|
"type": "protected",
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"component": [Function],
|
"component": [Function],
|
||||||
"layout": "main",
|
"layout": "main",
|
||||||
|
@ -10,7 +10,7 @@ import AdminInvoice from '../admin/invoice/InvoiceAdminPage';
|
|||||||
import AdminUsers from '../admin/users/UsersAdmin';
|
import AdminUsers from '../admin/users/UsersAdmin';
|
||||||
import { AuthSettings } from '../admin/auth/AuthSettings';
|
import { AuthSettings } from '../admin/auth/AuthSettings';
|
||||||
import Login from '../user/Login/Login';
|
import Login from '../user/Login/Login';
|
||||||
import { C, E, EEA, P, RE } from '../common/flags';
|
import { C, E, EEA, P, RE, SE } from '../common/flags';
|
||||||
import { NewUser } from '../user/NewUser/NewUser';
|
import { NewUser } from '../user/NewUser/NewUser';
|
||||||
import ResetPassword from '../user/ResetPassword/ResetPassword';
|
import ResetPassword from '../user/ResetPassword/ResetPassword';
|
||||||
import ForgottenPassword from '../user/ForgottenPassword/ForgottenPassword';
|
import ForgottenPassword from '../user/ForgottenPassword/ForgottenPassword';
|
||||||
@ -46,6 +46,7 @@ import { EventHistoryPage } from '../history/EventHistoryPage/EventHistoryPage';
|
|||||||
import { FeatureEventHistoryPage } from '../history/FeatureEventHistoryPage/FeatureEventHistoryPage';
|
import { FeatureEventHistoryPage } from '../history/FeatureEventHistoryPage/FeatureEventHistoryPage';
|
||||||
import { CreateStrategy } from '../strategies/CreateStrategy/CreateStrategy';
|
import { CreateStrategy } from '../strategies/CreateStrategy/CreateStrategy';
|
||||||
import { EditStrategy } from '../strategies/EditStrategy/EditStrategy';
|
import { EditStrategy } from '../strategies/EditStrategy/EditStrategy';
|
||||||
|
import { SegmentsList } from 'component/segments/SegmentList/SegmentList';
|
||||||
|
|
||||||
export const routes = [
|
export const routes = [
|
||||||
// Project
|
// Project
|
||||||
@ -350,6 +351,19 @@ export const routes = [
|
|||||||
menu: { mobile: true, advanced: true },
|
menu: { mobile: true, advanced: true },
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Segments
|
||||||
|
|
||||||
|
{
|
||||||
|
path: '/segments',
|
||||||
|
title: 'Segments',
|
||||||
|
component: SegmentsList,
|
||||||
|
hidden: false,
|
||||||
|
type: 'protected',
|
||||||
|
layout: 'main',
|
||||||
|
menu: { mobile: true, advanced: true },
|
||||||
|
flag: SE,
|
||||||
|
},
|
||||||
|
|
||||||
// History
|
// History
|
||||||
{
|
{
|
||||||
path: '/history/:toggleName',
|
path: '/history/:toggleName',
|
||||||
|
@ -26,3 +26,6 @@ export const DELETE_FEATURE_STRATEGY = 'DELETE_FEATURE_STRATEGY';
|
|||||||
export const UPDATE_FEATURE_ENVIRONMENT = 'UPDATE_FEATURE_ENVIRONMENT';
|
export const UPDATE_FEATURE_ENVIRONMENT = 'UPDATE_FEATURE_ENVIRONMENT';
|
||||||
export const UPDATE_FEATURE_VARIANTS = 'UPDATE_FEATURE_VARIANTS';
|
export const UPDATE_FEATURE_VARIANTS = 'UPDATE_FEATURE_VARIANTS';
|
||||||
export const MOVE_FEATURE_TOGGLE = 'MOVE_FEATURE_TOGGLE';
|
export const MOVE_FEATURE_TOGGLE = 'MOVE_FEATURE_TOGGLE';
|
||||||
|
export const CREATE_SEGMENT = 'CREATE_SEGMENT';
|
||||||
|
export const UPDATE_SEGMENT = 'UPDATE_SEGMENT';
|
||||||
|
export const DELETE_SEGMENT = 'DELETE_SEGMENT';
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
export const useStyles = makeStyles(theme => ({
|
||||||
|
deleteParagraph: {
|
||||||
|
marginTop: '2rem',
|
||||||
|
},
|
||||||
|
deleteInput: {
|
||||||
|
marginTop: '1rem',
|
||||||
|
},
|
||||||
|
}));
|
@ -0,0 +1,61 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Dialogue from 'component/common/Dialogue';
|
||||||
|
import Input from 'component/common/Input/Input';
|
||||||
|
import { useStyles } from './SegmentDeleteConfirm.styles';
|
||||||
|
import { ISegment } from 'interfaces/segment';
|
||||||
|
|
||||||
|
interface ISegmentDeleteConfirmProps {
|
||||||
|
segment: ISegment;
|
||||||
|
open: boolean;
|
||||||
|
setDeldialogue: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
handleDeleteSegment: (id: number) => Promise<void>;
|
||||||
|
confirmName: string;
|
||||||
|
setConfirmName: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SegmentDeleteConfirm = ({
|
||||||
|
segment,
|
||||||
|
open,
|
||||||
|
setDeldialogue,
|
||||||
|
handleDeleteSegment,
|
||||||
|
confirmName,
|
||||||
|
setConfirmName,
|
||||||
|
}: ISegmentDeleteConfirmProps) => {
|
||||||
|
const styles = useStyles();
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setConfirmName(e.currentTarget.value);
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setDeldialogue(false);
|
||||||
|
setConfirmName('');
|
||||||
|
};
|
||||||
|
const formId = 'delete-segment-confirmation-form';
|
||||||
|
return (
|
||||||
|
<Dialogue
|
||||||
|
title="Are you sure you want to delete this segment?"
|
||||||
|
open={open}
|
||||||
|
primaryButtonText="Delete segment"
|
||||||
|
secondaryButtonText="Cancel"
|
||||||
|
onClick={() => handleDeleteSegment(segment.id)}
|
||||||
|
disabledPrimaryButton={segment?.name !== confirmName}
|
||||||
|
onClose={handleCancel}
|
||||||
|
formId={formId}
|
||||||
|
>
|
||||||
|
<p className={styles.deleteParagraph}>
|
||||||
|
In order to delete this segment, please enter the name of the
|
||||||
|
segment in the textfield below: <strong>{segment?.name}</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form id={formId}>
|
||||||
|
<Input
|
||||||
|
autoFocus
|
||||||
|
onChange={handleChange}
|
||||||
|
value={confirmName}
|
||||||
|
label="Segment name"
|
||||||
|
className={styles.deleteInput}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</Dialogue>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,54 @@
|
|||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
export const useStyles = makeStyles(theme => ({
|
||||||
|
main: {
|
||||||
|
paddingBottom: '2rem',
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: '5rem',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: theme.fontSizes.mainHeader,
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
color: theme.palette.grey[600],
|
||||||
|
maxWidth: 515,
|
||||||
|
marginBottom: 20,
|
||||||
|
wordBreak: 'break-all',
|
||||||
|
whiteSpace: 'normal',
|
||||||
|
},
|
||||||
|
tableRow: {
|
||||||
|
background: '#F6F6FA',
|
||||||
|
borderRadius: '8px',
|
||||||
|
},
|
||||||
|
paramButton: {
|
||||||
|
color: theme.palette.primary.dark,
|
||||||
|
},
|
||||||
|
cell: {
|
||||||
|
borderBottom: 'none',
|
||||||
|
display: 'table-cell',
|
||||||
|
},
|
||||||
|
firstHeader: {
|
||||||
|
borderTopLeftRadius: '5px',
|
||||||
|
borderBottomLeftRadius: '5px',
|
||||||
|
},
|
||||||
|
lastHeader: {
|
||||||
|
borderTopRightRadius: '5px',
|
||||||
|
borderBottomRightRadius: '5px',
|
||||||
|
},
|
||||||
|
hideSM: {
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hideXS: {
|
||||||
|
[theme.breakpoints.down('xs')]: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
181
frontend/src/component/segments/SegmentList/SegmentList.tsx
Normal file
181
frontend/src/component/segments/SegmentList/SegmentList.tsx
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
import { useContext, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Typography,
|
||||||
|
} from '@material-ui/core';
|
||||||
|
import AccessContext from 'contexts/AccessContext';
|
||||||
|
import usePagination from 'hooks/usePagination';
|
||||||
|
import {
|
||||||
|
CREATE_SEGMENT,
|
||||||
|
UPDATE_SEGMENT,
|
||||||
|
} from 'component/providers/AccessProvider/permissions';
|
||||||
|
import PaginateUI from 'component/common/PaginateUI/PaginateUI';
|
||||||
|
import { SegmentListItem } from './SegmentListItem/SegmentListItem';
|
||||||
|
import { ISegment } from 'interfaces/segment';
|
||||||
|
import { useStyles } from './SegmentList.styles';
|
||||||
|
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
|
||||||
|
import { SegmentDeleteConfirm } from '../SegmentDeleteConfirm/SegmentDeleteConfirm';
|
||||||
|
import { useSegmentsApi } from 'hooks/api/actions/useSegmentsApi/useSegmentsApi';
|
||||||
|
import useToast from 'hooks/useToast';
|
||||||
|
import { formatUnknownError } from 'utils/format-unknown-error';
|
||||||
|
import { Link, useHistory } from 'react-router-dom';
|
||||||
|
import ConditionallyRender from 'component/common/ConditionallyRender';
|
||||||
|
import HeaderTitle from 'component/common/HeaderTitle';
|
||||||
|
import PageContent from 'component/common/PageContent';
|
||||||
|
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||||
|
|
||||||
|
export const SegmentsList = () => {
|
||||||
|
const history = useHistory();
|
||||||
|
const { hasAccess } = useContext(AccessContext);
|
||||||
|
const { segments, refetchSegments } = useSegments();
|
||||||
|
const { deleteSegment } = useSegmentsApi();
|
||||||
|
const { page, pages, nextPage, prevPage, setPageIndex, pageIndex } =
|
||||||
|
usePagination(segments, 10);
|
||||||
|
const [currentSegment, setCurrentSegment] = useState<ISegment>();
|
||||||
|
const [delDialog, setDelDialog] = useState(false);
|
||||||
|
const [confirmName, setConfirmName] = useState('');
|
||||||
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
|
||||||
|
const styles = useStyles();
|
||||||
|
|
||||||
|
const onDeleteSegment = async () => {
|
||||||
|
if (!currentSegment?.id) return;
|
||||||
|
try {
|
||||||
|
await deleteSegment(currentSegment?.id);
|
||||||
|
refetchSegments();
|
||||||
|
setToastData({
|
||||||
|
type: 'success',
|
||||||
|
title: 'Successfully deleted segment',
|
||||||
|
});
|
||||||
|
} catch (error: unknown) {
|
||||||
|
setToastApiError(formatUnknownError(error));
|
||||||
|
}
|
||||||
|
setDelDialog(false);
|
||||||
|
setConfirmName('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderSegments = () => {
|
||||||
|
return page.map((segment: ISegment) => {
|
||||||
|
return (
|
||||||
|
<SegmentListItem
|
||||||
|
key={segment.id}
|
||||||
|
id={segment.id}
|
||||||
|
name={segment.name}
|
||||||
|
description={segment.description}
|
||||||
|
createdAt={segment.createdAt}
|
||||||
|
createdBy={segment.createdBy}
|
||||||
|
setCurrentSegment={setCurrentSegment}
|
||||||
|
setDelDialog={setDelDialog}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderNoSegments = () => {
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<Typography className={styles.title}>
|
||||||
|
There are no segments created yet.
|
||||||
|
</Typography>
|
||||||
|
<p className={styles.subtitle}>
|
||||||
|
Segment makes it easy for you to define who should be
|
||||||
|
exposed to your feature. The segment is often a collection
|
||||||
|
of constraints and can be reused.
|
||||||
|
</p>
|
||||||
|
<Link to="/segments/create" className={styles.paramButton}>
|
||||||
|
Create your first segment
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContent
|
||||||
|
headerContent={
|
||||||
|
<HeaderTitle
|
||||||
|
title="Segments"
|
||||||
|
actions={
|
||||||
|
<PermissionButton
|
||||||
|
onClick={() => history.push('/segments/create')}
|
||||||
|
permission={CREATE_SEGMENT}
|
||||||
|
>
|
||||||
|
New Segment
|
||||||
|
</PermissionButton>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className={styles.main}>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow className={styles.tableRow}>
|
||||||
|
<TableCell
|
||||||
|
className={styles.firstHeader}
|
||||||
|
classes={{ root: styles.cell }}
|
||||||
|
>
|
||||||
|
Name
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
classes={{ root: styles.cell }}
|
||||||
|
className={styles.hideSM}
|
||||||
|
>
|
||||||
|
Description
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
classes={{ root: styles.cell }}
|
||||||
|
className={styles.hideXS}
|
||||||
|
>
|
||||||
|
Created on
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
classes={{ root: styles.cell }}
|
||||||
|
className={styles.hideXS}
|
||||||
|
>
|
||||||
|
Created By
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
align="right"
|
||||||
|
classes={{ root: styles.cell }}
|
||||||
|
className={styles.lastHeader}
|
||||||
|
>
|
||||||
|
{hasAccess(UPDATE_SEGMENT) ? 'Actions' : ''}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={segments.length > 0}
|
||||||
|
show={renderSegments()}
|
||||||
|
/>
|
||||||
|
</TableBody>
|
||||||
|
|
||||||
|
<PaginateUI
|
||||||
|
pages={pages}
|
||||||
|
pageIndex={pageIndex}
|
||||||
|
setPageIndex={setPageIndex}
|
||||||
|
nextPage={nextPage}
|
||||||
|
prevPage={prevPage}
|
||||||
|
/>
|
||||||
|
</Table>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={segments.length === 0}
|
||||||
|
show={renderNoSegments()}
|
||||||
|
/>
|
||||||
|
{currentSegment && (
|
||||||
|
<SegmentDeleteConfirm
|
||||||
|
segment={currentSegment}
|
||||||
|
open={delDialog}
|
||||||
|
setDeldialogue={setDelDialog}
|
||||||
|
handleDeleteSegment={onDeleteSegment}
|
||||||
|
confirmName={confirmName}
|
||||||
|
setConfirmName={setConfirmName}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</PageContent>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,30 @@
|
|||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
export const useStyles = makeStyles(theme => ({
|
||||||
|
tableRow: {
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.grey[200],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
leftTableCell: {
|
||||||
|
textAlign: 'left',
|
||||||
|
maxWidth: '300px',
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
color: theme.palette.grey[600],
|
||||||
|
},
|
||||||
|
descriptionCell: {
|
||||||
|
textAlign: 'left',
|
||||||
|
maxWidth: '300px',
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
createdAtCell: {
|
||||||
|
[theme.breakpoints.down('xs')]: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
textAlign: 'left',
|
||||||
|
maxWidth: '300px',
|
||||||
|
},
|
||||||
|
}));
|
@ -0,0 +1,85 @@
|
|||||||
|
import { useStyles } from './SegmentListItem.styles';
|
||||||
|
import { TableCell, TableRow, Typography } from '@material-ui/core';
|
||||||
|
import { Delete, Edit } from '@material-ui/icons';
|
||||||
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||||
|
import TimeAgo from 'react-timeago';
|
||||||
|
import { ISegment } from 'interfaces/segment';
|
||||||
|
|
||||||
|
interface ISegmentListItemProps {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
createdAt: string;
|
||||||
|
createdBy: string;
|
||||||
|
setCurrentSegment: React.Dispatch<
|
||||||
|
React.SetStateAction<ISegment | undefined>
|
||||||
|
>;
|
||||||
|
setDelDialog: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SegmentListItem = ({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
createdAt,
|
||||||
|
createdBy,
|
||||||
|
setCurrentSegment,
|
||||||
|
setDelDialog,
|
||||||
|
}: ISegmentListItemProps) => {
|
||||||
|
const styles = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow className={styles.tableRow}>
|
||||||
|
<TableCell className={styles.leftTableCell}>
|
||||||
|
<Typography variant="body2" data-loading>
|
||||||
|
{name}
|
||||||
|
</Typography>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={styles.descriptionCell}>
|
||||||
|
<Typography variant="body2" data-loading>
|
||||||
|
{description}
|
||||||
|
</Typography>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={styles.createdAtCell}>
|
||||||
|
<Typography variant="body2" data-loading>
|
||||||
|
<TimeAgo date={createdAt} live={false} />
|
||||||
|
</Typography>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={styles.createdAtCell}>
|
||||||
|
<Typography variant="body2" data-loading>
|
||||||
|
{createdBy}
|
||||||
|
</Typography>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell align="right">
|
||||||
|
<PermissionIconButton
|
||||||
|
data-loading
|
||||||
|
aria-label="Edit"
|
||||||
|
onClick={() => {}}
|
||||||
|
permission={ADMIN}
|
||||||
|
>
|
||||||
|
<Edit />
|
||||||
|
</PermissionIconButton>
|
||||||
|
<PermissionIconButton
|
||||||
|
data-loading
|
||||||
|
aria-label="Remove segment"
|
||||||
|
onClick={() => {
|
||||||
|
setCurrentSegment({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
createdAt,
|
||||||
|
createdBy,
|
||||||
|
constraints: [],
|
||||||
|
});
|
||||||
|
setDelDialog(true);
|
||||||
|
}}
|
||||||
|
permission={ADMIN}
|
||||||
|
>
|
||||||
|
<Delete />
|
||||||
|
</PermissionIconButton>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,38 @@
|
|||||||
|
import { ISegmentPayload } from 'interfaces/segment';
|
||||||
|
import useAPI from '../useApi/useApi';
|
||||||
|
|
||||||
|
export const useSegmentsApi = () => {
|
||||||
|
const { makeRequest, createRequest, errors, loading } = useAPI({
|
||||||
|
propagateErrors: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const PATH = 'api/admin/segments';
|
||||||
|
|
||||||
|
const createSegment = async (segment: ISegmentPayload, user: any) => {
|
||||||
|
const req = createRequest(PATH, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(segment),
|
||||||
|
});
|
||||||
|
|
||||||
|
return makeRequest(req.caller, req.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteSegment = async (id: number) => {
|
||||||
|
const req = createRequest(`${PATH}/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
|
||||||
|
return makeRequest(req.caller, req.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSegment = async (segment: ISegmentPayload) => {
|
||||||
|
const req = createRequest(PATH, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(segment),
|
||||||
|
});
|
||||||
|
|
||||||
|
return makeRequest(req.caller, req.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { createSegment, deleteSegment, updateSegment, errors, loading };
|
||||||
|
};
|
39
frontend/src/hooks/api/getters/useSegments/useSegments.ts
Normal file
39
frontend/src/hooks/api/getters/useSegments/useSegments.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { formatApiPath } from 'utils/format-path';
|
||||||
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
import { ISegment } from 'interfaces/segment';
|
||||||
|
|
||||||
|
const PATH = formatApiPath('api/admin/segments');
|
||||||
|
|
||||||
|
export interface UseSegmentsOutput {
|
||||||
|
segments: ISegment[];
|
||||||
|
refetchSegments: () => void;
|
||||||
|
loading: boolean;
|
||||||
|
error?: Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSegments = (options?: SWRConfiguration): UseSegmentsOutput => {
|
||||||
|
const { data, error } = useSWR<{ segments: ISegment[] }>(
|
||||||
|
PATH,
|
||||||
|
fetchSegments,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
const refetchSegments = useCallback(() => {
|
||||||
|
mutate(PATH).catch(console.warn);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
segments: data?.segments || [],
|
||||||
|
refetchSegments,
|
||||||
|
loading: !error && !data,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchSegments = () => {
|
||||||
|
return fetch(PATH, { method: 'GET' })
|
||||||
|
.then(handleErrorResponses('Segments'))
|
||||||
|
.then(res => res.json());
|
||||||
|
};
|
16
frontend/src/interfaces/segment.ts
Normal file
16
frontend/src/interfaces/segment.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { IConstraint } from './strategy';
|
||||||
|
|
||||||
|
export interface ISegment {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
createdAt: string;
|
||||||
|
createdBy: string;
|
||||||
|
constraints: IConstraint[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISegmentPayload {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
constraints: IConstraint[];
|
||||||
|
}
|
@ -27,6 +27,7 @@ export interface IFlags {
|
|||||||
EEA?: boolean;
|
EEA?: boolean;
|
||||||
OIDC?: boolean;
|
OIDC?: boolean;
|
||||||
CO?: boolean;
|
CO?: boolean;
|
||||||
|
SE?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IVersionInfo {
|
export interface IVersionInfo {
|
||||||
|
Loading…
Reference in New Issue
Block a user