diff --git a/frontend/src/component/signOnLog/SignOnLog.tsx b/frontend/src/component/loginHistory/LoginHistory.tsx similarity index 78% rename from frontend/src/component/signOnLog/SignOnLog.tsx rename to frontend/src/component/loginHistory/LoginHistory.tsx index 98b3080e15..1f2de73458 100644 --- a/frontend/src/component/signOnLog/SignOnLog.tsx +++ b/frontend/src/component/loginHistory/LoginHistory.tsx @@ -3,16 +3,16 @@ import AccessContext from 'contexts/AccessContext'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; -import { SignOnLogTable } from './SignOnLogTable/SignOnLogTable'; +import { LoginHistoryTable } from './LoginHistoryTable/LoginHistoryTable'; -export const SignOnLog = () => { +export const LoginHistory = () => { const { hasAccess } = useContext(AccessContext); return (
} + show={} elseShow={} />
diff --git a/frontend/src/component/signOnLog/SignOnLogTable/SignOnLogActionsCell/SignOnLogActionsCell.tsx b/frontend/src/component/loginHistory/LoginHistoryTable/LoginHistoryActionsCell/LoginHistoryActionsCell.tsx similarity index 86% rename from frontend/src/component/signOnLog/SignOnLogTable/SignOnLogActionsCell/SignOnLogActionsCell.tsx rename to frontend/src/component/loginHistory/LoginHistoryTable/LoginHistoryActionsCell/LoginHistoryActionsCell.tsx index 9d395670a7..52d2c9a411 100644 --- a/frontend/src/component/signOnLog/SignOnLogTable/SignOnLogActionsCell/SignOnLogActionsCell.tsx +++ b/frontend/src/component/loginHistory/LoginHistoryTable/LoginHistoryActionsCell/LoginHistoryActionsCell.tsx @@ -8,13 +8,13 @@ const StyledBox = styled(Box)(() => ({ justifyContent: 'center', })); -interface ISignOnLogActionsCellProps { +interface ILoginHistoryActionsCellProps { onDelete: (event: React.SyntheticEvent) => void; } -export const SignOnLogActionsCell = ({ +export const LoginHistoryActionsCell = ({ onDelete, -}: ISignOnLogActionsCellProps) => { +}: ILoginHistoryActionsCellProps) => { return ( >; onConfirm: () => void; } -export const SignOnLogDeleteAllDialog = ({ +export const LoginHistoryDeleteAllDialog = ({ open, setOpen, onConfirm, -}: IServiceAccountDeleteAllDialogProps) => ( +}: ILoginHistoryDeleteAllDialogProps) => ( { setOpen(false); }} > - You are about to clear the sign-on log. + You are about to clear the login history.
- This will delete all the sign-on events. + This will delete all the login events.
); diff --git a/frontend/src/component/signOnLog/SignOnLogTable/SignOnLogDeleteDialog/SignOnLogDeleteDialog.tsx b/frontend/src/component/loginHistory/LoginHistoryTable/LoginHistoryDeleteDialog/LoginHistoryDeleteDialog.tsx similarity index 67% rename from frontend/src/component/signOnLog/SignOnLogTable/SignOnLogDeleteDialog/SignOnLogDeleteDialog.tsx rename to frontend/src/component/loginHistory/LoginHistoryTable/LoginHistoryDeleteDialog/LoginHistoryDeleteDialog.tsx index b0e30da0aa..4fea356c97 100644 --- a/frontend/src/component/signOnLog/SignOnLogTable/SignOnLogDeleteDialog/SignOnLogDeleteDialog.tsx +++ b/frontend/src/component/loginHistory/LoginHistoryTable/LoginHistoryDeleteDialog/LoginHistoryDeleteDialog.tsx @@ -1,19 +1,19 @@ import { Dialogue } from 'component/common/Dialogue/Dialogue'; -import { ISignOnEvent } from 'interfaces/signOnEvent'; +import { ILoginEvent } from 'interfaces/loginEvent'; -interface IServiceAccountDeleteDialogProps { - event?: ISignOnEvent; +interface ILoginHistoryDeleteDialogProps { + event?: ILoginEvent; open: boolean; setOpen: React.Dispatch>; - onConfirm: (event: ISignOnEvent) => void; + onConfirm: (event: ILoginEvent) => void; } -export const SignOnLogDeleteDialog = ({ +export const LoginHistoryDeleteDialog = ({ event, open, setOpen, onConfirm, -}: IServiceAccountDeleteDialogProps) => ( +}: ILoginHistoryDeleteDialogProps) => ( ({ justifyContent: 'center', })); -interface ISignOnLogSuccessfulCellProps { +interface ILoginHistorySuccessfulCellProps { row: { - original: ISignOnEvent; + original: ILoginEvent; }; value: boolean; } -export const SignOnLogSuccessfulCell: VFC = ({ - row, - value, -}) => { +export const LoginHistorySuccessfulCell: VFC< + ILoginHistorySuccessfulCellProps +> = ({ row, value }) => { const { searchQuery } = useSearchHighlightContext(); if (value) diff --git a/frontend/src/component/signOnLog/SignOnLogTable/SignOnLogTable.tsx b/frontend/src/component/loginHistory/LoginHistoryTable/LoginHistoryTable.tsx similarity index 87% rename from frontend/src/component/signOnLog/SignOnLogTable/SignOnLogTable.tsx rename to frontend/src/component/loginHistory/LoginHistoryTable/LoginHistoryTable.tsx index f3ae723a26..17906b85a5 100644 --- a/frontend/src/component/signOnLog/SignOnLogTable/SignOnLogTable.tsx +++ b/frontend/src/component/loginHistory/LoginHistoryTable/LoginHistoryTable.tsx @@ -16,17 +16,17 @@ import { Search } from 'component/common/Search/Search'; import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns'; import { useSearch } from 'hooks/useSearch'; import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell'; -import { useSignOnLog } from 'hooks/api/getters/useSignOnLog/useSignOnLog'; -import { SignOnLogSuccessfulCell } from './SignOnLogSuccessfulCell/SignOnLogSuccessfulCell'; -import { ISignOnEvent } from 'interfaces/signOnEvent'; -import { SignOnLogActionsCell } from './SignOnLogActionsCell/SignOnLogActionsCell'; -import { SignOnLogDeleteDialog } from './SignOnLogDeleteDialog/SignOnLogDeleteDialog'; -import { useSignOnLogApi } from 'hooks/api/actions/useSignOnLogApi/useSignOnLogApi'; +import { useLoginHistory } from 'hooks/api/getters/useLoginHistory/useLoginHistory'; +import { LoginHistorySuccessfulCell } from './LoginHistorySuccessfulCell/LoginHistorySuccessfulCell'; +import { ILoginEvent } from 'interfaces/loginEvent'; +import { LoginHistoryActionsCell } from './LoginHistoryActionsCell/LoginHistoryActionsCell'; +import { LoginHistoryDeleteDialog } from './LoginHistoryDeleteDialog/LoginHistoryDeleteDialog'; +import { useLoginHistoryApi } from 'hooks/api/actions/useLoginHistoryApi/useLoginHistoryApi'; import { formatDateYMDHMS } from 'utils/formatDate'; import { useSearchParams } from 'react-router-dom'; import { createLocalStorage } from 'utils/createLocalStorage'; import { Delete, Download } from '@mui/icons-material'; -import { SignOnLogDeleteAllDialog } from './SignOnLogDeleteAllDialog/SignOnLogDeleteAllDialog'; +import { LoginHistoryDeleteAllDialog } from './LoginHistoryDeleteAllDialog/LoginHistoryDeleteAllDialog'; export type PageQueryType = Partial< Record<'sort' | 'order' | 'search', string> @@ -35,7 +35,7 @@ export type PageQueryType = Partial< const defaultSort: SortingRule = { id: 'created_at' }; const { value: storedParams, setValue: setStoredParams } = createLocalStorage( - 'SignOnLogTable:v1', + 'LoginHistoryTable:v1', defaultSort ); @@ -46,11 +46,11 @@ const AUTH_TYPE_LABEL: { [key: string]: string } = { google: 'Google', }; -export const SignOnLogTable = () => { +export const LoginHistoryTable = () => { const { setToastData, setToastApiError } = useToast(); - const { events, loading, refetch } = useSignOnLog(); - const { removeEvent, removeAllEvents, downloadCSV } = useSignOnLogApi(); + const { events, loading, refetch } = useLoginHistory(); + const { removeEvent, removeAllEvents, downloadCSV } = useLoginHistoryApi(); const [searchParams, setSearchParams] = useSearchParams(); const [initialState] = useState(() => ({ @@ -67,11 +67,11 @@ export const SignOnLogTable = () => { })); const [searchValue, setSearchValue] = useState(initialState.globalFilter); - const [selectedEvent, setSelectedEvent] = useState(); + const [selectedEvent, setSelectedEvent] = useState(); const [deleteOpen, setDeleteOpen] = useState(false); const [deleteAllOpen, setDeleteAllOpen] = useState(false); - const onDeleteConfirm = async (event: ISignOnEvent) => { + const onDeleteConfirm = async (event: ILoginEvent) => { try { await removeEvent(event.id); setToastData({ @@ -89,7 +89,7 @@ export const SignOnLogTable = () => { try { await removeAllEvents(); setToastData({ - title: `Log has been cleared`, + title: `History has been cleared`, type: 'success', }); refetch(); @@ -122,7 +122,7 @@ export const SignOnLogTable = () => { }, { Header: 'Authentication', - accessor: (event: ISignOnEvent) => + accessor: (event: ILoginEvent) => AUTH_TYPE_LABEL[event.auth_type] || event.auth_type, width: 150, maxWidth: 150, @@ -140,7 +140,7 @@ export const SignOnLogTable = () => { Header: 'Success', accessor: 'successful', align: 'center', - Cell: SignOnLogSuccessfulCell, + Cell: LoginHistorySuccessfulCell, filterName: 'success', filterParsing: (value: boolean) => value.toString(), }, @@ -149,7 +149,7 @@ export const SignOnLogTable = () => { id: 'Actions', align: 'center', Cell: ({ row: { original: event } }: any) => ( - { setSelectedEvent(event); setDeleteOpen(true); @@ -238,7 +238,7 @@ export const SignOnLogTable = () => { isLoading={loading} header={ { show={} /> @@ -269,7 +269,7 @@ export const SignOnLogTable = () => { { condition={searchValue?.length > 0} show={ - No sign-on events found matching “ + No login events found matching “ {searchValue} ” } elseShow={ - No sign-on events available. + No login events available. } /> } /> - - { +export const SegmentDocsValuesInfo = () => { const { segmentValuesLimit } = useSegmentLimits(); if (typeof segmentValuesLimit === 'undefined') { @@ -9,9 +9,16 @@ export const SegmentDocsValuesWarning = () => { } return ( - - Segments is an experimental feature, currently limited to at most{' '} - {segmentValuesLimit} values. + + A segment can have{' '} + + at most {segmentValuesLimit} across all of its contraints + + . ); }; @@ -25,8 +32,15 @@ export const SegmentDocsValuesError = (props: { values: number }) => { return ( - Segments are limited to at most {segmentValuesLimit} values. This - segment currently has {props.values}{' '} + A segment can have{' '} + + at most {segmentValuesLimit} across all of its contraints + + . This segment has {props.values}{' '} {props.values === 1 ? 'value' : 'values'}. ); @@ -41,8 +55,8 @@ export const SegmentDocsStrategyWarning = () => { return ( - Strategies are limited to {strategySegmentsLimit} segments.{' '} - + You can't apply more than {strategySegmentsLimit} segments to a + strategy. ); }; @@ -50,16 +64,14 @@ export const SegmentDocsStrategyWarning = () => { const SegmentLimitsLink = () => { return ( <> - Please{' '} - get in touch + Get in touch {' '} - if you would like this limit increased. + if you'd like to increase this limit. ); }; diff --git a/frontend/src/component/segments/SegmentFormStepTwo.tsx b/frontend/src/component/segments/SegmentFormStepTwo.tsx index 7921bc5d11..4c13cf7ac4 100644 --- a/frontend/src/component/segments/SegmentFormStepTwo.tsx +++ b/frontend/src/component/segments/SegmentFormStepTwo.tsx @@ -23,7 +23,7 @@ import { IAutocompleteBoxOption, } from 'component/common/AutocompleteBox/AutocompleteBox'; import { - SegmentDocsValuesWarning, + SegmentDocsValuesInfo, SegmentDocsValuesError, } from 'component/segments/SegmentDocs'; import { useSegmentValuesCount } from 'component/segments/hooks/useSegmentValuesCount'; @@ -43,7 +43,7 @@ const StyledForm = styled('div')(({ theme }) => ({ height: '100%', })); -const StyledWarning = styled('div')(({ theme }) => ({ +const StyledInfo = styled('div')(({ theme }) => ({ marginBottom: '1.5rem', })); @@ -130,9 +130,9 @@ export const SegmentFormStepTwo: React.FC = ({ return ( <> - - - + + +
Select the context fields you want to include in the diff --git a/frontend/src/hooks/api/actions/useSignOnLogApi/useSignOnLogApi.ts b/frontend/src/hooks/api/actions/useLoginHistoryApi/useLoginHistoryApi.ts similarity index 89% rename from frontend/src/hooks/api/actions/useSignOnLogApi/useSignOnLogApi.ts rename to frontend/src/hooks/api/actions/useLoginHistoryApi/useLoginHistoryApi.ts index bd2078017d..38ab16bdd5 100644 --- a/frontend/src/hooks/api/actions/useSignOnLogApi/useSignOnLogApi.ts +++ b/frontend/src/hooks/api/actions/useLoginHistoryApi/useLoginHistoryApi.ts @@ -1,6 +1,6 @@ import useAPI from '../useApi/useApi'; -export const useSignOnLogApi = () => { +export const useLoginHistoryApi = () => { const { loading, makeRequest, createRequest, errors } = useAPI({ propagateErrors: true, }); @@ -8,7 +8,7 @@ export const useSignOnLogApi = () => { const downloadCSV = async () => { const requestId = 'downloadCSV'; const req = createRequest( - 'api/admin/signons', + 'api/admin/logins', { method: 'GET', responseType: 'blob', @@ -25,7 +25,7 @@ export const useSignOnLogApi = () => { const removeEvent = async (eventId: number) => { const requestId = 'removeEvent'; const req = createRequest( - `api/admin/signons/${eventId}`, + `api/admin/logins/${eventId}`, { method: 'DELETE' }, requestId ); @@ -36,7 +36,7 @@ export const useSignOnLogApi = () => { const removeAllEvents = async () => { const requestId = 'removeAllEvents'; const req = createRequest( - 'api/admin/signons', + 'api/admin/logins', { method: 'DELETE' }, requestId ); diff --git a/frontend/src/hooks/api/getters/useSignOnLog/useSignOnLog.ts b/frontend/src/hooks/api/getters/useLoginHistory/useLoginHistory.ts similarity index 68% rename from frontend/src/hooks/api/getters/useSignOnLog/useSignOnLog.ts rename to frontend/src/hooks/api/getters/useLoginHistory/useLoginHistory.ts index 812f078bdd..ab66c8060e 100644 --- a/frontend/src/hooks/api/getters/useSignOnLog/useSignOnLog.ts +++ b/frontend/src/hooks/api/getters/useLoginHistory/useLoginHistory.ts @@ -1,25 +1,25 @@ -import { ISignOnEvent } from 'interfaces/signOnEvent'; +import { ILoginEvent } from 'interfaces/loginEvent'; import { useMemo } from 'react'; import { formatApiPath } from 'utils/formatPath'; import handleErrorResponses from '../httpErrorResponseHandler'; import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR'; import useUiConfig from '../useUiConfig/useUiConfig'; -export const useSignOnLog = () => { +export const useLoginHistory = () => { const { uiConfig, isEnterprise } = useUiConfig(); - const { signOnLog } = uiConfig.flags; + const { loginHistory } = uiConfig.flags; const { data, error, mutate } = useConditionalSWR( - signOnLog && isEnterprise(), + loginHistory && isEnterprise(), { events: [] }, - formatApiPath(`api/admin/signons`), + formatApiPath(`api/admin/logins`), fetcher ); return useMemo( () => ({ - events: (data?.events ?? []) as ISignOnEvent[], + events: (data?.events ?? []) as ILoginEvent[], loading: !error && !data, refetch: () => mutate(), error, @@ -30,6 +30,6 @@ export const useSignOnLog = () => { const fetcher = (path: string) => { return fetch(path) - .then(handleErrorResponses('Sign-On Log')) + .then(handleErrorResponses('Login History')) .then(res => res.json()); }; diff --git a/frontend/src/interfaces/signOnEvent.ts b/frontend/src/interfaces/loginEvent.ts similarity index 82% rename from frontend/src/interfaces/signOnEvent.ts rename to frontend/src/interfaces/loginEvent.ts index 285709ac5c..c543829b30 100644 --- a/frontend/src/interfaces/signOnEvent.ts +++ b/frontend/src/interfaces/loginEvent.ts @@ -1,4 +1,4 @@ -export interface ISignOnEvent { +export interface ILoginEvent { id: number; username: string; auth_type: string; diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts index e9f32e352c..945d3addd5 100644 --- a/frontend/src/interfaces/uiConfig.ts +++ b/frontend/src/interfaces/uiConfig.ts @@ -47,7 +47,7 @@ export interface IFlags { showProjectApiAccess?: boolean; proPlanAutoCharge?: boolean; notifications?: boolean; - signOnLog?: boolean; + loginHistory?: boolean; } export interface IVersionInfo { diff --git a/package.json b/package.json index d27b9a8fdc..5bcbfe98cd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "unleash-server", "description": "Unleash is an enterprise ready feature toggles service. It provides different strategies for handling feature toggles.", - "version": "4.22.0-beta.33", + "version": "4.22.0-beta.35", "keywords": [ "unleash", "feature toggle", diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap index ccd38d593a..2c650e6b8a 100644 --- a/src/lib/__snapshots__/create-config.test.ts.snap +++ b/src/lib/__snapshots__/create-config.test.ts.snap @@ -74,6 +74,7 @@ exports[`should create default config 1`] = ` "embedProxy": true, "embedProxyFrontend": true, "featuresExportImport": false, + "loginHistory": false, "maintenanceMode": false, "messageBanner": false, "newProjectOverview": false, @@ -83,7 +84,6 @@ exports[`should create default config 1`] = ` "proxyReturnAllToggles": false, "responseTimeWithAppNameKillSwitch": false, "showProjectApiAccess": false, - "signOnLog": false, "strictSchemaValidation": false, }, }, @@ -96,6 +96,7 @@ exports[`should create default config 1`] = ` "embedProxy": true, "embedProxyFrontend": true, "featuresExportImport": false, + "loginHistory": false, "maintenanceMode": false, "messageBanner": false, "newProjectOverview": false, @@ -105,7 +106,6 @@ exports[`should create default config 1`] = ` "proxyReturnAllToggles": false, "responseTimeWithAppNameKillSwitch": false, "showProjectApiAccess": false, - "signOnLog": false, "strictSchemaValidation": false, }, "externalResolver": { diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts index 87addfaa88..2bafbb3fc1 100644 --- a/src/lib/types/experimental.ts +++ b/src/lib/types/experimental.ts @@ -63,7 +63,7 @@ const flags = { false, ), notifications: parseEnvVarBoolean(process.env.NOTIFICATIONS, false), - signOnLog: parseEnvVarBoolean(process.env.UNLEASH_SIGN_ON_LOG, false), + loginHistory: parseEnvVarBoolean(process.env.UNLEASH_LOGIN_HISTORY, false), }; export const defaultExperimentalOptions: IExperimentalOptions = { diff --git a/src/migrations/20230302133740-rename-sign-on-log-table-to-login-history.js b/src/migrations/20230302133740-rename-sign-on-log-table-to-login-history.js new file mode 100644 index 0000000000..dc061955d9 --- /dev/null +++ b/src/migrations/20230302133740-rename-sign-on-log-table-to-login-history.js @@ -0,0 +1,20 @@ +exports.up = function (db, cb) { + db.runSql(`ALTER TABLE sign_on_log RENAME TO login_history`, cb); + db.runSql(`DELETE FROM settings WHERE name = 'sign_on_log_retention'`, cb); + db.runSql( + `INSERT INTO settings(name, content) VALUES ('login_history_retention', '{"hours": 336}')`, + cb, + ); +}; + +exports.down = function (db, cb) { + db.runSql(`ALTER TABLE login_history RENAME TO sign_on_log`, cb); + db.runSql( + `DELETE FROM settings WHERE name = 'login_history_retention'`, + cb, + ); + db.runSql( + `INSERT INTO settings(name, content) VALUES ('sign_on_log_retention', '{"hours": 336}')`, + cb, + ); +}; diff --git a/website/docs/reference/deploy/configuring-unleash.md b/website/docs/reference/deploy/configuring-unleash.md index 2c61d0629a..cb904cafc7 100644 --- a/website/docs/reference/deploy/configuring-unleash.md +++ b/website/docs/reference/deploy/configuring-unleash.md @@ -180,6 +180,20 @@ const start = async () => { start(); ``` +### Segment limits {#segments} + +:::caution + +Changing segment limits could have a negative impact on the performance of Unleash SDKs and cause network congestion. Think twice before changing these values. + +::: + +Some facets of the [segments feature](../segments.mdx) can be customized via environment variables. This lets you change the [segment limits](../segments.mdx#segment-limits) that Unleash uses. + +`UNLEASH_STRATEGY_SEGMENTS_LIMIT` controls the maximum number of segments that can be applied to a single strategy. The default is 5. + +`UNLEASH_SEGMENT_VALUES_LIMIT` controls the maximum number of values that you can assign across a segment's constraints. The default is 100. + ## Securing Unleash {#securing-unleash} You can integrate Unleash with your authentication provider (OAuth 2.0). Read more about [securing unleash](./securing-unleash.md). diff --git a/website/docs/reference/segments.mdx b/website/docs/reference/segments.mdx index 5ded1c3724..917adc535d 100644 --- a/website/docs/reference/segments.mdx +++ b/website/docs/reference/segments.mdx @@ -38,6 +38,22 @@ Segments are collections of strategy constraints. To satisfy a segment, _all_ th If an activation strategy has a segment _and_ additional constraints applied, the segment _and_ the strategies must all be satisfied. Similarly, if an activation strategy has multiple segments, then they must _must all be satisfied_. +## Segment limits + +In theory, you could create segments with a thousand constraints, each with a million values. But this wouldn't scale well, so there are limitations in place to stop you from doing this. Unleash enforces the following limits on use of segments: + +1. By default, a segment can have **at most 100 values** specified across all of its constraints. That means that if you add a constraint that uses 10 values, you will have 90 more values to use for any other constraints you add to the same segment. + +2. By default, you can apply **at most 5 segments to any one strategy**. Separate strategies (even on the same feature) do not count towards the same total, so you can have two strategies with 5 segments each. + +You **can** [configure segment limits](deploy/configuring-unleash.md#segments) with environment variables. + +### A note on large segments {#large-segments} + +Segments are just constraints, so any limitations that apply to constraints also apply to segments. + +This means that if you want to add a hundred different user IDs to one of your constraints, you are most likely better off thinking about finding another way to solve this problem. That may be using a different abstraction or finding another pattern that you can use instead. Refer to the section on [constraint limitations](../reference/strategy-constraints.md#limitations) for a more thorough explanation. + ## Creating, updating, and deleting segments Segments can be created, edited, and deleted from the segments page in the admin UI or via the API (see the [segments API documentation](/reference/api/legacy/unleash/admin/segments.mdx)). @@ -45,9 +61,3 @@ Segments can be created, edited, and deleted from the segments page in the admin A segment that is in use **cannot** be deleted. If you'd like to delete a segment that is in use, you must first remove the segment from all the activation strategies that are currently using it. ![The Segments page, listing two existing segments: "Mobile users" and "Users in the APAC region". The navigation menu with the Segments page link is opened and highlighted to provide navigation help.](/img/segments-page.png) - -### A note on large segments and limits {#large-segments} - -In theory, you could you create segments with a thousand constraints, each with a million values. But this wouldn't scale well, so there are limitations in place to stop you from doing this. Segments are just constraints, so any limitations that apply to constraints also apply to segments. - -This means that if you want to add a hundred different user IDs to one of your constraints, you are most likely better off thinking about finding another way to solve this problem. That may be using a different abstraction or finding another pattern that you can use instead. Refer to the section on [constraint limitations](../reference/strategy-constraints.md#limitations) for a more thorough explanation.