mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-05 17:53:12 +02:00
Merge branch 'main' into docs/managing-feature-flags-in-code
This commit is contained in:
commit
b0db884fba
@ -86,7 +86,7 @@ export const ApiTokenTable = ({
|
||||
<span>
|
||||
{'No tokens available. Read '}
|
||||
<Link
|
||||
href='https://docs.getunleash.io/how-to/api'
|
||||
href='https://docs.getunleash.io/api-overview'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
|
@ -120,6 +120,7 @@ export const ChartConfigModal: FC<ChartConfigModalProps> = ({
|
||||
selectedRange={formData.selectedRange}
|
||||
selectedLabels={formData.selectedLabels}
|
||||
beginAtZero={formData.beginAtZero}
|
||||
showRate={formData.showRate}
|
||||
/>
|
||||
</StyledPreviewPanel>
|
||||
</Box>
|
||||
|
@ -21,6 +21,10 @@ const getConfigDescription = (config: ChartConfig): string => {
|
||||
|
||||
parts.push(`last ${config.selectedRange}`);
|
||||
|
||||
if (config.showRate) {
|
||||
parts.push('rate per second');
|
||||
}
|
||||
|
||||
const labelCount = Object.keys(config.selectedLabels).length;
|
||||
if (labelCount > 0) {
|
||||
parts.push(`${labelCount} filter${labelCount > 1 ? 's' : ''}`);
|
||||
@ -29,15 +33,6 @@ const getConfigDescription = (config: ChartConfig): string => {
|
||||
return parts.join(' • ');
|
||||
};
|
||||
|
||||
const StyledChartWrapper = styled(Box)({
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
'& > div': {
|
||||
height: '100% !important',
|
||||
width: '100% !important',
|
||||
},
|
||||
});
|
||||
|
||||
const StyledWidget = styled(Paper)(({ theme }) => ({
|
||||
borderRadius: `${theme.shape.borderRadiusMedium}px`,
|
||||
boxShadow: 'none',
|
||||
@ -127,17 +122,16 @@ export const ChartItem: FC<ChartItemProps> = ({ config, onEdit, onDelete }) => (
|
||||
|
||||
<StyledChartContent>
|
||||
<StyledImpactChartContainer>
|
||||
<StyledChartWrapper>
|
||||
<ImpactMetricsChart
|
||||
selectedSeries={config.selectedSeries}
|
||||
selectedRange={config.selectedRange}
|
||||
selectedLabels={config.selectedLabels}
|
||||
beginAtZero={config.beginAtZero}
|
||||
aspectRatio={1.5}
|
||||
overrideOptions={{ maintainAspectRatio: false }}
|
||||
emptyDataDescription='Send impact metrics using Unleash SDK for this series to view the chart.'
|
||||
/>
|
||||
</StyledChartWrapper>
|
||||
<ImpactMetricsChart
|
||||
selectedSeries={config.selectedSeries}
|
||||
selectedRange={config.selectedRange}
|
||||
selectedLabels={config.selectedLabels}
|
||||
beginAtZero={config.beginAtZero}
|
||||
showRate={config.showRate}
|
||||
aspectRatio={1.5}
|
||||
overrideOptions={{ maintainAspectRatio: false }}
|
||||
emptyDataDescription='Send impact metrics using Unleash SDK for this series to view the chart.'
|
||||
/>
|
||||
</StyledImpactChartContainer>
|
||||
</StyledChartContent>
|
||||
</StyledWidget>
|
||||
|
@ -9,6 +9,8 @@ import { ChartItem } from './ChartItem.tsx';
|
||||
import { GridLayoutWrapper, type GridItem } from './GridLayoutWrapper.tsx';
|
||||
import { useImpactMetricsState } from './hooks/useImpactMetricsState.ts';
|
||||
import type { ChartConfig, LayoutItem } from './types.ts';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
|
||||
const StyledEmptyState = styled(Paper)(({ theme }) => ({
|
||||
textAlign: 'center',
|
||||
@ -21,9 +23,18 @@ const StyledEmptyState = styled(Paper)(({ theme }) => ({
|
||||
export const ImpactMetrics: FC = () => {
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [editingChart, setEditingChart] = useState<ChartConfig | undefined>();
|
||||
const { setToastApiError } = useToast();
|
||||
|
||||
const { charts, layout, addChart, updateChart, deleteChart, updateLayout } =
|
||||
useImpactMetricsState();
|
||||
const {
|
||||
charts,
|
||||
layout,
|
||||
loading: settingsLoading,
|
||||
error: settingsError,
|
||||
addChart,
|
||||
updateChart,
|
||||
deleteChart,
|
||||
updateLayout,
|
||||
} = useImpactMetricsState();
|
||||
|
||||
const {
|
||||
metadata,
|
||||
@ -51,20 +62,39 @@ export const ImpactMetrics: FC = () => {
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
const handleSaveChart = (config: Omit<ChartConfig, 'id'>) => {
|
||||
if (editingChart) {
|
||||
updateChart(editingChart.id, config);
|
||||
} else {
|
||||
addChart(config);
|
||||
const handleSaveChart = async (config: Omit<ChartConfig, 'id'>) => {
|
||||
try {
|
||||
if (editingChart) {
|
||||
await updateChart(editingChart.id, config);
|
||||
} else {
|
||||
await addChart(config);
|
||||
}
|
||||
setModalOpen(false);
|
||||
} catch (error) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
setModalOpen(false);
|
||||
};
|
||||
|
||||
const handleLayoutChange = useCallback(
|
||||
(layout: any[]) => {
|
||||
updateLayout(layout as LayoutItem[]);
|
||||
async (layout: any[]) => {
|
||||
try {
|
||||
await updateLayout(layout as LayoutItem[]);
|
||||
} catch (error) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
},
|
||||
[updateLayout],
|
||||
[updateLayout, setToastApiError],
|
||||
);
|
||||
|
||||
const handleDeleteChart = useCallback(
|
||||
async (id: string) => {
|
||||
try {
|
||||
await deleteChart(id);
|
||||
} catch (error) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
},
|
||||
[deleteChart],
|
||||
);
|
||||
|
||||
const gridItems: GridItem[] = useMemo(
|
||||
@ -79,7 +109,7 @@ export const ImpactMetrics: FC = () => {
|
||||
<ChartItem
|
||||
config={config}
|
||||
onEdit={handleEditChart}
|
||||
onDelete={deleteChart}
|
||||
onDelete={handleDeleteChart}
|
||||
/>
|
||||
),
|
||||
w: existingLayout?.w ?? 6,
|
||||
@ -92,10 +122,11 @@ export const ImpactMetrics: FC = () => {
|
||||
maxH: 8,
|
||||
};
|
||||
}),
|
||||
[charts, layout, handleEditChart, deleteChart],
|
||||
[charts, layout, handleEditChart, handleDeleteChart],
|
||||
);
|
||||
|
||||
const hasError = metadataError;
|
||||
const hasError = metadataError || settingsError;
|
||||
const isLoading = metadataLoading || settingsLoading;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -111,14 +142,14 @@ export const ImpactMetrics: FC = () => {
|
||||
variant='contained'
|
||||
startIcon={<Add />}
|
||||
onClick={handleAddChart}
|
||||
disabled={metadataLoading || !!hasError}
|
||||
disabled={isLoading || !!hasError}
|
||||
>
|
||||
Add Chart
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
{charts.length === 0 && !metadataLoading && !hasError ? (
|
||||
{charts.length === 0 && !isLoading && !hasError ? (
|
||||
<StyledEmptyState>
|
||||
<Typography variant='h6' gutterBottom>
|
||||
No charts configured
|
||||
@ -135,7 +166,7 @@ export const ImpactMetrics: FC = () => {
|
||||
variant='contained'
|
||||
startIcon={<Add />}
|
||||
onClick={handleAddChart}
|
||||
disabled={metadataLoading || !!hasError}
|
||||
disabled={isLoading || !!hasError}
|
||||
>
|
||||
Add Chart
|
||||
</Button>
|
||||
@ -153,7 +184,7 @@ export const ImpactMetrics: FC = () => {
|
||||
onSave={handleSaveChart}
|
||||
initialConfig={editingChart}
|
||||
metricSeries={metricSeries}
|
||||
loading={metadataLoading}
|
||||
loading={metadataLoading || settingsLoading}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { FC, ReactNode } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { Alert } from '@mui/material';
|
||||
import { Alert, Box, Typography } from '@mui/material';
|
||||
import {
|
||||
LineChart,
|
||||
NotEnoughData,
|
||||
@ -16,11 +16,13 @@ type ImpactMetricsChartProps = {
|
||||
selectedRange: 'hour' | 'day' | 'week' | 'month';
|
||||
selectedLabels: Record<string, string[]>;
|
||||
beginAtZero: boolean;
|
||||
showRate?: boolean;
|
||||
aspectRatio?: number;
|
||||
overrideOptions?: Record<string, unknown>;
|
||||
errorTitle?: string;
|
||||
emptyDataDescription?: string;
|
||||
noSeriesPlaceholder?: ReactNode;
|
||||
isPreview?: boolean;
|
||||
};
|
||||
|
||||
export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
||||
@ -28,14 +30,16 @@ export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
||||
selectedRange,
|
||||
selectedLabels,
|
||||
beginAtZero,
|
||||
showRate,
|
||||
aspectRatio,
|
||||
overrideOptions = {},
|
||||
errorTitle = 'Failed to load impact metrics. Please check if Prometheus is configured and the feature flag is enabled.',
|
||||
errorTitle = 'Failed to load impact metrics.',
|
||||
emptyDataDescription = 'Send impact metrics using Unleash SDK and select data series to view the chart.',
|
||||
noSeriesPlaceholder,
|
||||
isPreview,
|
||||
}) => {
|
||||
const {
|
||||
data: { start, end, series: timeSeriesData },
|
||||
data: { start, end, series: timeSeriesData, debug },
|
||||
loading: dataLoading,
|
||||
error: dataError,
|
||||
} = useImpactMetricsData(
|
||||
@ -43,6 +47,7 @@ export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
||||
? {
|
||||
series: selectedSeries,
|
||||
range: selectedRange,
|
||||
showRate,
|
||||
labels:
|
||||
Object.keys(selectedLabels).length > 0
|
||||
? selectedLabels
|
||||
@ -113,13 +118,14 @@ export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
||||
y: {
|
||||
beginAtZero,
|
||||
title: {
|
||||
display: false,
|
||||
display: !!showRate,
|
||||
text: showRate ? 'Rate per second' : '',
|
||||
},
|
||||
ticks: {
|
||||
precision: 0,
|
||||
callback: (value: unknown): string | number =>
|
||||
typeof value === 'number'
|
||||
? formatLargeNumbers(value)
|
||||
? `${formatLargeNumbers(value)}${showRate ? '/s' : ''}`
|
||||
: (value as number),
|
||||
},
|
||||
},
|
||||
@ -143,13 +149,50 @@ export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasError ? <Alert severity='error'>{errorTitle}</Alert> : null}
|
||||
<LineChart
|
||||
data={notEnoughData || isLoading ? placeholderData : data}
|
||||
aspectRatio={aspectRatio}
|
||||
overrideOptions={chartOptions}
|
||||
cover={cover}
|
||||
/>
|
||||
<Box
|
||||
sx={
|
||||
!isPreview
|
||||
? {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
'& > div': {
|
||||
height: '100% !important',
|
||||
width: '100% !important',
|
||||
},
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<LineChart
|
||||
data={notEnoughData || isLoading ? placeholderData : data}
|
||||
aspectRatio={aspectRatio}
|
||||
overrideOptions={chartOptions}
|
||||
cover={
|
||||
hasError ? (
|
||||
<Alert severity='error'>{errorTitle}</Alert>
|
||||
) : (
|
||||
cover
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
{isPreview && debug?.query ? (
|
||||
<Box
|
||||
sx={(theme) => ({
|
||||
margin: theme.spacing(2),
|
||||
padding: theme.spacing(2),
|
||||
background: theme.palette.background.elevation1,
|
||||
})}
|
||||
>
|
||||
<Typography
|
||||
variant='caption'
|
||||
color='text.secondary'
|
||||
sx={{ textWrap: 'break-all' }}
|
||||
>
|
||||
<code>{debug.query}</code>
|
||||
</Typography>
|
||||
</Box>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -8,6 +8,7 @@ type ImpactMetricsChartPreviewProps = {
|
||||
selectedRange: 'hour' | 'day' | 'week' | 'month';
|
||||
selectedLabels: Record<string, string[]>;
|
||||
beginAtZero: boolean;
|
||||
showRate?: boolean;
|
||||
};
|
||||
|
||||
export const ImpactMetricsChartPreview: FC<ImpactMetricsChartPreviewProps> = ({
|
||||
@ -15,6 +16,7 @@ export const ImpactMetricsChartPreview: FC<ImpactMetricsChartPreviewProps> = ({
|
||||
selectedRange,
|
||||
selectedLabels,
|
||||
beginAtZero,
|
||||
showRate,
|
||||
}) => (
|
||||
<>
|
||||
<Typography variant='h6' color='text.secondary'>
|
||||
@ -33,6 +35,8 @@ export const ImpactMetricsChartPreview: FC<ImpactMetricsChartPreviewProps> = ({
|
||||
selectedRange={selectedRange}
|
||||
selectedLabels={selectedLabels}
|
||||
beginAtZero={beginAtZero}
|
||||
showRate={showRate}
|
||||
isPreview
|
||||
/>
|
||||
</StyledChartContainer>
|
||||
</>
|
||||
|
@ -15,6 +15,7 @@ export type ImpactMetricsControlsProps = {
|
||||
| 'setSelectedRange'
|
||||
| 'setBeginAtZero'
|
||||
| 'setSelectedLabels'
|
||||
| 'setShowRate'
|
||||
>;
|
||||
metricSeries: (ImpactMetricsSeries & { name: string })[];
|
||||
loading?: boolean;
|
||||
@ -54,16 +55,29 @@ export const ImpactMetricsControls: FC<ImpactMetricsControlsProps> = ({
|
||||
onChange={actions.setSelectedRange}
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={formData.beginAtZero}
|
||||
onChange={(e) => actions.setBeginAtZero(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label='Begin at zero'
|
||||
/>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={formData.beginAtZero}
|
||||
onChange={(e) =>
|
||||
actions.setBeginAtZero(e.target.checked)
|
||||
}
|
||||
/>
|
||||
}
|
||||
label='Begin at zero'
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={formData.showRate}
|
||||
onChange={(e) => actions.setShowRate(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label='Show rate per second'
|
||||
/>
|
||||
</Box>
|
||||
{availableLabels && (
|
||||
<LabelsFilter
|
||||
selectedLabels={formData.selectedLabels}
|
||||
|
@ -14,6 +14,7 @@ export type ChartFormState = {
|
||||
selectedSeries: string;
|
||||
selectedRange: 'hour' | 'day' | 'week' | 'month';
|
||||
beginAtZero: boolean;
|
||||
showRate: boolean;
|
||||
selectedLabels: Record<string, string[]>;
|
||||
};
|
||||
actions: {
|
||||
@ -21,6 +22,7 @@ export type ChartFormState = {
|
||||
setSelectedSeries: (series: string) => void;
|
||||
setSelectedRange: (range: 'hour' | 'day' | 'week' | 'month') => void;
|
||||
setBeginAtZero: (beginAtZero: boolean) => void;
|
||||
setShowRate: (showRate: boolean) => void;
|
||||
setSelectedLabels: (labels: Record<string, string[]>) => void;
|
||||
handleSeriesChange: (series: string) => void;
|
||||
getConfigToSave: () => Omit<ChartConfig, 'id'>;
|
||||
@ -46,6 +48,7 @@ export const useChartFormState = ({
|
||||
const [selectedLabels, setSelectedLabels] = useState<
|
||||
Record<string, string[]>
|
||||
>(initialConfig?.selectedLabels || {});
|
||||
const [showRate, setShowRate] = useState(initialConfig?.showRate || false);
|
||||
|
||||
const {
|
||||
data: { labels: currentAvailableLabels },
|
||||
@ -54,6 +57,7 @@ export const useChartFormState = ({
|
||||
? {
|
||||
series: selectedSeries,
|
||||
range: selectedRange,
|
||||
showRate,
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
@ -65,12 +69,14 @@ export const useChartFormState = ({
|
||||
setSelectedRange(initialConfig.selectedRange);
|
||||
setBeginAtZero(initialConfig.beginAtZero);
|
||||
setSelectedLabels(initialConfig.selectedLabels);
|
||||
setShowRate(initialConfig.showRate || false);
|
||||
} else if (open && !initialConfig) {
|
||||
setTitle('');
|
||||
setSelectedSeries('');
|
||||
setSelectedRange('day');
|
||||
setBeginAtZero(false);
|
||||
setSelectedLabels({});
|
||||
setShowRate(false);
|
||||
}
|
||||
}, [open, initialConfig]);
|
||||
|
||||
@ -85,6 +91,7 @@ export const useChartFormState = ({
|
||||
selectedRange,
|
||||
beginAtZero,
|
||||
selectedLabels,
|
||||
showRate,
|
||||
});
|
||||
|
||||
const isValid = selectedSeries.length > 0;
|
||||
@ -95,6 +102,7 @@ export const useChartFormState = ({
|
||||
selectedSeries,
|
||||
selectedRange,
|
||||
beginAtZero,
|
||||
showRate,
|
||||
selectedLabels,
|
||||
},
|
||||
actions: {
|
||||
@ -102,6 +110,7 @@ export const useChartFormState = ({
|
||||
setSelectedSeries,
|
||||
setSelectedRange,
|
||||
setBeginAtZero,
|
||||
setShowRate,
|
||||
setSelectedLabels,
|
||||
handleSeriesChange,
|
||||
getConfigToSave,
|
||||
|
@ -1,120 +1,332 @@
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render } from 'utils/testRenderer';
|
||||
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
||||
import { useImpactMetricsState } from './useImpactMetricsState.ts';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import { createLocalStorage } from '../../../utils/createLocalStorage.ts';
|
||||
import type { FC } from 'react';
|
||||
import type { ImpactMetricsState } from '../types.ts';
|
||||
|
||||
const TestComponent: FC = () => {
|
||||
const { charts, layout } = useImpactMetricsState();
|
||||
const server = testServerSetup();
|
||||
|
||||
const TestComponent: FC<{
|
||||
enableActions?: boolean;
|
||||
}> = ({ enableActions = false }) => {
|
||||
const {
|
||||
charts,
|
||||
layout,
|
||||
loading,
|
||||
error,
|
||||
addChart,
|
||||
updateChart,
|
||||
deleteChart,
|
||||
} = useImpactMetricsState();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span data-testid='charts-count'>{charts.length}</span>
|
||||
<span data-testid='layout-count'>{layout.length}</span>
|
||||
<span data-testid='loading'>{loading.toString()}</span>
|
||||
<span data-testid='error'>{error ? 'has-error' : 'no-error'}</span>
|
||||
|
||||
{enableActions && (
|
||||
<button
|
||||
type='button'
|
||||
data-testid='add-chart'
|
||||
onClick={() =>
|
||||
addChart({
|
||||
selectedSeries: 'test-series',
|
||||
selectedRange: 'day',
|
||||
beginAtZero: true,
|
||||
showRate: false,
|
||||
selectedLabels: {},
|
||||
title: 'Test Chart',
|
||||
})
|
||||
}
|
||||
>
|
||||
Add Chart
|
||||
</button>
|
||||
)}
|
||||
|
||||
{enableActions && charts.length > 0 && (
|
||||
<button
|
||||
type='button'
|
||||
data-testid='update-chart'
|
||||
onClick={() =>
|
||||
updateChart(charts[0].id, { title: 'Updated Chart' })
|
||||
}
|
||||
>
|
||||
Update Chart
|
||||
</button>
|
||||
)}
|
||||
|
||||
{enableActions && charts.length > 0 && (
|
||||
<button
|
||||
type='button'
|
||||
data-testid='delete-chart'
|
||||
onClick={() => deleteChart(charts[0].id)}
|
||||
>
|
||||
Delete Chart
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TestWrapper = () => (
|
||||
<Routes>
|
||||
<Route path='/impact-metrics' element={<TestComponent />} />
|
||||
</Routes>
|
||||
);
|
||||
const mockSettings: ImpactMetricsState = {
|
||||
charts: [
|
||||
{
|
||||
id: 'test-chart',
|
||||
selectedSeries: 'test-series',
|
||||
selectedRange: 'day' as const,
|
||||
beginAtZero: true,
|
||||
showRate: false,
|
||||
selectedLabels: {},
|
||||
title: 'Test Chart',
|
||||
},
|
||||
],
|
||||
layout: [
|
||||
{
|
||||
i: 'test-chart',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 6,
|
||||
h: 4,
|
||||
minW: 4,
|
||||
minH: 2,
|
||||
maxW: 12,
|
||||
maxH: 8,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const emptySettings: ImpactMetricsState = {
|
||||
charts: [],
|
||||
layout: [],
|
||||
};
|
||||
|
||||
describe('useImpactMetricsState', () => {
|
||||
beforeEach(() => {
|
||||
window.localStorage.clear();
|
||||
testServerRoute(server, '/api/admin/ui-config', {});
|
||||
});
|
||||
|
||||
it('loads state from localStorage to the URL after opening page without URL state', async () => {
|
||||
const { setValue } = createLocalStorage<ImpactMetricsState>(
|
||||
'impact-metrics-state',
|
||||
{
|
||||
charts: [],
|
||||
layout: [],
|
||||
},
|
||||
it('loads settings from API', async () => {
|
||||
testServerRoute(
|
||||
server,
|
||||
'/api/admin/impact-metrics/settings',
|
||||
mockSettings,
|
||||
);
|
||||
|
||||
setValue({
|
||||
charts: [
|
||||
{
|
||||
id: 'test-chart',
|
||||
selectedSeries: 'test-series',
|
||||
selectedRange: 'day' as const,
|
||||
beginAtZero: true,
|
||||
selectedLabels: {},
|
||||
title: 'Test Chart',
|
||||
},
|
||||
],
|
||||
layout: [
|
||||
{
|
||||
i: 'test-chart',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 6,
|
||||
h: 4,
|
||||
minW: 4,
|
||||
minH: 2,
|
||||
maxW: 12,
|
||||
maxH: 8,
|
||||
},
|
||||
],
|
||||
render(<TestComponent />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('charts-count')).toHaveTextContent('1');
|
||||
expect(screen.getByTestId('layout-count')).toHaveTextContent('1');
|
||||
expect(screen.getByTestId('loading')).toHaveTextContent('false');
|
||||
expect(screen.getByTestId('error')).toHaveTextContent('no-error');
|
||||
});
|
||||
|
||||
render(<TestWrapper />, { route: '/impact-metrics' });
|
||||
|
||||
expect(window.location.href).toContain('charts=');
|
||||
expect(window.location.href).toContain('layout=');
|
||||
});
|
||||
|
||||
it('does not modify URL when URL already contains data', async () => {
|
||||
const { setValue } = createLocalStorage<ImpactMetricsState>(
|
||||
'impact-metrics-state',
|
||||
it('handles empty settings', async () => {
|
||||
testServerRoute(
|
||||
server,
|
||||
'/api/admin/impact-metrics/settings',
|
||||
emptySettings,
|
||||
);
|
||||
|
||||
render(<TestComponent />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('charts-count')).toHaveTextContent('0');
|
||||
expect(screen.getByTestId('layout-count')).toHaveTextContent('0');
|
||||
expect(screen.getByTestId('loading')).toHaveTextContent('false');
|
||||
expect(screen.getByTestId('error')).toHaveTextContent('no-error');
|
||||
});
|
||||
});
|
||||
|
||||
it('handles API errors', async () => {
|
||||
testServerRoute(
|
||||
server,
|
||||
'/api/admin/impact-metrics/settings',
|
||||
{ message: 'Server error' },
|
||||
'get',
|
||||
500,
|
||||
);
|
||||
|
||||
render(<TestComponent />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('error')).toHaveTextContent('has-error');
|
||||
});
|
||||
});
|
||||
|
||||
it('adds a chart successfully', async () => {
|
||||
testServerRoute(
|
||||
server,
|
||||
'/api/admin/impact-metrics/settings',
|
||||
emptySettings,
|
||||
);
|
||||
|
||||
render(<TestComponent enableActions />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('charts-count')).toHaveTextContent('0');
|
||||
});
|
||||
|
||||
testServerRoute(
|
||||
server,
|
||||
'/api/admin/impact-metrics/settings',
|
||||
{
|
||||
charts: [],
|
||||
layout: [],
|
||||
charts: [
|
||||
{
|
||||
id: 'new-chart-id',
|
||||
selectedSeries: 'test-series',
|
||||
selectedRange: 'day',
|
||||
beginAtZero: true,
|
||||
showRate: false,
|
||||
selectedLabels: {},
|
||||
title: 'Test Chart',
|
||||
},
|
||||
],
|
||||
layout: [
|
||||
{
|
||||
i: 'new-chart-id',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 6,
|
||||
h: 4,
|
||||
minW: 4,
|
||||
minH: 2,
|
||||
maxW: 12,
|
||||
maxH: 8,
|
||||
},
|
||||
],
|
||||
},
|
||||
'put',
|
||||
200,
|
||||
);
|
||||
|
||||
setValue({
|
||||
charts: [
|
||||
{
|
||||
id: 'old-chart',
|
||||
selectedSeries: 'old-series',
|
||||
selectedRange: 'day' as const,
|
||||
beginAtZero: true,
|
||||
selectedLabels: {},
|
||||
title: 'Old Chart',
|
||||
},
|
||||
],
|
||||
layout: [],
|
||||
});
|
||||
|
||||
const urlCharts = btoa(
|
||||
JSON.stringify([
|
||||
{
|
||||
id: 'url-chart',
|
||||
selectedSeries: 'url-series',
|
||||
selectedRange: 'day',
|
||||
beginAtZero: true,
|
||||
selectedLabels: {},
|
||||
title: 'URL Chart',
|
||||
},
|
||||
]),
|
||||
testServerRoute(
|
||||
server,
|
||||
'/api/admin/impact-metrics/settings',
|
||||
{
|
||||
charts: [
|
||||
{
|
||||
id: 'new-chart-id',
|
||||
selectedSeries: 'test-series',
|
||||
selectedRange: 'day',
|
||||
beginAtZero: true,
|
||||
showRate: false,
|
||||
selectedLabels: {},
|
||||
title: 'Test Chart',
|
||||
},
|
||||
],
|
||||
layout: [
|
||||
{
|
||||
i: 'new-chart-id',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 6,
|
||||
h: 4,
|
||||
minW: 4,
|
||||
minH: 2,
|
||||
maxW: 12,
|
||||
maxH: 8,
|
||||
},
|
||||
],
|
||||
},
|
||||
'get',
|
||||
200,
|
||||
);
|
||||
|
||||
render(<TestWrapper />, {
|
||||
route: `/impact-metrics?charts=${encodeURIComponent(urlCharts)}`,
|
||||
const addButton = screen.getByTestId('add-chart');
|
||||
await userEvent.click(addButton);
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(screen.getByTestId('charts-count')).toHaveTextContent(
|
||||
'1',
|
||||
);
|
||||
},
|
||||
{ timeout: 5000 },
|
||||
);
|
||||
});
|
||||
|
||||
it('updates a chart successfully', async () => {
|
||||
testServerRoute(
|
||||
server,
|
||||
'/api/admin/impact-metrics/settings',
|
||||
mockSettings,
|
||||
);
|
||||
|
||||
testServerRoute(
|
||||
server,
|
||||
'/api/admin/impact-metrics/settings',
|
||||
{
|
||||
charts: [
|
||||
{
|
||||
...mockSettings.charts[0],
|
||||
title: 'Updated Chart',
|
||||
},
|
||||
],
|
||||
layout: mockSettings.layout,
|
||||
},
|
||||
'put',
|
||||
200,
|
||||
);
|
||||
|
||||
render(<TestComponent enableActions />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('charts-count')).toHaveTextContent('1');
|
||||
});
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const chartsParam = urlParams.get('charts');
|
||||
const updateButton = screen.getByTestId('update-chart');
|
||||
await userEvent.click(updateButton);
|
||||
|
||||
expect(chartsParam).toBeTruthy();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('charts-count')).toHaveTextContent('1');
|
||||
});
|
||||
});
|
||||
|
||||
const decodedCharts = JSON.parse(atob(chartsParam!));
|
||||
expect(decodedCharts[0].id).toBe('url-chart');
|
||||
expect(decodedCharts[0].id).not.toBe('old-chart');
|
||||
it('deletes a chart successfully', async () => {
|
||||
testServerRoute(
|
||||
server,
|
||||
'/api/admin/impact-metrics/settings',
|
||||
mockSettings,
|
||||
);
|
||||
|
||||
render(<TestComponent enableActions />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('charts-count')).toHaveTextContent('1');
|
||||
});
|
||||
|
||||
testServerRoute(
|
||||
server,
|
||||
'/api/admin/impact-metrics/settings',
|
||||
emptySettings,
|
||||
'put',
|
||||
200,
|
||||
);
|
||||
|
||||
testServerRoute(
|
||||
server,
|
||||
'/api/admin/impact-metrics/settings',
|
||||
emptySettings,
|
||||
'get',
|
||||
200,
|
||||
);
|
||||
|
||||
const deleteButton = screen.getByTestId('delete-chart');
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(screen.getByTestId('charts-count')).toHaveTextContent(
|
||||
'0',
|
||||
);
|
||||
},
|
||||
{ timeout: 5000 },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -1,72 +1,40 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { withDefault } from 'use-query-params';
|
||||
import { usePersistentTableState } from 'hooks/usePersistentTableState';
|
||||
import { useCallback } from 'react';
|
||||
import { useImpactMetricsSettings } from 'hooks/api/getters/useImpactMetricsSettings/useImpactMetricsSettings.js';
|
||||
import { useImpactMetricsSettingsApi } from 'hooks/api/actions/useImpactMetricsSettingsApi/useImpactMetricsSettingsApi.js';
|
||||
import type { ChartConfig, ImpactMetricsState, LayoutItem } from '../types.ts';
|
||||
|
||||
const createArrayParam = <T>() => ({
|
||||
encode: (items: T[]): string =>
|
||||
items.length > 0 ? btoa(JSON.stringify(items)) : '',
|
||||
|
||||
decode: (value: string | (string | null)[] | null | undefined): T[] => {
|
||||
if (typeof value !== 'string' || !value) return [];
|
||||
try {
|
||||
return JSON.parse(atob(value));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const ChartsParam = createArrayParam<ChartConfig>();
|
||||
const LayoutParam = createArrayParam<LayoutItem>();
|
||||
|
||||
export const useImpactMetricsState = () => {
|
||||
const stateConfig = {
|
||||
charts: withDefault(ChartsParam, []),
|
||||
layout: withDefault(LayoutParam, []),
|
||||
};
|
||||
const {
|
||||
settings,
|
||||
loading: settingsLoading,
|
||||
error: settingsError,
|
||||
refetch,
|
||||
} = useImpactMetricsSettings();
|
||||
|
||||
const [tableState, setTableState] = usePersistentTableState(
|
||||
'impact-metrics-state',
|
||||
stateConfig,
|
||||
);
|
||||
|
||||
const currentState: ImpactMetricsState = useMemo(
|
||||
() => ({
|
||||
charts: tableState.charts || [],
|
||||
layout: tableState.layout || [],
|
||||
}),
|
||||
[tableState.charts, tableState.layout],
|
||||
);
|
||||
|
||||
const updateState = useCallback(
|
||||
(newState: ImpactMetricsState) => {
|
||||
setTableState({
|
||||
charts: newState.charts,
|
||||
layout: newState.layout,
|
||||
});
|
||||
},
|
||||
[setTableState],
|
||||
);
|
||||
const {
|
||||
updateSettings,
|
||||
loading: actionLoading,
|
||||
errors: actionErrors,
|
||||
} = useImpactMetricsSettingsApi();
|
||||
|
||||
const addChart = useCallback(
|
||||
(config: Omit<ChartConfig, 'id'>) => {
|
||||
async (config: Omit<ChartConfig, 'id'>) => {
|
||||
const newChart: ChartConfig = {
|
||||
...config,
|
||||
id: `chart-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||
};
|
||||
|
||||
const maxY =
|
||||
currentState.layout.length > 0
|
||||
settings.layout.length > 0
|
||||
? Math.max(
|
||||
...currentState.layout.map((item) => item.y + item.h),
|
||||
...settings.layout.map((item) => item.y + item.h),
|
||||
)
|
||||
: 0;
|
||||
|
||||
updateState({
|
||||
charts: [...currentState.charts, newChart],
|
||||
const newState: ImpactMetricsState = {
|
||||
charts: [...settings.charts, newChart],
|
||||
layout: [
|
||||
...currentState.layout,
|
||||
...settings.layout,
|
||||
{
|
||||
i: newChart.id,
|
||||
x: 0,
|
||||
@ -79,46 +47,61 @@ export const useImpactMetricsState = () => {
|
||||
maxH: 8,
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
await updateSettings(newState);
|
||||
refetch();
|
||||
},
|
||||
[currentState.charts, currentState.layout, updateState],
|
||||
[settings, updateSettings, refetch],
|
||||
);
|
||||
|
||||
const updateChart = useCallback(
|
||||
(id: string, updates: Partial<ChartConfig>) => {
|
||||
updateState({
|
||||
charts: currentState.charts.map((chart) =>
|
||||
chart.id === id ? { ...chart, ...updates } : chart,
|
||||
),
|
||||
layout: currentState.layout,
|
||||
});
|
||||
async (id: string, updates: Partial<ChartConfig>) => {
|
||||
const updatedCharts = settings.charts.map((chart) =>
|
||||
chart.id === id ? { ...chart, ...updates } : chart,
|
||||
);
|
||||
const newState: ImpactMetricsState = {
|
||||
charts: updatedCharts,
|
||||
layout: settings.layout,
|
||||
};
|
||||
await updateSettings(newState);
|
||||
refetch();
|
||||
},
|
||||
[currentState.charts, currentState.layout, updateState],
|
||||
[settings, updateSettings, refetch],
|
||||
);
|
||||
|
||||
const deleteChart = useCallback(
|
||||
(id: string) => {
|
||||
updateState({
|
||||
charts: currentState.charts.filter((chart) => chart.id !== id),
|
||||
layout: currentState.layout.filter((item) => item.i !== id),
|
||||
});
|
||||
async (id: string) => {
|
||||
const newState: ImpactMetricsState = {
|
||||
charts: settings.charts.filter((chart) => chart.id !== id),
|
||||
layout: settings.layout.filter((item) => item.i !== id),
|
||||
};
|
||||
await updateSettings(newState);
|
||||
refetch();
|
||||
},
|
||||
[currentState.charts, currentState.layout, updateState],
|
||||
[settings, updateSettings, refetch],
|
||||
);
|
||||
|
||||
const updateLayout = useCallback(
|
||||
(newLayout: LayoutItem[]) => {
|
||||
updateState({
|
||||
charts: currentState.charts,
|
||||
async (newLayout: LayoutItem[]) => {
|
||||
const newState: ImpactMetricsState = {
|
||||
charts: settings.charts,
|
||||
layout: newLayout,
|
||||
});
|
||||
};
|
||||
await updateSettings(newState);
|
||||
refetch();
|
||||
},
|
||||
[currentState.charts, updateState],
|
||||
[settings, updateSettings, refetch],
|
||||
);
|
||||
|
||||
return {
|
||||
charts: currentState.charts || [],
|
||||
layout: currentState.layout || [],
|
||||
charts: settings.charts || [],
|
||||
layout: settings.layout || [],
|
||||
loading: settingsLoading || actionLoading,
|
||||
error:
|
||||
settingsError || Object.keys(actionErrors).length > 0
|
||||
? actionErrors
|
||||
: undefined,
|
||||
addChart,
|
||||
updateChart,
|
||||
deleteChart,
|
||||
|
@ -3,6 +3,7 @@ export type ChartConfig = {
|
||||
selectedSeries: string;
|
||||
selectedRange: 'hour' | 'day' | 'week' | 'month';
|
||||
beginAtZero: boolean;
|
||||
showRate: boolean;
|
||||
selectedLabels: Record<string, string[]>;
|
||||
title?: string;
|
||||
};
|
||||
|
@ -18,7 +18,6 @@ import { allOption } from 'component/common/ProjectSelect/ProjectSelect';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { WidgetTitle } from './components/WidgetTitle/WidgetTitle.tsx';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { useUiFlag } from 'hooks/useUiFlag.ts';
|
||||
|
||||
export interface IChartsProps {
|
||||
flagTrends: InstanceInsightsSchema['flagTrends'];
|
||||
@ -39,6 +38,7 @@ export interface IChartsProps {
|
||||
potentiallyStale: number;
|
||||
averageUsers: number;
|
||||
averageHealth?: string;
|
||||
technicalDebt?: string;
|
||||
flagsPerUser?: string;
|
||||
medianTimeToProduction?: number;
|
||||
};
|
||||
@ -105,7 +105,6 @@ export const InsightsCharts: FC<IChartsProps> = ({
|
||||
const showAllProjects = projects[0] === allOption.id;
|
||||
const isOneProjectSelected = projects.length === 1;
|
||||
const { isEnterprise } = useUiConfig();
|
||||
const healthToDebtEnabled = useUiFlag('healthToTechDebt');
|
||||
|
||||
const lastUserTrend = userTrends[userTrends.length - 1];
|
||||
const lastFlagTrend = flagTrends[flagTrends.length - 1];
|
||||
@ -186,20 +185,15 @@ export const InsightsCharts: FC<IChartsProps> = ({
|
||||
<StyledWidgetStats width={350} padding={0}>
|
||||
<HealthStats
|
||||
value={summary.averageHealth}
|
||||
technicalDebt={summary.technicalDebt}
|
||||
healthy={summary.active}
|
||||
stale={summary.stale}
|
||||
potentiallyStale={summary.potentiallyStale}
|
||||
title={
|
||||
<WidgetTitle
|
||||
title={
|
||||
healthToDebtEnabled
|
||||
? 'Technical debt'
|
||||
: 'Health'
|
||||
}
|
||||
title={'Technical debt'}
|
||||
tooltip={
|
||||
healthToDebtEnabled
|
||||
? 'Percentage of stale and potentially stale flags.'
|
||||
: 'Percentage of flags that are not stale or potentially stale.'
|
||||
'Percentage of stale and potentially stale flags.'
|
||||
}
|
||||
/>
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import type { TooltipState } from 'component/insights/components/LineChart/Chart
|
||||
import { HorizontalDistributionChart } from 'component/insights/components/HorizontalDistributionChart/HorizontalDistributionChart';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { getTechnicalDebtColor } from 'utils/getTechnicalDebtColor.ts';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
|
||||
const StyledTooltipItemContainer = styled(Paper)(({ theme }) => ({
|
||||
padding: theme.spacing(2),
|
||||
@ -19,22 +18,6 @@ const StyledItemHeader = styled(Box)(({ theme }) => ({
|
||||
alignItems: 'center',
|
||||
}));
|
||||
|
||||
const getHealthBadgeColor = (health?: number | null) => {
|
||||
if (health === undefined || health === null) {
|
||||
return 'info';
|
||||
}
|
||||
|
||||
if (health >= 75) {
|
||||
return 'success';
|
||||
}
|
||||
|
||||
if (health >= 50) {
|
||||
return 'warning';
|
||||
}
|
||||
|
||||
return 'error';
|
||||
};
|
||||
|
||||
const getTechnicalDebtBadgeColor = (technicalDebt?: number | null) => {
|
||||
if (technicalDebt === undefined || technicalDebt === null) {
|
||||
return 'info';
|
||||
@ -108,8 +91,6 @@ const Distribution = ({ stale = 0, potentiallyStale = 0, total = 0 }) => {
|
||||
export const HealthTooltip: FC<{ tooltip: TooltipState | null }> = ({
|
||||
tooltip,
|
||||
}) => {
|
||||
const healthToTechDebtEnabled = useUiFlag('healthToTechDebt');
|
||||
|
||||
const data = tooltip?.dataPoints.map((point) => {
|
||||
return {
|
||||
label: point.label,
|
||||
@ -148,9 +129,7 @@ export const HealthTooltip: FC<{ tooltip: TooltipState | null }> = ({
|
||||
color='textSecondary'
|
||||
component='span'
|
||||
>
|
||||
{healthToTechDebtEnabled
|
||||
? 'Technical debt'
|
||||
: 'Project health'}
|
||||
Technical debt
|
||||
</Typography>
|
||||
</StyledItemHeader>
|
||||
<StyledItemHeader>
|
||||
@ -163,21 +142,13 @@ export const HealthTooltip: FC<{ tooltip: TooltipState | null }> = ({
|
||||
</Typography>
|
||||
<strong>{point.title}</strong>
|
||||
</Typography>
|
||||
{healthToTechDebtEnabled ? (
|
||||
<Badge
|
||||
color={getTechnicalDebtBadgeColor(
|
||||
point.value.technicalDebt,
|
||||
)}
|
||||
>
|
||||
{point.value.technicalDebt}%
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge
|
||||
color={getHealthBadgeColor(point.value.health)}
|
||||
>
|
||||
{point.value.health}%
|
||||
</Badge>
|
||||
)}
|
||||
<Badge
|
||||
color={getTechnicalDebtBadgeColor(
|
||||
point.value.technicalDebt,
|
||||
)}
|
||||
>
|
||||
{point.value.technicalDebt}%
|
||||
</Badge>
|
||||
</StyledItemHeader>{' '}
|
||||
<Divider
|
||||
sx={(theme) => ({ margin: theme.spacing(1.5, 0) })}
|
||||
|
@ -14,7 +14,6 @@ import {
|
||||
import { useTheme } from '@mui/material';
|
||||
import type { GroupedDataByProject } from 'component/insights/hooks/useGroupedProjectTrends';
|
||||
import { usePlaceholderData } from 'component/insights/hooks/usePlaceholderData';
|
||||
import { useUiFlag } from 'hooks/useUiFlag.ts';
|
||||
|
||||
interface IProjectHealthChartProps {
|
||||
projectFlagTrends: GroupedDataByProject<
|
||||
@ -46,7 +45,6 @@ export const ProjectHealthChart: FC<IProjectHealthChartProps> = ({
|
||||
const projectsData = useProjectChartData(projectFlagTrends);
|
||||
const theme = useTheme();
|
||||
const placeholderData = usePlaceholderData();
|
||||
const healthToTechDebtEnabled = useUiFlag('healthToTechDebt');
|
||||
|
||||
const aggregateHealthData = useMemo(() => {
|
||||
const labels = Array.from(
|
||||
@ -85,18 +83,12 @@ export const ProjectHealthChart: FC<IProjectHealthChartProps> = ({
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
label: healthToTechDebtEnabled
|
||||
? 'Technical debt'
|
||||
: 'Health',
|
||||
label: 'Technical debt',
|
||||
data: weeks.map((item) => ({
|
||||
health: item.total ? calculateHealth(item) : undefined,
|
||||
...(healthToTechDebtEnabled
|
||||
? {
|
||||
technicalDebt: item.total
|
||||
? calculateTechDebt(item)
|
||||
: undefined,
|
||||
}
|
||||
: {}),
|
||||
technicalDebt: item.total
|
||||
? calculateTechDebt(item)
|
||||
: undefined,
|
||||
date: item.date,
|
||||
total: item.total,
|
||||
stale: item.stale,
|
||||
@ -132,9 +124,7 @@ export const ProjectHealthChart: FC<IProjectHealthChartProps> = ({
|
||||
? {}
|
||||
: {
|
||||
parsing: {
|
||||
yAxisKey: healthToTechDebtEnabled
|
||||
? 'technicalDebt'
|
||||
: 'health',
|
||||
yAxisKey: 'technicalDebt',
|
||||
xAxisKey: 'date',
|
||||
},
|
||||
plugins: {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import type { FC, ReactNode } from 'react';
|
||||
import { Box, Divider, Link, styled } from '@mui/material';
|
||||
import { ReactComponent as InstanceHealthIcon } from 'assets/icons/instance-health.svg';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
|
||||
interface IHealthStatsProps {
|
||||
value?: string | number;
|
||||
@ -73,8 +72,6 @@ export const HealthStats: FC<IHealthStatsProps> = ({
|
||||
potentiallyStale,
|
||||
title,
|
||||
}) => {
|
||||
const healthToDebtEnabled = useUiFlag('healthToTechDebt');
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledHeader>
|
||||
@ -84,12 +81,8 @@ export const HealthStats: FC<IHealthStatsProps> = ({
|
||||
<StyledSection>
|
||||
<StyledStatsRow>
|
||||
<StyledIcon />
|
||||
{healthToDebtEnabled ? 'Technical debt' : 'Instance health'}
|
||||
{healthToDebtEnabled ? (
|
||||
<StyledMainValue>{`${technicalDebt}%`}</StyledMainValue>
|
||||
) : (
|
||||
<StyledMainValue>{`${value || 0}%`}</StyledMainValue>
|
||||
)}
|
||||
Technical debt
|
||||
<StyledMainValue>{`${technicalDebt}%`}</StyledMainValue>
|
||||
</StyledStatsRow>
|
||||
</StyledSection>
|
||||
<Divider />
|
||||
@ -112,9 +105,7 @@ export const HealthStats: FC<IHealthStatsProps> = ({
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
{healthToDebtEnabled
|
||||
? 'What affects technical debt?'
|
||||
: 'What affects instance health?'}
|
||||
What affects technical debt?
|
||||
</Link>
|
||||
</ExplanationRow>
|
||||
</FlagsSection>
|
||||
|
@ -23,7 +23,6 @@ import {
|
||||
StyledWidgetContent,
|
||||
StyledWidgetStats,
|
||||
} from '../InsightsCharts.styles';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
|
||||
export const PerformanceInsights: FC = () => {
|
||||
const statePrefix = 'performance-';
|
||||
@ -48,8 +47,6 @@ export const PerformanceInsights: FC = () => {
|
||||
state[`${statePrefix}to`]?.values[0],
|
||||
);
|
||||
|
||||
const healthToTechDebtEnabled = useUiFlag('healthToTechDebt');
|
||||
|
||||
const projects = state[`${statePrefix}project`]?.values ?? [allOption.id];
|
||||
|
||||
const showAllProjects = projects[0] === allOption.id;
|
||||
@ -135,21 +132,12 @@ export const PerformanceInsights: FC = () => {
|
||||
stale={summary.stale}
|
||||
potentiallyStale={summary.potentiallyStale}
|
||||
title={
|
||||
healthToTechDebtEnabled ? (
|
||||
<WidgetTitle
|
||||
title='Technical debt'
|
||||
tooltip={
|
||||
'Percentage of flags that are stale or potentially stale.'
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<WidgetTitle
|
||||
title='Health'
|
||||
tooltip={
|
||||
'Percentage of flags that are not stale or potentially stale.'
|
||||
}
|
||||
/>
|
||||
)
|
||||
<WidgetTitle
|
||||
title='Technical debt'
|
||||
tooltip={
|
||||
'Percentage of flags that are stale or potentially stale.'
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</StyledWidgetStats>
|
||||
|
@ -39,13 +39,10 @@ import { ActionBox } from './ActionBox.tsx';
|
||||
import useLoading from 'hooks/useLoading';
|
||||
import { NoProjectsContactAdmin } from './NoProjectsContactAdmin.tsx';
|
||||
import { AskOwnerToAddYouToTheirProject } from './AskOwnerToAddYouToTheirProject.tsx';
|
||||
import { useUiFlag } from 'hooks/useUiFlag.ts';
|
||||
|
||||
const ActiveProjectDetails: FC<{
|
||||
project: PersonalDashboardSchemaProjectsItem;
|
||||
}> = ({ project }) => {
|
||||
const healthToTechDebtEnabled = useUiFlag('healthToTechDebt');
|
||||
|
||||
const techicalDebt = project.technicalDebt;
|
||||
return (
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
@ -67,10 +64,10 @@ const ActiveProjectDetails: FC<{
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Typography variant='subtitle2' color='primary'>
|
||||
{healthToTechDebtEnabled ? techicalDebt : project.health}%
|
||||
{techicalDebt}%
|
||||
</Typography>
|
||||
<Typography variant='caption' color='text.secondary'>
|
||||
{healthToTechDebtEnabled ? 'technical debt' : 'health'}
|
||||
technical debt
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
@ -18,7 +18,7 @@ const setupLongRunningProject = () => {
|
||||
id: 'projectId',
|
||||
memberCount: 10,
|
||||
featureCount: 100,
|
||||
health: 80,
|
||||
techicalDebt: 20,
|
||||
name: 'projectName',
|
||||
},
|
||||
],
|
||||
@ -40,7 +40,7 @@ const setupLongRunningProject = () => {
|
||||
potentiallyStaleFlags: 14,
|
||||
staleFlags: 13,
|
||||
activeFlags: 12,
|
||||
health: 81,
|
||||
technicalDebt: 19,
|
||||
},
|
||||
latestEvents: [{ summary: 'someone created a flag', id: 0 }],
|
||||
roles: [{ name: 'Member' }],
|
||||
@ -84,7 +84,7 @@ const setupNewProject = () => {
|
||||
id: 'projectId',
|
||||
memberCount: 3,
|
||||
featureCount: 0,
|
||||
health: 100,
|
||||
technicalDebt: 0,
|
||||
name: 'projectName',
|
||||
},
|
||||
],
|
||||
@ -142,10 +142,10 @@ test('Render personal dashboard for a long running project', async () => {
|
||||
await screen.findByText('projectName');
|
||||
await screen.findByText('10'); // members
|
||||
await screen.findByText('100'); // features
|
||||
await screen.findAllByText('80%'); // health
|
||||
await screen.findAllByText('20%'); // technical debt
|
||||
|
||||
await screen.findByText('Project health');
|
||||
await screen.findByText('70%'); // avg health past window
|
||||
await screen.findByText('Technical debt');
|
||||
await screen.findByText('30%'); // avg technical debt past window
|
||||
await screen.findByText('someone created a flag');
|
||||
await screen.findByText('Member');
|
||||
await screen.findByText('myFlag');
|
||||
@ -161,7 +161,7 @@ test('Render personal dashboard for a new project', async () => {
|
||||
await screen.findByText('projectName');
|
||||
await screen.findByText('3'); // members
|
||||
await screen.findByText('0'); // features
|
||||
await screen.findByText('100%'); // health
|
||||
await screen.findByText('0%'); // technical debt
|
||||
|
||||
await screen.findByText('Create a feature flag');
|
||||
await screen.findByText('Connect an SDK');
|
||||
|
@ -4,7 +4,6 @@ import { Link } from 'react-router-dom';
|
||||
import Lightbulb from '@mui/icons-material/LightbulbOutlined';
|
||||
import type { PersonalDashboardProjectDetailsSchemaInsights } from 'openapi';
|
||||
import { ActionBox } from './ActionBox.tsx';
|
||||
import { useUiFlag } from 'hooks/useUiFlag.ts';
|
||||
|
||||
const PercentageScore = styled('span')(({ theme }) => ({
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
@ -58,42 +57,24 @@ const ProjectHealthMessage: FC<{
|
||||
insights: PersonalDashboardProjectDetailsSchemaInsights;
|
||||
project: string;
|
||||
}> = ({ trend, insights, project }) => {
|
||||
const healthToTechDebtEnabled = useUiFlag('healthToTechDebt');
|
||||
const { avgHealthCurrentWindow, avgHealthPastWindow, health } = insights;
|
||||
const improveMessage = healthToTechDebtEnabled
|
||||
? 'Remember to archive your stale feature flags to keep the technical debt low.'
|
||||
: 'Remember to archive your stale feature flags to keep the project health growing.';
|
||||
const { avgHealthCurrentWindow, avgHealthPastWindow } = insights;
|
||||
const improveMessage =
|
||||
'Remember to archive your stale feature flags to keep the technical debt low.';
|
||||
const keepDoingMessage =
|
||||
'This indicates that you are doing a good job of archiving your feature flags.';
|
||||
const avgCurrentTechnicalDebt = 100 - (avgHealthCurrentWindow ?? 0);
|
||||
const avgPastTechnicalDebt = 100 - (avgHealthPastWindow ?? 0);
|
||||
|
||||
if (trend === 'improved') {
|
||||
if (healthToTechDebtEnabled) {
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
On average, your project technical debt went down from{' '}
|
||||
<PercentageScore>
|
||||
{avgPastTechnicalDebt}%
|
||||
</PercentageScore>{' '}
|
||||
to{' '}
|
||||
<PercentageScore>
|
||||
{avgCurrentTechnicalDebt}%
|
||||
</PercentageScore>{' '}
|
||||
during the last 4 weeks.
|
||||
</Typography>
|
||||
<Typography>{keepDoingMessage}</Typography>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
On average, your project health went up from{' '}
|
||||
<PercentageScore>{avgHealthPastWindow}%</PercentageScore> to{' '}
|
||||
<PercentageScore>{avgHealthCurrentWindow}%</PercentageScore>{' '}
|
||||
On average, your project technical debt went down from{' '}
|
||||
<PercentageScore>{avgPastTechnicalDebt}%</PercentageScore>{' '}
|
||||
to{' '}
|
||||
<PercentageScore>
|
||||
{avgCurrentTechnicalDebt}%
|
||||
</PercentageScore>{' '}
|
||||
during the last 4 weeks.
|
||||
</Typography>
|
||||
<Typography>{keepDoingMessage}</Typography>
|
||||
@ -102,31 +83,15 @@ const ProjectHealthMessage: FC<{
|
||||
}
|
||||
|
||||
if (trend === 'declined') {
|
||||
if (healthToTechDebtEnabled) {
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
On average, your project technical debt went up from{' '}
|
||||
<PercentageScore>
|
||||
{avgPastTechnicalDebt}%
|
||||
</PercentageScore>{' '}
|
||||
to{' '}
|
||||
<PercentageScore>
|
||||
{avgCurrentTechnicalDebt}%
|
||||
</PercentageScore>{' '}
|
||||
during the last 4 weeks.
|
||||
</Typography>
|
||||
<Typography>{improveMessage}</Typography>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
On average, your project health went down from{' '}
|
||||
<PercentageScore>{avgHealthPastWindow}%</PercentageScore> to{' '}
|
||||
<PercentageScore>{avgHealthCurrentWindow}%</PercentageScore>{' '}
|
||||
On average, your project technical debt went up from{' '}
|
||||
<PercentageScore>{avgPastTechnicalDebt}%</PercentageScore>{' '}
|
||||
to{' '}
|
||||
<PercentageScore>
|
||||
{avgCurrentTechnicalDebt}%
|
||||
</PercentageScore>{' '}
|
||||
during the last 4 weeks.
|
||||
</Typography>
|
||||
<Typography>{improveMessage}</Typography>
|
||||
@ -135,62 +100,31 @@ const ProjectHealthMessage: FC<{
|
||||
}
|
||||
|
||||
if (trend === 'consistent') {
|
||||
if (healthToTechDebtEnabled) {
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
On average, your project technical debt has remained at{' '}
|
||||
<PercentageScore>
|
||||
{avgCurrentTechnicalDebt}%
|
||||
</PercentageScore>{' '}
|
||||
during the last 4 weeks.
|
||||
</Typography>
|
||||
<Typography>{keepDoingMessage}</Typography>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
On average, your project health has remained at{' '}
|
||||
<PercentageScore>{avgHealthCurrentWindow}%</PercentageScore>{' '}
|
||||
On average, your project technical debt has remained at{' '}
|
||||
<PercentageScore>
|
||||
{avgCurrentTechnicalDebt}%
|
||||
</PercentageScore>{' '}
|
||||
during the last 4 weeks.
|
||||
</Typography>
|
||||
<Typography>
|
||||
{avgHealthCurrentWindow && avgHealthCurrentWindow >= 75
|
||||
? keepDoingMessage
|
||||
: improveMessage}
|
||||
</Typography>
|
||||
<Typography>{keepDoingMessage}</Typography>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (trend === 'unknown') {
|
||||
if (healthToTechDebtEnabled) {
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
Your current project technical debt is{' '}
|
||||
<PercentageScore>
|
||||
{avgCurrentTechnicalDebt}%
|
||||
</PercentageScore>
|
||||
.
|
||||
</Typography>
|
||||
<Typography>{improveMessage}</Typography>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
Your current health score is{' '}
|
||||
<PercentageScore>{health}%</PercentageScore>.
|
||||
</Typography>
|
||||
<Typography>
|
||||
{health >= 75 ? keepDoingMessage : improveMessage}
|
||||
Your current project technical debt is{' '}
|
||||
<PercentageScore>
|
||||
{avgCurrentTechnicalDebt}%
|
||||
</PercentageScore>
|
||||
.
|
||||
</Typography>
|
||||
<Typography>{improveMessage}</Typography>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -202,7 +136,6 @@ export const ProjectSetupComplete: FC<{
|
||||
project: string;
|
||||
insights: PersonalDashboardProjectDetailsSchemaInsights;
|
||||
}> = ({ project, insights }) => {
|
||||
const healthToTechDebtEnabled = useUiFlag('healthToTechDebt');
|
||||
const projectHealthTrend = determineProjectHealthTrend(insights);
|
||||
|
||||
return (
|
||||
@ -211,9 +144,7 @@ export const ProjectSetupComplete: FC<{
|
||||
<>
|
||||
<Lightbulb color='primary' />
|
||||
<Typography sx={{ fontWeight: 'bold' }} component='h4'>
|
||||
{healthToTechDebtEnabled
|
||||
? 'Technical debt'
|
||||
: 'Project health'}
|
||||
Technical debt
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { HealthGridTile } from './ProjectHealthGrid.styles';
|
||||
import { PrettifyLargeNumber } from 'component/common/PrettifyLargeNumber/PrettifyLargeNumber';
|
||||
import { getTechnicalDebtColor } from 'utils/getTechnicalDebtColor.ts';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
|
||||
const ChartRadius = 40;
|
||||
const ChartStrokeWidth = 13;
|
||||
@ -82,17 +81,6 @@ const UnhealthyFlagBox = ({ flagCount }: { flagCount: number }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const useHealthColor = (healthRating: number) => {
|
||||
const theme = useTheme();
|
||||
if (healthRating <= 24) {
|
||||
return theme.palette.error.main;
|
||||
}
|
||||
if (healthRating <= 74) {
|
||||
return theme.palette.warning.border;
|
||||
}
|
||||
return theme.palette.success.border;
|
||||
};
|
||||
|
||||
const useTechnicalDebtColor = (techicalDebt: number) => {
|
||||
const theme = useTheme();
|
||||
switch (getTechnicalDebtColor(techicalDebt)) {
|
||||
@ -115,22 +103,18 @@ const Wrapper = styled(HealthGridTile)(({ theme }) => ({
|
||||
export const ProjectHealth = () => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const {
|
||||
data: { health, technicalDebt, staleFlags },
|
||||
data: { technicalDebt, staleFlags },
|
||||
} = useProjectStatus(projectId);
|
||||
const { isOss } = useUiConfig();
|
||||
const theme = useTheme();
|
||||
const healthToDebtEnabled = useUiFlag('healthToTechDebt');
|
||||
const circumference = 2 * Math.PI * ChartRadius;
|
||||
const healthRating = health.current;
|
||||
|
||||
const gapLength = 0.3;
|
||||
const filledLength = 1 - gapLength;
|
||||
const offset = 0.75 - gapLength / 2;
|
||||
const healthLength = (healthRating / 100) * circumference * 0.7;
|
||||
const technicalDebtLength =
|
||||
((technicalDebt.current || 0) / 100) * circumference * 0.7;
|
||||
|
||||
const healthColor = useHealthColor(healthRating);
|
||||
const technicalDebtColor = useTechnicalDebtColor(
|
||||
technicalDebt.current || 0,
|
||||
);
|
||||
@ -155,17 +139,9 @@ export const ProjectHealth = () => {
|
||||
cy='50'
|
||||
r={ChartRadius}
|
||||
fill='none'
|
||||
stroke={
|
||||
healthToDebtEnabled
|
||||
? technicalDebtColor
|
||||
: healthColor
|
||||
}
|
||||
stroke={technicalDebtColor}
|
||||
strokeWidth={ChartStrokeWidth}
|
||||
strokeDasharray={
|
||||
healthToDebtEnabled
|
||||
? `${technicalDebtLength} ${circumference - technicalDebtLength}`
|
||||
: `${healthLength} ${circumference - healthLength}`
|
||||
}
|
||||
strokeDasharray={`${technicalDebtLength} ${circumference - technicalDebtLength}`}
|
||||
strokeDashoffset={offset * circumference}
|
||||
/>
|
||||
<text
|
||||
@ -176,32 +152,18 @@ export const ProjectHealth = () => {
|
||||
fill={theme.palette.text.primary}
|
||||
fontSize={theme.typography.h1.fontSize}
|
||||
>
|
||||
{healthToDebtEnabled
|
||||
? technicalDebt.current || 0
|
||||
: healthRating}
|
||||
%
|
||||
{technicalDebt.current || 0}%
|
||||
</text>
|
||||
</StyledSVG>
|
||||
</SVGWrapper>
|
||||
<TextContainer>
|
||||
<Typography>
|
||||
{healthToDebtEnabled ? (
|
||||
<>
|
||||
Your current technical debt rating is{' '}
|
||||
{technicalDebt.current}%.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Your current project health rating is{' '}
|
||||
{healthRating}%.
|
||||
</>
|
||||
)}
|
||||
Your current technical debt rating is{' '}
|
||||
{technicalDebt.current}%.
|
||||
</Typography>
|
||||
{!isOss() && (
|
||||
<Link to={`/insights?project=IS%3A${projectId}`}>
|
||||
{healthToDebtEnabled
|
||||
? 'View technical debt over time'
|
||||
: 'View health over time'}
|
||||
View technical debt over time
|
||||
</Link>
|
||||
)}
|
||||
</TextContainer>
|
||||
|
@ -351,7 +351,7 @@ export const ProjectAccessAssign = ({
|
||||
modal
|
||||
title={`${!edit ? 'Assign' : 'Edit'} ${entityType} access`}
|
||||
description='Custom project roles allow you to fine-tune access rights and permissions within your projects.'
|
||||
documentationLink='https://docs.getunleash.io/how-to/how-to-create-and-assign-custom-project-roles'
|
||||
documentationLink='https://docs.getunleash.io/reference/rbac#create-and-assign-a-custom-project-role'
|
||||
documentationLinkLabel='Project access documentation'
|
||||
formatApiCode={formatApiCode}
|
||||
>
|
||||
|
@ -0,0 +1,32 @@
|
||||
import { useCallback } from 'react';
|
||||
import useAPI from '../useApi/useApi.js';
|
||||
import type { ImpactMetricsState } from 'component/impact-metrics/types.ts';
|
||||
|
||||
export const useImpactMetricsSettingsApi = () => {
|
||||
const { makeRequest, createRequest, errors, loading } = useAPI({
|
||||
propagateErrors: true,
|
||||
});
|
||||
|
||||
const updateSettings = useCallback(
|
||||
async (settings: ImpactMetricsState) => {
|
||||
const path = `api/admin/impact-metrics/settings`;
|
||||
const req = createRequest(
|
||||
path,
|
||||
{
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(settings),
|
||||
},
|
||||
'updateImpactMetricsSettings',
|
||||
);
|
||||
|
||||
return makeRequest(req.caller, req.id);
|
||||
},
|
||||
[makeRequest, createRequest],
|
||||
);
|
||||
|
||||
return {
|
||||
updateSettings,
|
||||
errors,
|
||||
loading,
|
||||
};
|
||||
};
|
@ -16,12 +16,16 @@ export type ImpactMetricsResponse = {
|
||||
step?: string;
|
||||
series: ImpactMetricsSeries[];
|
||||
labels?: ImpactMetricsLabels;
|
||||
debug?: {
|
||||
query?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type ImpactMetricsQuery = {
|
||||
series: string;
|
||||
range: 'hour' | 'day' | 'week' | 'month';
|
||||
labels?: Record<string, string[]>;
|
||||
showRate?: boolean;
|
||||
};
|
||||
|
||||
export const useImpactMetricsData = (query?: ImpactMetricsQuery) => {
|
||||
@ -34,6 +38,10 @@ export const useImpactMetricsData = (query?: ImpactMetricsQuery) => {
|
||||
range: query.range,
|
||||
});
|
||||
|
||||
if (query.showRate !== undefined) {
|
||||
params.append('showRate', query.showRate.toString());
|
||||
}
|
||||
|
||||
if (query.labels && Object.keys(query.labels).length > 0) {
|
||||
// Send labels as they are - the backend will handle the formatting
|
||||
const labelsParam = Object.entries(query.labels).reduce(
|
||||
|
@ -0,0 +1,18 @@
|
||||
import { fetcher, useApiGetter } from '../useApiGetter/useApiGetter.js';
|
||||
import { formatApiPath } from 'utils/formatPath';
|
||||
import type { ImpactMetricsState } from 'component/impact-metrics/types.ts';
|
||||
|
||||
export const useImpactMetricsSettings = () => {
|
||||
const PATH = `api/admin/impact-metrics/settings`;
|
||||
const { data, refetch, loading, error } = useApiGetter<ImpactMetricsState>(
|
||||
formatApiPath(PATH),
|
||||
() => fetcher(formatApiPath(PATH), 'Impact metrics settings'),
|
||||
);
|
||||
|
||||
return {
|
||||
settings: data || { charts: [], layout: [] },
|
||||
refetch,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
};
|
@ -87,7 +87,6 @@ export type UiFlags = {
|
||||
customMetrics?: boolean;
|
||||
lifecycleMetrics?: boolean;
|
||||
createFlagDialogCache?: boolean;
|
||||
healthToTechDebt?: boolean;
|
||||
improvedJsonDiff?: boolean;
|
||||
impactMetrics?: boolean;
|
||||
crDiffView?: boolean;
|
||||
|
@ -129,7 +129,7 @@
|
||||
"ts-toolbelt": "^9.6.0",
|
||||
"type-is": "^2.0.0",
|
||||
"ulidx": "^2.4.1",
|
||||
"unleash-client": "^6.7.0-beta.1",
|
||||
"unleash-client": "^6.7.0-beta.3",
|
||||
"uuid": "^11.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -215,7 +215,7 @@ export default class ClientMetricsServiceV2 {
|
||||
|
||||
const invalidToggleNames =
|
||||
toggleNames.length - validatedToggleNames.length;
|
||||
this.logger.info(
|
||||
this.logger.debug(
|
||||
`Got ${toggleNames.length} (${invalidToggleNames > 0 ? `${invalidToggleNames} invalid ones` : 'all valid'}) metrics from ${value.appName}`,
|
||||
);
|
||||
|
||||
@ -244,7 +244,7 @@ export default class ClientMetricsServiceV2 {
|
||||
seenAt: value.bucket.stop,
|
||||
environment,
|
||||
}));
|
||||
this.logger.info(
|
||||
this.logger.debug(
|
||||
`Registering ${unknownFlags.length} unknown flags from ${value.appName} in the ${environment} environment. Some of the unknown flag names include: ${unknownFlags
|
||||
.slice(0, 10)
|
||||
.map(({ name }) => `"${name}"`)
|
||||
|
@ -56,7 +56,6 @@ export type IFlagKey =
|
||||
| 'edgeObservability'
|
||||
| 'reportUnknownFlags'
|
||||
| 'lifecycleMetrics'
|
||||
| 'healthToTechDebt'
|
||||
| 'customMetrics'
|
||||
| 'impactMetrics'
|
||||
| 'createFlagDialogCache'
|
||||
@ -269,10 +268,6 @@ const flags: IFlags = {
|
||||
process.env.UNLEASH_EXPERIMENTAL_LIFECYCLE_METRICS,
|
||||
false,
|
||||
),
|
||||
healthToTechDebt: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_HEALTH_TO_TECH_DEBT,
|
||||
false,
|
||||
),
|
||||
createFlagDialogCache: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_CREATE_FLAG_DIALOG_CACHE,
|
||||
false,
|
||||
|
62
website/docs/api-overview.mdx
Normal file
62
website/docs/api-overview.mdx
Normal file
@ -0,0 +1,62 @@
|
||||
---
|
||||
title: API overview
|
||||
description: "An overview of the three main Unleash APIs: Client API, Frontend API, and Admin API."
|
||||
displayed_sidebar: documentation
|
||||
---
|
||||
|
||||
## Unleash APIs
|
||||
|
||||
Unleash provides a set of APIs to give you full programmatic control over your feature flags and to connect your applications and services to Unleash. There are three main APIs, each designed for a specific purpose.
|
||||
|
||||
| API | Used by | Primary use case |
|
||||
|---------------|---------|---|
|
||||
| **Client API** | Server-side SDKs | Fetch feature flag configurations. |
|
||||
| **Frontend API** | Client-side SDKs | Fetch enabled feature flags for a specific [Unleash Context](/reference/unleash-context). |
|
||||
| **Admin API** | [Admin UI](#the-unleash-admin-ui), internal tooling, and third-party [integrations](/reference/integrations) | Access and manage all resources within Unleash, such as context, environments, events, metrics, and users. |
|
||||
|
||||
## API authentication and tokens
|
||||
|
||||
All Unleash APIs require authentication using an [API token](/reference/api-tokens-and-client-keys). The type of token you use depends on the API you are accessing and your specific use case.
|
||||
|
||||
### Token types
|
||||
|
||||
Unleash supports four types of API tokens:
|
||||
- **Client tokens**: Used to connect server-side SDKs, Unleash Edge, and the Unleash Proxy to the Client API. Can be scoped to a specific project and environment.
|
||||
- **Frontend tokens**: Used to connect client-side SDKs to the Frontend API or Unleash Edge. These tokens are designed to be publicly accessible and have limited permissions. Can be scoped to a specific project and environment.
|
||||
- **Personal access tokens**: Tied to a specific user account. They are useful for testing, debugging, or providing temporary access to tools and scripts that need to interact with the Admin API.
|
||||
- **Service account tokens**: The recommended method for providing API access to integrations, automation tools, and other non-human users. Service accounts provide a more secure and manageable way to grant Admin API access.
|
||||
|
||||
For an end-to-end Unleash integration, you might need to use multiple token types. For example, when connecting a client-side SDK to Unleash using Unleash Edge, you'll need the following tokens:
|
||||
- A frontend token for the client-side SDK to securely communicate with Unleash Edge.
|
||||
- A client token for Unleash Edge to communicate with the main Unleash server.
|
||||
|
||||

|
||||
|
||||
Ensure that the client token has access to the same project and environment (or a broader scope) as the frontend token.
|
||||
|
||||
### Create an API token
|
||||
|
||||
Depending on your permissions, you can create API tokens in the Unleash Admin UI in four ways:
|
||||
|
||||
- **Admin settings > Access control > API access**: for client or frontend tokens; requires the Admin root role, or a custom root role with API token permissions.
|
||||
- **Admin settings > Service accounts > New service account**: for creating a service account and assigning a token.
|
||||
- **Settings > API access** [inside a project]: for project-specific client or frontend tokens; permitted for project Members or users with a corresponding root role.
|
||||
- **Profile > View profile settings > Personal API tokens**: for personal access tokens.
|
||||
|
||||
## API specification
|
||||
|
||||
For a comprehensive and interactive reference of all available endpoints, Unleash provides an OpenAPI specification. This is useful for exploring the APIs, generating client libraries, and for testing.
|
||||
|
||||
:::note Availability
|
||||
|
||||
**Unleash version**: `5.2+` enabled by default.
|
||||
**Unleash version**: `4.13+` can be enabled using the `ENABLE_OAS` [environment variable](using-unleash/deploy/configuring-unleash).
|
||||
:::
|
||||
|
||||
You can access the specification from your Unleash instance at the following paths:
|
||||
|
||||
- **Interactive Swagger UI**: `/docs/openapi/`
|
||||
- **Raw JSON specification**: `/docs/openapi.json`
|
||||
|
||||
For detailed guides on each API, please refer to the full reference documentation.
|
||||
|
@ -18,7 +18,7 @@ For this tutorial, you'll need the following:
|
||||
|
||||

|
||||
|
||||
The Unleash Server is a **Feature Flag Control Service**, which manages your feature flags and lets you retrieve flag data. Unleash has a UI for creating and managing projects and feature flags. For server-side applications or automated scripts, Unleash exposes an [API](/reference/api/unleash) defined by an OpenAPI specification, allowing you to perform these actions programmatically.
|
||||
The Unleash Server is a **Feature Flag Control Service**, which manages your feature flags and lets you retrieve flag data. Unleash has a UI for creating and managing projects and feature flags. For server-side applications or automated scripts, Unleash exposes an [API](/api-overview) defined by an OpenAPI specification, allowing you to perform these actions programmatically.
|
||||
|
||||
## 1. Install a local feature flag provider
|
||||
|
||||
|
@ -24,7 +24,7 @@ For this tutorial, you’ll need the following:
|
||||
|
||||

|
||||
|
||||
The Unleash Server is a **Feature Flag Control Service**, which manages your feature flags and lets you retrieve flag data. Unleash has a UI for creating and managing projects and feature flags. There are also [API commands available](https://docs.getunleash.io/reference/api/unleash) to perform the same actions straight from your CLI or app.
|
||||
The Unleash Server is a **Feature Flag Control Service**, which manages your feature flags and lets you retrieve flag data. Unleash has a UI for creating and managing projects and feature flags. There are also [API commands available](/api-overview) to perform the same actions straight from your CLI or app.
|
||||
|
||||
## 1. Install a local feature flag provider
|
||||
|
||||
|
@ -21,7 +21,7 @@ For this tutorial, you'll need the following:
|
||||
|
||||

|
||||
|
||||
The Unleash Server is a **Feature Flag Control Service**, which manages your feature flags and lets you retrieve flag data. Unleash has a UI for creating and managing projects and feature flags. You can perform the same actions straight from your CLI or server-side app using the [Unleash API](/reference/api/unleash).
|
||||
The Unleash Server is a **Feature Flag Control Service**, which manages your feature flags and lets you retrieve flag data. Unleash has a UI for creating and managing projects and feature flags. You can perform the same actions straight from your CLI or server-side app using the [Unleash API](/api-overview).
|
||||
|
||||
## Best practices for back-end apps with Unleash
|
||||
|
||||
|
@ -37,7 +37,7 @@ In this tutorial, you will need the following:
|
||||
|
||||
This architecture diagram breaks down how the Java app works with Unleash to control feature flags. We connect the Unleash service to your Java app using the Java SDK.
|
||||
|
||||
The Unleash Server acts as a Feature Flag Control Service, managing and storing your feature flags. It enables the retrieval of flag data and, particularly when not utilizing a user interface, supports the sending of data to and from the service. The Unleash Server has a UI for creating and managing projects and feature flags. There are also [API commands available](https://docs.getunleash.io/reference/api/unleash) to perform the same actions straight from your CLI or server-side app.
|
||||
The Unleash Server acts as a Feature Flag Control Service, managing and storing your feature flags. It enables the retrieval of flag data and, particularly when not utilizing a user interface, supports the sending of data to and from the service. The Unleash Server has a UI for creating and managing projects and feature flags. There are also [API commands available](/api-overview) to perform the same actions straight from your CLI or server-side app.
|
||||
|
||||
## 1. Feature Flag best practices for backend apps
|
||||
|
||||
|
@ -38,7 +38,7 @@ In this tutorial, you will need the following:
|
||||
|
||||
This architecture diagram breaks down how the Java Spring Boot app works with Unleash to use feature flags.
|
||||
|
||||
The Unleash Server is a Feature Flag Control Service for managing and storing your feature flags. It enables the retrieval of flag data and, particularly when not utilizing a user interface, supports sending data to and from the service. The Unleash Server has a UI for creating and managing projects and feature flags. API commands are also [available](/reference/api/unleash) to perform the same actions from your CLI or server-side app.
|
||||
The Unleash Server is a Feature Flag Control Service for managing and storing your feature flags. It enables the retrieval of flag data and, particularly when not utilizing a user interface, supports sending data to and from the service. The Unleash Server has a UI for creating and managing projects and feature flags. API commands are also [available](/api-overview) to perform the same actions from your CLI or server-side app.
|
||||
|
||||
The Spring Boot SDK is an extension of the Java SDK, configured for Spring Boot-specific architecture and conventions.
|
||||
|
||||
|
@ -43,7 +43,7 @@ In this tutorial, you will need the following:
|
||||
|
||||
This architecture diagram breaks down how the Python app works with Unleash to control feature flags. We connect the Unleash service to your Python app using the Python SDK.
|
||||
|
||||
The Unleash Server is a **Feature Flag Control Service**, which is a service that manages your feature flags and is used to retrieve flag data from (and send data to, especially when not using a UI). The Unleash server has a UI for creating and managing projects and feature flags. There are also [API commands available](https://docs.getunleash.io/reference/api/unleash) to perform the same actions straight from your CLI or server-side app.
|
||||
The Unleash Server is a **Feature Flag Control Service**, which is a service that manages your feature flags and is used to retrieve flag data from (and send data to, especially when not using a UI). The Unleash server has a UI for creating and managing projects and feature flags. There are also [API commands available](/api-overview) to perform the same actions straight from your CLI or server-side app.
|
||||
|
||||
## 1. Unleash best practice for backend apps
|
||||
|
||||
|
@ -30,7 +30,7 @@ For this tutorial, you’ll need the following:
|
||||
|
||||

|
||||
|
||||
The Unleash Server is a **Feature Flag Control Service**, which manages your feature flags and lets you retrieve flag data. Unleash has a UI for creating and managing projects and feature flags. There are also [API commands available](/reference/api/unleash) to perform the same actions straight from your CLI or server-side app.
|
||||
The Unleash Server is a **Feature Flag Control Service**, which manages your feature flags and lets you retrieve flag data. Unleash has a UI for creating and managing projects and feature flags. There are also [API commands available](/api-overview) to perform the same actions straight from your CLI or server-side app.
|
||||
|
||||
## 1. Best practices for back-end apps with Unleash
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
---
|
||||
title: How to Implement Feature Flags in React
|
||||
slug: /feature-flag-tutorials/react
|
||||
pagination_next: feature-flag-tutorials/react/examples
|
||||
---
|
||||
|
||||
import VideoContent from '@site/src/components/VideoContent.jsx';
|
||||
|
@ -35,7 +35,7 @@ For this tutorial, you’ll need the following:
|
||||
|
||||

|
||||
|
||||
The Unleash Server is a **Feature Flag Control Service**, which manages your feature flags and lets you retrieve flag data. Unleash has a UI for creating and managing projects and feature flags. There are also [API commands available](https://docs.getunleash.io/reference/api/unleash) to perform the same actions straight from your CLI or server-side app.
|
||||
The Unleash Server is a **Feature Flag Control Service**, which manages your feature flags and lets you retrieve flag data. Unleash has a UI for creating and managing projects and feature flags. There are also [API commands available](/api-overview) to perform the same actions straight from your CLI or server-side app.
|
||||
|
||||
## 1. Best practices for back-end apps with Unleash
|
||||
|
||||
|
@ -26,7 +26,7 @@ For this tutorial, you’ll need the following:
|
||||
|
||||

|
||||
|
||||
The Unleash Server is a **Feature Flag Control Service**, which manages your feature flags and lets you retrieve flag data. Unleash has a UI for creating and managing projects and feature flags. There are also [API commands available](https://docs.getunleash.io/reference/api/unleash) to perform the same actions straight from your CLI or app.
|
||||
The Unleash Server is a **Feature Flag Control Service**, which manages your feature flags and lets you retrieve flag data. Unleash has a UI for creating and managing projects and feature flags. There are also [API commands available](/api-overview) to perform the same actions straight from your CLI or app.
|
||||
|
||||
## 1. Install a local feature flag provider
|
||||
|
||||
|
@ -1,11 +1,12 @@
|
||||
---
|
||||
title: How to do A/B Testing using Feature Flags
|
||||
title: Implement A/B testing using feature flags
|
||||
slug: /feature-flag-tutorials/use-cases/a-b-testing
|
||||
pagination_next: feature-flag-tutorials/use-cases/ai
|
||||
---
|
||||
|
||||
Feature flags are a great way to run A/B or multivariate tests with minimal code modifications, and Unleash offers built-in features that make it easy to get started. In this tutorial, we will walk through how to do an A/B test using Unleash with your application.
|
||||
|
||||
## How to Perform A/B Testing with Feature Flags
|
||||
## How to perform A/B testing with feature flags
|
||||
|
||||
To follow along with this tutorial, you need access to an Unleash instance to create and manage feature flags. Head over to our [Quick Start documentation](/quickstart) for options, including running locally or using an [Unleash SaaS instance](https://www.getunleash.io/pricing?).
|
||||
|
||||
@ -21,7 +22,7 @@ In this tutorial, you will learn how to set up and run an A/B test using feature
|
||||
|
||||
You will also learn about how to [automate advanced A/B testing strategies](#multi-arm-bandit-tests-to-find-the-winning-variant) such as multi-arm bandit testing using feature flags.
|
||||
|
||||
### Create a Feature Flag
|
||||
### Create a feature flag
|
||||
|
||||
To do A/B testing, we'll create a feature flag to implement the rollout strategy. After that, we'll explore what strategies are and how they are configured in Unleash.
|
||||
|
||||
@ -39,7 +40,7 @@ Once you have completed the form, click **Create feature flag**.
|
||||
|
||||
Your new feature flag is now ready to be used. Next, we will configure the A/B testing strategy for your flag.
|
||||
|
||||
### Target Users for A/B Testing
|
||||
### Target users for A/B testing
|
||||
|
||||
With an A/B testing strategy, you’ll be able to:
|
||||
|
||||
@ -93,13 +94,13 @@ Next, decide the percentage of users to target for each variant, known as the va
|
||||
|
||||

|
||||
|
||||
### Manage User Session Behavior
|
||||
### Manage user session behavior
|
||||
|
||||
Unleash is built to give developers confidence in their ability to run A/B tests effectively. One critical component of implementing A/B testing strategies is maintaining a consistent experience for each user across multiple user sessions.
|
||||
|
||||
For example, user `uuid1234` should be the target of `variantA` regardless of their session. The original subset of users that get `variantA` will continue to experience that variation of the feature over time. At Unleash, we call this [stickiness](/reference/stickiness). You can define the parameter of stickiness in the gradual rollout form. By default, stickiness is calculated by `sessionId` and `groupId`.
|
||||
|
||||
### Track A/B Testing for your Key Performance Metrics
|
||||
### Track A/B testing for your key performance metrics
|
||||
|
||||
An A/B testing strategy is most useful when you can track the results of a feature rollout to users. When your team has clearly defined the goals for your A/B tests, you can use Unleash to analyze how results tie back to key metrics, like conversion rates or time spent on a page. One way to collect this data is by enabling [impression data](/reference/impression-data) per feature flag. Impression data contains information about a specific feature flag activation check.
|
||||
|
||||
@ -130,7 +131,7 @@ The output from the impression data in your app may look like this code snippet:
|
||||
}
|
||||
```
|
||||
|
||||
In order to capture impression events in your app, follow our [language and framework-specific tutorials](/languages-and-frameworks).
|
||||
In order to capture impression events in your app, follow our [language and framework-specific tutorials](/feature-flag-tutorials/react).
|
||||
|
||||
Now that your application is capturing impression events, you can configure the correct data fields and formatting to send to any analytics tool or data warehouse you use.
|
||||
|
||||
@ -197,7 +198,7 @@ Here is an example of a payload that is returned from Google Analytics that incl
|
||||
|
||||
By enabling impression data for your feature flag and listening to events within your application code, you can leverage this data flowing to your integrated analytics tools to make informed decisions faster and adjust your strategies based on real user behavior.
|
||||
|
||||
### Roll Out the Winning Variant to All Users
|
||||
### Roll out the winning variant to all users
|
||||
|
||||
After you have implemented your A/B test and measured the performance of a feature to a subset of users, you can decide which variant is the most optimal experience to roll out to all users in production.
|
||||
|
||||
@ -207,11 +208,11 @@ When rolling out the winning variant, your flag may already be on in your produc
|
||||
|
||||
After the flag has been available to 100% of users over time, archive the flag and clean up your codebase.
|
||||
|
||||
## A/B Testing with Enterprise Automation
|
||||
## A/B testing with enterprise automation
|
||||
|
||||
With Unleash, you can automate feature flags through APIs and even rely on [actions](/reference/actions) and [signals](/reference/signals) to enable and disable flags dynamically. When running A/B tests, you can configure your projects to execute tasks in response to application metrics and thresholds you define. For example, if an experimentation feature that targets a part of your user base logs errors, your actions can automatically disable the feature so your team is given the time to triage while still providing a seamless, alternative experience to users. Similarly, you can use the APIs to modify the percentage of users targeted for variations of a feature based off users engaging with one variation more than the other.
|
||||
|
||||
### Multi-arm Bandit Tests to Find the Winning Variant
|
||||
### Multi-arm bandit tests to find the winning variant
|
||||
|
||||
When running complex multivariate tests with numerous combinations, automating the process of finding the best variation of a feature is the most optimal, cost-effective approach for organizations with a large user base. [Multi-arm bandit tests](https://en.wikipedia.org/wiki/Multi-armed_bandit) are a powerful technique used in A/B testing to allocate traffic to different versions of a feature or application in a way that maximizes the desired outcome, such as conversion rate or click-through rate. This approach offers several advantages over traditional A/B testing and is a viable solution for large enterprise teams.
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
---
|
||||
title: How to use feature flags with AI
|
||||
slug: /feature-flag-tutorials/use-cases/ai
|
||||
title: Experiment with AI using feature flags
|
||||
---
|
||||
|
||||
Hello,
|
||||
|
@ -1,9 +1,10 @@
|
||||
---
|
||||
title: How to Perform a Gradual Rollout
|
||||
title: How to perform a gradual rollout
|
||||
slug: /feature-flag-tutorials/use-cases/gradual-rollout
|
||||
pagination_next: feature-flag-tutorials/use-cases/a-b-testing
|
||||
---
|
||||
|
||||
## What is a Gradual Rollout?
|
||||
## What is a gradual rollout?
|
||||
|
||||
A **gradual rollout** is a controlled release strategy where a new feature is first released to a small subset of users. This allows for monitoring user behavior, identifying potential issues, and gathering feedback before a full-scale launch. It also allows us to experiment quickly and safely.
|
||||
|
||||
@ -13,7 +14,7 @@ Developers also use gradual rollouts to gather user feedback. Early adopters pro
|
||||
|
||||
The key benefits of gradual rollouts are that you can experiment rapidly on a controlled group and roll back quickly if the experiment goes wrong. This reduces the risk of failure, improves software quality, improves user experience, and optimizes resource utilization.
|
||||
|
||||
## How to Perform a Gradual Rollout with Unleash
|
||||
## How to perform a gradual rollout with Unleash
|
||||
|
||||
To follow along with this tutorial, you will need an Unleash instance. If you’d prefer to self-host Unleash, read our [Quickstart guide](/quickstart). Alternatively, if you’d like your project to be hosted by Unleash, go to [getunleash.io](https://www.getunleash.io/pricing).
|
||||
|
||||
@ -41,7 +42,7 @@ Your new feature flag has been created and is ready to be used. Upon returning t
|
||||
|
||||
Next, we will configure the gradual rollout strategy for your new flag.
|
||||
|
||||
## Implementing a Gradual Rollout Activation Strategy
|
||||
## Implementing a gradual rollout activation strategy
|
||||
|
||||
An important Unleash concept that enables developers to perform a gradual rollout is an [activation strategy](/reference/activation-strategies). An activation strategy defines who will be exposed to a particular flag or flags. Unleash comes pre-configured with multiple activation strategies that let you enable a feature only for a specified audience, depending on the parameters under which you would like to release a feature.
|
||||
|
||||
@ -75,7 +76,7 @@ Constraints and variants are not required for a gradual rollout. These additiona
|
||||
|
||||
For gradual rollouts, _strategy constraints_ are most applicable for more granular conditions of a feature release. In the next section, we’ll explore how to apply a strategy constraint on top of a gradual rollout for more advanced use cases.
|
||||
|
||||
## Applying Strategy Constraints
|
||||
## Applying strategy constraints
|
||||
|
||||
When utilizing an activation strategy such as a gradual rollout, it may be necessary to further define which subset of users get access to a feature and when the rollout takes place, depending on the complexity of your use case. Unleash provides [strategy constraints](/reference/activation-strategies#constraints) as a way to fine-tune conditions under which a feature flag is evaluated.
|
||||
|
||||
@ -94,11 +95,11 @@ Within a gradual rollout activation strategy, you can use strategy constraints t
|
||||
Add [constraints](/reference/activation-strategies#constraints) to refine the rollout based on user attributes, segments, or conditions.
|
||||
|
||||
|
||||
### Define Custom Context Fields for Strategy Constraints
|
||||
### Define custom context fields for strategy constraints
|
||||
|
||||
If you want to create new types of constraints that are not built into Unleash, you can create [custom context fields](/reference/unleash-context#custom-context-fields) to use in your gradual rollout for more advanced use cases.
|
||||
|
||||
## Leveraging Segments
|
||||
## Using segments
|
||||
|
||||
A [segment](/reference/segments) is a reusable collection of [strategy constraints](/reference/activation-strategies#constraints). Like with strategy constraints, you apply segments to feature flag activation strategies.
|
||||
|
||||
@ -125,7 +126,7 @@ You must pass the relevant fields in your context in the SDK in order for gradua
|
||||
|
||||
By following these steps and leveraging Unleash's features, you can effectively execute and refine gradual rollouts to minimize risks and optimize feature delivery.
|
||||
|
||||
## Managing Gradual Rollouts With Enterprise Security In Mind
|
||||
## Managing gradual rollouts with enterprise security in mind
|
||||
|
||||
For large-scale organizations, managing feature flags across many teams can be complex and challenging. Unleash was architected for your feature flag management to be scalable and traceable for enterprises, which boosts overall internal security posture while delivering software efficiently.
|
||||
|
||||
@ -137,7 +138,7 @@ After you have implemented a gradual rollout strategy, we recommend managing the
|
||||
|
||||
Read our documentation on how to effectively manage [feature flags at scale](/topics/feature-flags/best-practices-using-feature-flags-at-scale) while reducing security risks. Let’s walk through these recommended Unleash features in the subsequent sections.
|
||||
|
||||
### Reviewing Application Metrics
|
||||
### Reviewing application metrics
|
||||
|
||||
[Unleash metrics](/reference/api/unleash/metrics) are a great way to understand user traffic. With your application using feature flags, you can review:
|
||||
|
||||
@ -149,7 +150,7 @@ Read our documentation on how to effectively manage [feature flags at scale](/to
|
||||
|
||||
When managing a gradual rollout, leverage metrics to gain deeper insight into flag usage against your application over time. For large-scale organizations with many feature flags to manage, this can be a useful monitoring tool for individual flags you would like to keep track of.
|
||||
|
||||
### Reviewing Audit Logs
|
||||
### Reviewing audit logs
|
||||
|
||||
Because a feature flag service controls the way an application behaves in production, it can be highly important to have visibility into when changes have been made and by whom. This is especially true in highly regulated environments. In these cases, you might find audit logging useful for:
|
||||
|
||||
@ -164,7 +165,7 @@ Unleash provides the data to log any change that has happened over time, at the
|
||||
|
||||
Learn more about [Event Log](/reference/events#event-log) in our documentation.
|
||||
|
||||
### Managing Change Requests
|
||||
### Managing change requests
|
||||
|
||||
You can use Unleash's [change request](/reference/change-requests) feature to propose and review modifications to feature flags. This gives developers complete control over your production environment. In large scale organizations and heavily regulated industries, we want to help developers reduce risk of errors in production or making unwanted changes by team members that have not been properly reviewed and approved.
|
||||
|
||||
|
@ -1,15 +1,12 @@
|
||||
---
|
||||
title: How to capture impression data
|
||||
title: Send impression data to analytics tools
|
||||
---
|
||||
|
||||
import ApiRequest from "@site/src/components/ApiRequest";
|
||||
import VideoContent from "@site/src/components/VideoContent.jsx";
|
||||
|
||||
:::info Placeholders
|
||||
Placeholders in the code samples below are delimited by angle brackets (i.e. `<placeholder-name>`). You will need to replace them with the values that are correct in _your_ situation for the code samples to run properly.
|
||||
:::
|
||||
|
||||
Unleash allows you to gather [**impression data**](../reference/impression-data) from your feature flags, giving you complete visibility into who checked what flags and when. What you do with this data is entirely up to you, but a common use case is to send it off to an aggregation and analytics service such as [Posthog](https://posthog.com/) or [Google Analytics](https://analytics.google.com/), either just for monitoring purposes or to facilitate [A/B testing](../feature-flag-tutorials/use-cases/a-b-testing).
|
||||
Unleash allows you to gather [impression data](/reference/impression-data) from your feature flags, giving you complete visibility into who checked what flags and when. What you do with this data is entirely up to you, but a common use case is to send it off to an aggregation and analytics service such as [Posthog](https://posthog.com/) or [Google Analytics](https://analytics.google.com/), either just for monitoring purposes or to facilitate [A/B testing](/feature-flag-tutorials/use-cases/a-b-testing).
|
||||
|
||||
This guide will take you through everything you need to do in Unleash to facilitate such a workflow. It will show you how to send data to Posthog as an example sink, but the exact same principles will apply to any other service of the same kind.
|
||||
|
||||
@ -22,15 +19,15 @@ We will assume that you have the necessary details for your third-party service:
|
||||
- **where to send the data to**. We'll refer to this in the code samples below as **`<sink-url>`**.
|
||||
- **what format the data needs to be in**. This will determine how you transform the event data before you send it.
|
||||
|
||||
In addition, you'll need to have a flag to record impression data for and an [Unleash client SDK](../reference/sdks) that supports impression data. This guide will use the [JavaScript proxy SDK](/reference/sdks/javascript-browser).
|
||||
In addition, you'll need to have a flag to record impression data for and an [Unleash client SDK](/reference/sdks) that supports impression data. This guide will use the [JavaScript proxy SDK](/reference/sdks/javascript-browser).
|
||||
|
||||
When you have those things sorted, follow the below steps.
|
||||
|
||||
## Step 1: Enable impression data for your feature flag {#step-1}
|
||||
## Enable impression data for your feature flag
|
||||
|
||||
Because impression data is an **opt-in feature**, the first step is to enable it for the feature you want to gather data from. You can use both the UI and the API.
|
||||
|
||||
### Enabling impression data for new feature flags {#step-1-new-toggles}
|
||||
### Enable impression data for new feature flags
|
||||
|
||||
When you create a new feature flag via the UI, there's an option at the end of the form that you must enable:
|
||||
|
||||
@ -45,7 +42,7 @@ To create a feature flag with impression data enabled, set the `impressionData`
|
||||
title="Create a feature flag with impression data enabled."
|
||||
/>
|
||||
|
||||
### Enabling impression data for existing feature flags {#step-1-existing-toggles}
|
||||
### Enable impression data for existing feature flags
|
||||
|
||||
To enable impression data for an existing flag, go to the "Settings" tab of that feature flag and use the "edit" button near the Feature information title in the admin UI. It will take you to a form that looks like the flag creation form. Use the "Enable impression data" flag to enable it, the same way you would when [enabling impression data for a new feature flag](#step-1-new-toggles).
|
||||
|
||||
@ -60,9 +57,9 @@ To enable impression data for an existing flag, use the [API's flag patching fun
|
||||
title="Enable impression data on an existing flag."
|
||||
/>
|
||||
|
||||
## Step 2: Capture impression events in your client {#step-2}
|
||||
## Capture impression events in your client
|
||||
|
||||
In your client SDK, initialize the library for the third party service you're using.
|
||||
In your client SDK, initialize the library for the third-party service you're using.
|
||||
For the full details on setting up a Posthog client, see [the official Posthog JavaScript client docs](https://posthog.com/docs/integrate/client/js).
|
||||
The steps below will use extracts from said documentation.
|
||||
|
@ -0,0 +1,52 @@
|
||||
---
|
||||
title: Create and configure a feature flag
|
||||
description: 'This guide shows you how to create feature flags in Unleash and how to add constraints, segments, variants, and more.'
|
||||
slug: /how-to-create-feature-flag
|
||||
pagination_next: how-to/how-to-schedule-feature-releases
|
||||
---
|
||||
|
||||
[Feature flags](../reference/feature-toggles) are a foundational component of Unleash, enabling you to manage features dynamically. This guide details the process of creating and configuring feature flags within Unleash. You'll learn how to create flags, define activation strategies, enable them, and optionally refine their behavior with constraints, segments, and variants.
|
||||
|
||||
This guide focuses on the Unleash Admin UI, but you can also use the [Admin API](/reference/api/unleash/create-feature) to create, update, and manage feature flags.
|
||||
|
||||
## Create a feature flag
|
||||
|
||||
To create a feature flag in the Admin UI, do the following:
|
||||
|
||||
1. Go to a project and click **Create feature flag**.
|
||||
2. Enter a unique name for the flag and click **Create feature flag**.
|
||||
|
||||
## Add an activation strategy
|
||||
|
||||
[Activation strategies](/reference/activation-strategies) determine how and for whom a feature flag is enabled within a specific environment (for example, development, or production). To add an activation strategy to your feature flag in the Admin UI, do the following:
|
||||
|
||||
1. Go to a feature flag, and select the [environment](/reference/environments) where you want to configure the flag.
|
||||
2. Click **Add strategy** for that environment.
|
||||
3. In the **General** tab, select your rollout percentage. Optionally, you can set the strategy status to **Inactive** if you don't yet want the strategy to be exposed.
|
||||
4. Click **Save strategy**.
|
||||
|
||||
## Enable the feature flag in an environment
|
||||
|
||||
To enable the feature flag in an environment, use the main environment toggle to enable the flag in the environment. This ensures that the feature flag is evaluated using the rules of its activation strategies and the provided [Unleash context](/reference/unleash-context).
|
||||
|
||||
## Refine targeting with constraints and segments
|
||||
|
||||
[Strategy constraints](/reference/activation-strategies#constraints) and [segments](../reference/segments) allow you to apply fine-grained filters to your activation strategies, ensuring they only activate for users and applications matching specific criteria. You can add constraints or segments when creating or editing an existing activation strategy.
|
||||
|
||||
1. Go to the feature flag and the environment containing the strategy you want to modify and click the **Edit strategy**.
|
||||
2. In the Targeting Tab, click **Add constraint**.
|
||||
3. To define the constraint, add a context field, an operator, and values to compare against.
|
||||
4. Add more constraints if needed, and click **Save strategy**.
|
||||
|
||||
[Segments](/reference/segments) work similarly to constraints, but they are a reusable set of constraints that you define once and can reuse across flags.
|
||||
|
||||
### Configure strategy variants
|
||||
|
||||
[Variants](../reference/feature-toggle-variants) give you the ability to further target your users and split them into groups of your choosing, such as for A/B testing.
|
||||
|
||||
1. Go to the feature flag and the environment containing the strategy you want to modify and click the **Edit strategy**.
|
||||
2. In the **Variants** tab, click **Add variant**.
|
||||
3. Enter the variant name and an optional payload.
|
||||
4. Optionally, click **Add variant** again to add more variants.
|
||||
5. Toggle **Custom percentage** for fixed weights, if required.
|
||||
6. Click **Save strategy**.
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Scaling Unleash for Enterprise Workloads
|
||||
title: Scaling Unleash for enterprise workloads
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Feature Flag Security and Compliance for Enterprises
|
||||
title: Feature flag security and compliance for enterprises
|
||||
slug: /feature-flag-tutorials/use-cases/security-and-compliance
|
||||
---
|
||||
|
||||
@ -42,7 +42,7 @@ Your developers and other stakeholders need to securely access platforms used to
|
||||
|
||||
To use single sign-on in Unleash, your users can authenticate themselves through OpenID Connect (OIDC) or SAML 2.0 protocols.
|
||||
|
||||
We have integration guides to connect Unleash to enterprise identity providers like Okta, Microsoft Entra ID, and Keycloak, but you can use any identity provider that uses OIDC or SAML 2.0 protocol. Read our [how-to guide for single sign-on](/how-to/sso).
|
||||
We have integration guides to connect Unleash to enterprise identity providers like Okta, Microsoft Entra ID, and Keycloak, but you can use any identity provider that uses OIDC or SAML 2.0 protocol. Read our [how-to guide for single sign-on](/how-to/how-to-add-sso-open-id-connect).
|
||||
|
||||

|
||||
|
||||
@ -65,7 +65,7 @@ By enabling [SCIM](/reference/scim) in Unleash, you can:
|
||||
- Sync group membership.
|
||||
- Ensure consistent access across multiple platforms.
|
||||
|
||||
To unlock these benefits, set up [SCIM for automatic provisioning using our how-to guides](/how-to/provisioning).
|
||||
To unlock these benefits, set up [SCIM for automatic provisioning using our how-to guides](/how-to/how-to-setup-provisioning-with-okta).
|
||||
|
||||
## Configure role-based access control for administrators and developers
|
||||
|
||||
@ -88,7 +88,7 @@ Unleash is built with many mechanisms in place to handle all of these scenarios.
|
||||
|
||||
Let’s look at how Unleash gives you complete control over user roles and permissions. At a high level, there are multiple [predefined roles](/reference/rbac#predefined-roles) in Unleash for you to get started with. Root roles control permissions to top-level resources, spanning across all projects. Project roles, on the other hand, control permissions for a project, the feature flags, and individual configurations per environment.
|
||||
|
||||
The three predefined root roles are: Admin, Editor, and Viewer. The predefined project roles are Owner and Member. In addition to these, you can also create [custom root](/how-to/how-to-create-and-assign-custom-root-roles) or [project roles](/how-to/how-to-create-and-assign-custom-project-roles). The following diagram provides a visual overview of how root roles and project roles compare.
|
||||
The three predefined root roles are: Admin, Editor, and Viewer. The predefined project roles are Owner and Member. In addition to these, you can also create [custom root roles](/reference/rbac#create-and-assign-a-custom-root-role) or [project roles](/reference/rbac#create-and-assign-a-custom-project-role). The following diagram provides a visual overview of how root roles and project roles compare.
|
||||
|
||||

|
||||
|
||||
@ -171,7 +171,7 @@ For more advanced implementations, integrate Unleash event logs directly into br
|
||||
|
||||
### Leverage access logs for broader auditing
|
||||
|
||||
Let’s think back to the importance of user management that we covered earlier. Developers and other stakeholders go through onboarding to use the platform. Authentication protocols and user provisioning ensure these processes are secure, unified, and automated. During this process, access logs keep track of what users and systems accessed Unleash and what actions they performed, including [Unleash API interactions](/reference/api/unleash) from your services and applications. You can export these logs to S3 buckets for long-term data storage. This is valuable if you need to preserve data for complying with legal or regulatory compliance, storing critical backups for disaster recovery, and archiving.
|
||||
Let’s think back to the importance of user management that we covered earlier. Developers and other stakeholders go through onboarding to use the platform. Authentication protocols and user provisioning ensure these processes are secure, unified, and automated. During this process, access logs keep track of what users and systems accessed Unleash and what actions they performed, including [Unleash API interactions](/api-overview) from your services and applications. You can export these logs to S3 buckets for long-term data storage. This is valuable if you need to preserve data for complying with legal or regulatory compliance, storing critical backups for disaster recovery, and archiving.
|
||||
|
||||
Auditing your feature flag system is made simple for traceability and reportability with Unleash’s event logs and access logs. We recommend leveraging these features as data sources for third-party services that make your data a valuable asset for security reviews, meeting compliance standards, and overall risk mitigation.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: How to do Trunk-Based Development using Feature Flags
|
||||
title: Implement trunk-based development using feature flags
|
||||
slug: /feature-flag-tutorials/use-cases/trunk-based-development
|
||||
---
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
---
|
||||
title: How to Implement User Management, Access Controls, and Auditing with Feature Flags
|
||||
title: Implement user management, access controls, and auditing with feature flags
|
||||
slug: /feature-flag-tutorials/use-cases/user-management-access-controls-auditing
|
||||
pagination_next: feature-flag-tutorials/use-cases/security-compliance
|
||||
---
|
||||
|
||||
Feature flags are a game-changer for how software teams build, test, and release products. They enable you to roll out new features with confidence, manage risk, and keep your software development agile and secure.
|
||||
@ -19,7 +20,7 @@ In this tutorial, you will:
|
||||
|
||||
When an enterprise like a global banking platform considers implementing feature flags, keeping track of who can access your feature flag platform and handling authentication is critical. Traditional username and password approaches are insecure and [shared accounts pose a security risk](https://www.getunleash.io/blog/stop-sharing-accounts).
|
||||
|
||||
To ensure proper user authentication and reduce risk exposure, Unleash provides [single sign-on](/how-to/sso) as the recommended centralized method for managing user access.
|
||||
To ensure proper user authentication and reduce risk exposure, Unleash provides [single sign-on](/how-to/how-to-add-sso-open-id-connect) as the recommended centralized method for managing user access.
|
||||
|
||||
Unleash supports any SSO option through OpenID Connect or SAML 2.0, including identity providers like Okta, Microsoft Entra ID, and Keycloak to create a unified authentication process.
|
||||
|
||||
@ -97,7 +98,7 @@ Viewers can observe projects and flags, but cannot make changes. When added to a
|
||||
|
||||
Project permissions are separated from root permissions to make it even more targeted regarding what permissions someone can and cannot have in Unleash.
|
||||
|
||||
For more fine-tuned access controls, create [custom root roles](/how-to/how-to-create-and-assign-custom-root-roles) and [custom project roles](/how-to/how-to-create-and-assign-custom-project-roles), where you can define the privileges and limitations beyond the predefined roles we have built into Unleash.
|
||||
For more fine-tuned access controls, create [custom root roles](/reference/rbac#create-and-assign-a-custom-root-role) and [custom project roles](/reference/rbac#create-and-assign-a-custom-project-role), where you can define the privileges and limitations beyond the predefined roles we have built into Unleash.
|
||||
|
||||
For example, customize root permissions to perform CRUD operations for:
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: How to add SSO with SAML 2.0 and Microsoft Entra ID
|
||||
title: Set up SSO with SAML 2.0 and Microsoft Entra ID
|
||||
description: 'Configure Microsoft Entra ID SSO with SAML 2.0 for your Unleash instance.'
|
||||
---
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 'How to add SSO with Google'
|
||||
title: 'Set up SSO with Google'
|
||||
description: Set up SSO for Unleash with Google.
|
||||
---
|
||||
|
||||
:::caution Deprecation notice
|
||||
|
@ -1,5 +1,7 @@
|
||||
---
|
||||
title: How to add SSO with OpenID Connect
|
||||
title: Set up SSO with OpenID Connect
|
||||
description: Set up SSO for Unleash with OpenID Connect.
|
||||
pagination_next: how-to/how-to-add-sso-saml
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
|
@ -1,5 +1,6 @@
|
||||
---
|
||||
title: How to add SSO with SAML 2.0 Keycloak
|
||||
title: Set up SSO with SAML 2.0 and Keycloak
|
||||
description: Set up SSO for Unleash with SAML 2.0 and Keycloak.
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
|
@ -1,5 +1,6 @@
|
||||
---
|
||||
title: How to add SSO with SAML 2.0 Okta
|
||||
title: Set up SSO with SAML 2.0 and Okta
|
||||
description: Set up SSO for Unleash with SAML 2.0 and Okta.
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
|
@ -1,25 +0,0 @@
|
||||
---
|
||||
title: How to add new users to your Unleash instance
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
|
||||
**Version**: `4.0+`
|
||||
|
||||
:::
|
||||
|
||||
|
||||
You can add new users to Unleash in `Admin > Users`.
|
||||
|
||||
1. From the top-line menu – click on the “Settings Wheel” then click on “Users”.
|
||||

|
||||
|
||||
|
||||
2. To add a new user to your Unleash instance, use the "new user" button:
|
||||

|
||||
|
||||
3. Fill out the required fields in the "create user" form. Refer to the [predefined roles overview](../reference/rbac.md#predefined-roles) for more information on roles.
|
||||
|
||||

|
||||
|
||||
If you have configured an email server the user will receive the invite link in her inbox, otherwise you should share the magic invite link to Unleash presented in the confirmation dialogue.
|
@ -1,52 +0,0 @@
|
||||
---
|
||||
title: How to create and assign custom project roles
|
||||
---
|
||||
|
||||
import VideoContent from '@site/src/components/VideoContent.jsx'
|
||||
|
||||
:::note Availability
|
||||
|
||||
**Plan**: [Enterprise](https://www.getunleash.io/pricing) | **Version**: `4.6+`
|
||||
|
||||
:::
|
||||
|
||||
|
||||
This guide takes you through [how to create](#creating-custom-project-roles "how to create custom project roles") and [assign](#assigning-custom-project-roles "how to assign custom project roles") [custom project roles](../reference/rbac.md#custom-project-roles). Custom project roles allow you to fine-tune access rights and permissions within your projects.
|
||||
|
||||
<VideoContent videoUrls={["https://www.youtube.com/embed/2BlckVMHxgE"]}/>
|
||||
|
||||
## Creating custom project roles
|
||||
|
||||
It takes about three steps to create custom project roles:
|
||||
|
||||
1. Navigate to the custom project roles page by using the admin menu (the gear symbol) and navigating to users.
|
||||

|
||||
2. Navigate to the "Project roles" tab.
|
||||

|
||||
3. Use the "New project role" button to open the role creation form.
|
||||

|
||||
4. Give the role a name, an optional description, and the set of permissions you'd like it to have. For a full overview of all the options, consult the [custom project roles reference documentation](../reference/rbac.md#custom-project-roles).
|
||||

|
||||
|
||||
<VideoContent videoUrls={["https://www.youtube.com/embed/IqaD8iGxkwk"]}/>
|
||||
|
||||
## Assigning custom project roles
|
||||
|
||||
:::note Availability
|
||||
|
||||
**Plan**: [Enterprise](https://www.getunleash.io/pricing) | **Version**: `5.6+`.
|
||||
|
||||
:::
|
||||
|
||||
Assigning a custom project role is a pretty straightforward process and requires three steps, outlined below.
|
||||
|
||||
To assign a custom project role to a user:
|
||||
1. Navigate to the project you want to assign the user a role in.
|
||||

|
||||
2. Navigate to the project's _access_ tab.
|
||||

|
||||
3. This step depends on whether the user has already been added to the project or not:
|
||||
- If the user has already been added to the project, click on the edit icon corresponding with its line and from the overlay that will show up select the new role you want to assign it from the dropdown and save the changes.
|
||||

|
||||
- If the user _hasn't_ been added to the project, add them using the button 'Assign user/group'. From the overlay that will show up select the user, assign it a role and save the changes. Now you should be able to see the new user in the table.
|
||||

|
@ -1,36 +0,0 @@
|
||||
---
|
||||
title: How to create and assign custom root roles
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
|
||||
**Plan**: [Enterprise](https://www.getunleash.io/pricing) | **Version**: `5.4+`
|
||||
|
||||
:::
|
||||
|
||||
|
||||
This guide takes you through [how to create](#creating-custom-root-roles "how to create custom root roles") and [assign](#assigning-custom-root-roles "how to assign custom root roles") [custom root roles](../reference/rbac.md#custom-root-roles). Custom root roles allow you to fine-tune access rights and permissions to root resources in your Unleash instance.
|
||||
|
||||
## Creating custom root roles
|
||||
|
||||
### Step 1: Navigate to the custom root roles page {#create-step-1}
|
||||
|
||||
Navigate to the _roles_ page in the admin UI (available at the URL `/admin/roles`). Use the _settings_ button in the navigation menu and select "roles".
|
||||
|
||||

|
||||
|
||||
### Step 2: Click the "new root role" button. {#create-step-2}
|
||||
|
||||
Use the "new root role" button to open the "new root role" form.
|
||||
|
||||

|
||||
|
||||
### Step 3: Fill in the root role form {#create-step-3}
|
||||
|
||||
Give the root role a name, a description, and the set of permissions you'd like it to have. For a full overview of all the options, consult the [custom root roles reference documentation](../reference/rbac.md#custom-root-roles).
|
||||
|
||||

|
||||
|
||||
## Assigning custom root roles
|
||||
|
||||
You can assign custom root roles just like you would assign any other [predefined root role](../reference/rbac.md#predefined-roles). Root roles can be assigned to users, [service accounts](../reference/service-accounts.md), and [groups](../reference/rbac.md#user-groups).
|
@ -1,44 +0,0 @@
|
||||
---
|
||||
title: How to create and display banners
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
|
||||
**Plan**: [Enterprise](https://www.getunleash.io/pricing) | **Version**: `5.7+`
|
||||
|
||||
:::
|
||||
|
||||
|
||||
This guide takes you through [how to create](#creating-banners "how to create banners") and [display](#displaying-banners "how to display banners") [banners](../reference/banners.md).
|
||||
|
||||
## Creating banners
|
||||
|
||||
### Step 1: Navigate to the banners page {#create-step-1}
|
||||
|
||||
Navigate to the _banners_ page in the admin UI (available at the URL `/admin/banners`). Use the _settings_ button in the navigation menu and select "banners".
|
||||
|
||||

|
||||
|
||||
### Step 2: Use the "new banner" button {#create-step-2}
|
||||
|
||||
Use the "new banner" button to open the "new banner" form.
|
||||
|
||||

|
||||
|
||||
### Step 3: Fill in the banner form {#create-step-3}
|
||||
|
||||
Choose whether the banner should be enabled right away. If enabled, the banner will be visible to all users in your Unleash instance. Select the banner type, icon, and write the message that you'd like to see displayed on the banner. The message and dialog fields support [Markdown](https://www.markdownguide.org/basic-syntax/). Optionally, you can also configure a banner action for user interactivity. For a full overview of all the banner options, consult the [banners reference documentation](../reference/banners).
|
||||
|
||||
You'll be able to preview the banner at the top as you fill in the form.
|
||||
|
||||
Once you're satisfied, use the "add banner" button to create the banner.
|
||||
|
||||

|
||||
|
||||
## Displaying banners
|
||||
|
||||
You can choose whether a banner is currently displayed to all users of your Unleash instance by toggling the "enabled" switch on the banner table.
|
||||
|
||||
Alternatively, you can edit the banner by using the "edit" button on the banner table and then toggle the "banner status" switch.
|
||||
|
||||

|
@ -1,81 +0,0 @@
|
||||
---
|
||||
title: How to create and manage user groups
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
|
||||
**Plan**: [Enterprise](https://www.getunleash.io/pricing) | **Version**: `4.14+`
|
||||
|
||||
:::
|
||||
|
||||
This guide takes you through how to use user groups to manage permissions on your projects. User groups allow you to manage large groups of users more easily than assigning roles directly to those users. Refer to the section on [user groups](../reference/rbac.md#user-groups) in the RBAC documentation for more information.
|
||||
|
||||
## Creating user groups
|
||||
|
||||
1. Navigate to groups by using the admin menu (the gear icon) and selecting the groups option.
|
||||
|
||||

|
||||
|
||||
2. Navigate to new group.
|
||||
|
||||

|
||||
|
||||
3. Give the group a name, an optional description, an optional root role, and select the users you'd like to be in the group.
|
||||
|
||||

|
||||
|
||||
4. Review the details of the group and save them if you're happy.
|
||||
|
||||

|
||||
|
||||
## Managing users within a group
|
||||
|
||||
1. Navigate to groups by using the admin menu (the gear icon) and selecting the groups option.
|
||||
|
||||

|
||||
|
||||
2. Select the card of the group you want to edit.
|
||||
|
||||

|
||||
|
||||
3. Remove users by using the remove user button (displayed as a bin).
|
||||
|
||||

|
||||
|
||||
4. Confirm the remove.
|
||||
|
||||

|
||||
|
||||
5. Add users by selecting the add button.
|
||||
|
||||

|
||||
|
||||
6. Find the user you'd like to add to the group and select them.
|
||||
|
||||

|
||||
|
||||
7. Review the group users and save when you're happy.
|
||||
|
||||

|
||||
|
||||
## Assigning groups to projects
|
||||
|
||||
1. Navigate to projects
|
||||
|
||||

|
||||
|
||||
2. Select the project you want to manage.
|
||||
|
||||

|
||||
|
||||
3. Navigate to the access tab and then use the assign user/group button.
|
||||
|
||||

|
||||
|
||||
4. Find your group in the drop down.
|
||||
|
||||

|
||||
|
||||
5. Select the role that the group should have in this project. You can review the list of permissions that the group users will gain by having this role before confirming.
|
||||
|
||||

|
@ -2,9 +2,21 @@
|
||||
title: How to create API Tokens
|
||||
---
|
||||
|
||||
Depending on your [permissions](../reference/api-tokens-and-client-keys#api-token-permissions), you can create API tokens in the Unleash Admin UI in three ways:
|
||||
All Unleash APIs require authentication using an [API token](/reference/api-tokens-and-client-keys). The type of token you use depends on the API you are accessing and your specific use case.
|
||||
|
||||
### Token types
|
||||
|
||||
Unleash supports four types of API tokens:
|
||||
- **Client tokens**: Used to connect server-side SDKs, Unleash Edge, and the Unleash Proxy to the Client API. Can be scoped to a specific project and environment.
|
||||
- **Frontend tokens**: Used to connect client-side SDKs to the Frontend API or Unleash Edge. These tokens are designed to be publicly accessible and have limited permissions. Can be scoped to a specific project and environment.
|
||||
- **Personal access tokens**: Tied to a specific user account. They are useful for testing, debugging, or providing temporary access to tools and scripts that need to interact with the Admin API.
|
||||
- **Service account tokens**: The recommended method for providing API access to integrations, automation tools, and other non-human users. Service accounts provide a more secure and manageable way to grant Admin API access.
|
||||
|
||||
### Create an API token
|
||||
|
||||
Depending on your permissions, you can create API tokens in the Unleash Admin UI in four ways:
|
||||
|
||||
- **Admin settings > Access control > API access**: for client or frontend tokens; requires the Admin root role, or a custom root role with API token permissions.
|
||||
- **Settings > API access** inside a project: for project-specific client or frontend tokens; permitted for project Members or users with a [corresponding root role](../reference/api-tokens-and-client-keys#api-token-permissions).
|
||||
- **Admin settings > Service accounts > New service account**: for creating a service account and assigning a token.
|
||||
- **Settings > API access** [inside a project]: for project-specific client or frontend tokens; permitted for project Members or users with a corresponding root role.
|
||||
- **Profile > View profile settings > Personal API tokens**: for personal access tokens.
|
||||
|
||||
|
||||
|
@ -1,134 +0,0 @@
|
||||
---
|
||||
title: How to create a feature flag
|
||||
description: 'This guide shows you how to create feature flags in Unleash and how to add constraints, segments, variants, and more.'
|
||||
slug: /how-to-create-feature-flag
|
||||
---
|
||||
|
||||
[Feature flags](../reference/feature-toggles) are the foundation of Unleash. They are at the core of everything we do and are a fundamental building block in any feature management system. This guide shows you how to create feature flags in Unleash and how to add any optional constraints, segments, variants, and more. Links to learn more about these concepts will be scattered throughout the text.
|
||||
|
||||
You can perform every action both via the UI and the admin API. This guide includes screenshots to highlight the relevant UI controls and links to the relevant API methods for each step.
|
||||
|
||||
This guide is split into three sections:
|
||||
|
||||
1. [Prerequisites](#prerequisites): you need these before you can create a flag.
|
||||
2. [Required steps](#required-steps): all the required steps to create a flag and activate it in production.
|
||||
3. [Optional steps](#optional-steps): optional steps you can take to further target and configure your feature flag and its audience.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To perform all the steps in this guide, you will need:
|
||||
|
||||
- A running Unleash instance
|
||||
- A project to hold the flag
|
||||
- A user with an **editor** or **admin** role OR a user with the following permissions inside the target project:
|
||||
- **[project-level permissions](../reference/rbac#project-permissions)**
|
||||
- create feature flags
|
||||
- **[environment-level permissions](../reference/rbac#environment-permissions)**
|
||||
- create/edit variants[^1]
|
||||
- create activation strategies
|
||||
- update activation strategies
|
||||
- enable/disable flags
|
||||
|
||||
:::info roles
|
||||
|
||||
Refer to [the documentation on role-based access control](../reference/rbac) for more information about the available roles and their permissions.
|
||||
|
||||
:::
|
||||
|
||||
## Required steps
|
||||
|
||||
This section takes you through the required steps to create and activate a feature flag. It assumes that you have all the prerequisites from the previous section done.
|
||||
|
||||
### Step 1: Create a flag {#step-1}
|
||||
|
||||
:::tip API: create a flag
|
||||
|
||||
Use the [Admin API endpoint for creating a feature flag](/reference/api/legacy/unleash/admin/features-v2#create-toggle). The payload accepts all the same fields as the Admin UI form. The Admin UI also displays the corresponding cURL command when you use the form.
|
||||
|
||||
:::
|
||||
|
||||
In the project that you want to create the flag in, use the "new feature flag" button and fill the form out with your desired configuration. Refer to the [feature flag reference documentation](../reference/feature-toggles) for the full list of configuration options and explanations.
|
||||
|
||||

|
||||
|
||||
### Step 2: Add a strategy {#step-2}
|
||||
|
||||
:::tip API: Add a strategy
|
||||
|
||||
Use the [API for adding a strategy to a feature flag](/reference/api/legacy/unleash/admin/features-v2#add-strategy). You can find the configuration options for each strategy in the [activation strategy reference documentation](../reference/activation-strategies).
|
||||
|
||||
:::
|
||||
|
||||
Decide which environment you want to enable the flag in. Select that environment and add an activation strategy. Different activation strategies will act differently as described in the [activation strategy documentation](../reference/activation-strategies). The configuration for each strategy differs accordingly. After selecting a strategy, complete the steps to configure it.
|
||||
|
||||

|
||||
|
||||
### Step 3: Enable the flag {#step-3}
|
||||
|
||||
:::tip API: Enable a flag
|
||||
|
||||
Use the [API for enabling an environment for a flag](/reference/api/legacy/unleash/admin/features-v2#enable-env) and specify the environment you'd like to enable.
|
||||
|
||||
:::
|
||||
|
||||
Use the environments flags to switch on the environment that you chose above. Depending on the activation strategy you added in the previous step, the flag should now evaluate to true or false depending on the Unleash context you provide it.
|
||||
|
||||

|
||||
|
||||
## Optional steps
|
||||
|
||||
These optional steps allow you to further configure your feature flags to add optional payloads, variants for A/B testing, more detailed user targeting and exceptions/overrides.
|
||||
|
||||
### Add constraints and segmentation
|
||||
|
||||
Constraints and segmentation allow you to set filters on your strategies, so that they will only be evaluated for users and applications that match the specified preconditions. Refer to the [strategy constraints](../reference/activation-strategies#constraints) and [segments reference documentation](../reference/segments) for more information.
|
||||
|
||||
To add constraints and segmentation, use the "edit strategy" button for the desired strategy.
|
||||
|
||||

|
||||
|
||||
#### Constraints
|
||||
|
||||
:::info
|
||||
|
||||
Constraints aren't fixed and can be changed later to further narrow your audience. You can add them either when you add a strategy to a flag or at any point thereafter.
|
||||
|
||||
:::
|
||||
|
||||
:::tip API: Add constraints
|
||||
|
||||
You can either [add constraints when you add the strategy](/reference/api/unleash/add-feature-strategy) or [PUT](/reference/api/unleash/update-feature-strategy) or [PATCH](/reference/api/unleash/patch-feature-strategy) the strategy later.
|
||||
|
||||
:::
|
||||
|
||||
In the strategy configuration screen for the strategy that you want to configure, use the "add constraint" button to add a strategy constraint.
|
||||
|
||||

|
||||
|
||||
#### Segments
|
||||
|
||||
:::info
|
||||
|
||||
This can be done after you have created a strategy.
|
||||
|
||||
:::
|
||||
|
||||
:::tip API: add segments
|
||||
|
||||
Use the [API for adding segments to a strategy](/reference/api/unleash/update-feature-strategy-segments) to add segments to your strategy.
|
||||
|
||||
:::
|
||||
|
||||
In the strategy configuration screen for the strategy that you want to configure, use the "select segments" dropdown to add segments.
|
||||
|
||||

|
||||
|
||||
### Add variants
|
||||
|
||||
:::info
|
||||
|
||||
This can be done at any point, during or after the creation of your flag.
|
||||
|
||||
:::
|
||||
|
||||
[Variants](../reference/strategy-variants) give you the ability to further target your users and split them into groups of your choosing, such as for A/B testing. On the flag overview page, select the variants tab. Use the "new variant" button to add the variants that you want.
|
@ -1,23 +0,0 @@
|
||||
---
|
||||
title: How to download your login history
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
|
||||
**Plan**: [Enterprise](https://www.getunleash.io/pricing) | **Version**: `4.22+`
|
||||
|
||||
:::
|
||||
|
||||
[Login history](../reference/login-history.md) enables Unleash admins to audit login events and their respective information, including whether they were successful or not.
|
||||
|
||||
## Step 1: Navigate to the login history page {#step-1}
|
||||
|
||||
Navigate to the _login history_ page in the admin UI (available at the URL `/admin/logins`). Use the _settings_ button in the navigation menu and select "login history".
|
||||
|
||||

|
||||
|
||||
## Step 2: Click the "Download login history" button {#step-2}
|
||||
|
||||
Use the "download login history" button to proceed with the download of the login history as CSV.
|
||||
|
||||

|
@ -1,125 +0,0 @@
|
||||
---
|
||||
title: How to enable OpenAPI and the Swagger UI
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
:::note Availability
|
||||
|
||||
**Unleash version**: `4.13+` | **Unleash Proxy version**: `0.10+`
|
||||
|
||||
:::
|
||||
|
||||
Both Unleash and the Unleash proxy have included OpenAPI schemas and Swagger UIs for their APIs. The schemas can be used to get an overview of all API operations and to generate API clients using OpenAPI client generators. The Swagger UI lets you see and try out all the available API operations directly in your browser.
|
||||
|
||||
To enable the OpenAPI documentation and the Swagger UI, you must start Unleash or the proxy with the correct configuration option. The following section shows you how. The methods are the same for both Unleash and the Unleash proxy, so the steps described in the next section will work for either.
|
||||
|
||||
## Location of the OpenAPI spec
|
||||
|
||||
Once you enable OpenAPI, you can find the specification in JSON format at `/docs/openapi.json` and the swagger UI at `/docs/openapi`.
|
||||
|
||||
For instance, if you're running the Unleash server locally at `http://localhost:4242`, then
|
||||
|
||||
- the JSON specification will be at `http://localhost:4242/docs/openapi.json`
|
||||
- the Swagger UI will be at `http://localhost:4242/docs/openapi`
|
||||
|
||||
Similarly, if you're running the Unleash proxy locally at `http://localhost:3000` (so that the proxy endpoint is at `http://localhost:3000/proxy`), then
|
||||
|
||||
- the JSON specification will be at `http://localhost:3000/docs/openapi.json`
|
||||
- the Swagger UI will be at `http://localhost:3000/docs/openapi`
|
||||
|
||||
## Step 1: enable OpenAPI
|
||||
|
||||
The OpenAPI spec and the Swagger UI can be turned on either via environment variables or via configuration options. Configuration options take precedence over environment variables.
|
||||
|
||||
If you are using Unleash v5.2.0, OpenAPI is enabled by default. You still need to enable it for Unleash proxy.
|
||||
|
||||
### Enable OpenAPI via environment variables
|
||||
|
||||
To turn on OpenAPI via environment variables, set the `ENABLE_OAS` to `true` in the environment you're running the server in.
|
||||
|
||||
<Tabs groupId="openapi-env-configuration">
|
||||
|
||||
<TabItem value="bash" label="Environment variable (bash)" default>
|
||||
|
||||
```bash title="Enable OpenAPI via an environment variable"
|
||||
export ENABLE_OAS=true
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="docker-unleash" label="Docker (Unleash)">
|
||||
|
||||
```bash title="Enable OpenAPI for Unleash via Docker"
|
||||
docker run \
|
||||
// highlight-next-line
|
||||
-e ENABLE_OAS=true \ # other variables omitted for brevity
|
||||
unleashorg/unleash-server
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="docker-proxy" label="Docker (Unleash proxy)">
|
||||
|
||||
```bash title="Enable OpenAPI for the Unleash proxy via Docker"
|
||||
docker run \
|
||||
// highlight-next-line
|
||||
-e ENABLE_OAS=true \ # other variables omitted for brevity
|
||||
unleashorg/unleash-proxy
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
### Enable OpenAPI via configuration options
|
||||
|
||||
The configuration option for enabling OpenAPI and the swagger UI is `enableOAS`. Set this option to `true`.
|
||||
|
||||
The following examples have been shortened to show only the relevant configuration options. For more detailed instructions, refer to [our self-hosting guide](/using-unleash/deploy/getting-started).
|
||||
|
||||
<Tabs groupId="openapi-configuration">
|
||||
|
||||
<TabItem value="unleash" label="Unleash">
|
||||
|
||||
```js title="Enable OpenAPI for Unleash via configuration option"
|
||||
const unleash = require('unleash-server');
|
||||
|
||||
unleash
|
||||
.start({
|
||||
// ... Other options emitted for brevity
|
||||
// highlight-next-line
|
||||
enableOAS: true,
|
||||
})
|
||||
.then((unleash) => {
|
||||
console.log(
|
||||
`Unleash started on http://localhost:${unleash.app.get('port')}`,
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="proxy" label="Unleash proxy">
|
||||
|
||||
```js title="Enable OpenAPI for the Unleash proxy via configuration"
|
||||
const port = 3000;
|
||||
|
||||
const { createApp } = require('@unleash/proxy');
|
||||
|
||||
const app = createApp({
|
||||
// ... Other options elided for brevity
|
||||
// highlight-next-line
|
||||
enableOAS: true,
|
||||
});
|
||||
|
||||
app.listen(port, () =>
|
||||
console.log(`Unleash Proxy listening on http://localhost:${port}/proxy`),
|
||||
);
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Environment Import & Export
|
||||
title: Environment import and export
|
||||
---
|
||||
|
||||
|
||||
@ -136,7 +136,7 @@ If change requests are enabled, any permissions for **Create activation strategi
|
||||
|
||||
## Environment import/export vs the instance import/export API
|
||||
|
||||
Environment import/export has some similarities to the [instance import/export API](./how-to-import-export), but they serve different purposes.
|
||||
Environment import/export has some similarities to the [instance import/export API](/reference/environment-import-export), but they serve different purposes.
|
||||
|
||||
The instance import/export API was designed to export all feature flags (optionally with strategies and projects) from one Unleash instance to another. When it was developed, Unleash had much fewer features than it does now. As such, the API lacks support for some of the more recent features in Unleash.
|
||||
|
||||
|
@ -1,26 +0,0 @@
|
||||
---
|
||||
title: How to manage public invite tokens
|
||||
---
|
||||
|
||||
[Public invite links](../reference/public-signup.mdx) let you invite new members to an Unleash instance. A key part of an invite link is the public invite token. This guide shows you how to use the Unleash admin UI to create, update, and delete public invite tokens. You can also [manage public signup tokens via the Unleash API](../reference/api/unleash/public-signup-tokens.tag.mdx).
|
||||
|
||||
Only Unleash instance admins have the necessary permissions to create and manage public invite tokens.
|
||||
|
||||
## Creating a token
|
||||
|
||||
1. Navigate to the **users** page in Unleash and use the **create invite link** button
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
2. Fill out the "create invite link" form and (optionally) copy the invite link. You can always get the link later.
|
||||

|
||||
|
||||

|
||||
|
||||
## Updating/Deleting a token
|
||||
|
||||
1. Follow the steps in [the previous paragraph](#creating-a-token) to navigate to the users page.
|
||||
2. When you have an active invite token, use the button labeled "update invite link".
|
||||
3. Use the form to edit the expiry for the token or to delete it entirely.
|
@ -4,9 +4,9 @@ title: How to run the Unleash Proxy
|
||||
|
||||
import ApiRequest from '@site/src/components/ApiRequest'
|
||||
|
||||
:::info Placeholders
|
||||
:::warning
|
||||
|
||||
Placeholders in the code samples below are delimited by angle brackets (i.e. `<placeholder-name>`). You will need to replace them with the values that are correct in _your_ situation for the code samples to run properly.
|
||||
Unleash Proxy is deprecated. Use [Unleash Edge](/reference/unleash-edge) instead.
|
||||
|
||||
:::
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: How to schedule feature releases
|
||||
title: Schedule feature releases
|
||||
---
|
||||
import ApiRequest from '@site/src/components/ApiRequest'
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: How to Set Up User Group SSO Syncing
|
||||
title: Set up user group SSO syncing
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: How to Setup Entra Provisioning
|
||||
title: Set up Entra provisioning
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
|
@ -1,5 +1,6 @@
|
||||
---
|
||||
title: How to Setup Okta Provisioning
|
||||
title: Set up Okta provisioning
|
||||
pagination_next: how-to/how-to-setup-provisioning-with-entra
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'How to set up Keycloak and Unleash to sync user groups'
|
||||
title: 'Set up user group syncing with Keycloack'
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
|
@ -1,35 +0,0 @@
|
||||
---
|
||||
title: How to use the Admin API
|
||||
---
|
||||
|
||||
This guide explains the steps required to using the Admin API.
|
||||
|
||||
## Create API token
|
||||
|
||||
First, you'll need to create a [personal access token](/reference/api-tokens-and-client-keys.mdx#personal-access-tokens).
|
||||
|
||||
Please note that it may take up to 60 seconds for the new key to propagate to all Unleash instances due to eager caching.
|
||||
|
||||
:::note
|
||||
|
||||
If you need an API token to use in a client SDK you should create a client token instead, as these have fewer access rights.
|
||||
|
||||
:::
|
||||
|
||||
## Use Admin API
|
||||
|
||||
Now that you have an access token with admin privileges, you can use it to make changes in your Unleash instance.
|
||||
|
||||
In the example below we will use the [Unleash Admin API](/reference/api/legacy/unleash/admin/features.md) to enable the `checkout-flow` feature flag in `development` using curl.
|
||||
|
||||
```sh
|
||||
curl -X POST -H "Content-Type: application/json" \
|
||||
-H "Authorization: <your-token>" \
|
||||
https://app.unleash-hosted.com/docs-demo/api/admin/projects/docs-project/features/checkout-flow/environments/development/on
|
||||
```
|
||||
|
||||
We have now enabled the feature flag. We can also verify that it was actually changed by the API user by navigating to [Event Log](/reference/events#event-log) and filtering events for this feature flag.
|
||||
|
||||

|
||||
|
||||
You can find the full documentation on everything the Unleash API supports in the [Unleash API documentation](/reference/api/legacy/unleash/admin/features.md).
|
@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Quickstart
|
||||
pagination_next: topics/what-is-a-feature-flag
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
@ -125,6 +126,6 @@ For examples that show how to connect to Unleash in other programming languages,
|
||||
|
||||
You have successfully connected Unleash to your application. To continue exploring, see the following resources:
|
||||
|
||||
- **Core concepts**: Learn about the [Unleash architecture](/understanding-unleash/unleash-overview), available [hosting options](/understanding-unleash/hosting-options), and other [reference documentation](/reference).
|
||||
- **Core concepts**: Learn about the [Unleash architecture](/understanding-unleash/unleash-overview), available [hosting options](/understanding-unleash/hosting-options), and other [reference documentation](/reference/projects).
|
||||
- **Developer guides**: Explore feature flag [best practices](/topics/feature-flags/feature-flag-best-practices) and [language-specific tutorials](/feature-flag-tutorials/react).
|
||||
- **Join the community**: Have questions or feedback? Join the [Unleash community on Slack](https://slack.unleash.run) to connect with other developers and the Unleash team.
|
||||
- **Join the community**: Have questions or feedback? Join the [Unleash community on Slack](https://slack.unleash.run) to connect with other developers and the Unleash team.
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Activation Strategies
|
||||
title: Activation strategies
|
||||
---
|
||||
|
||||
import VideoContent from '@site/src/components/VideoContent.jsx'
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: API Tokens and Client Keys
|
||||
title: API tokens and client keys
|
||||
pagination_next: reference/front-end-api
|
||||
---
|
||||
|
||||
@ -137,8 +137,9 @@ be44368985f7fb3237c584ef86f3d6bdada42ddbd63a019d26955178
|
||||
|
||||
## Create an API token
|
||||
|
||||
Depending on your [permissions](#api-token-permissions), you can create API tokens in the Unleash Admin UI in three ways:
|
||||
Depending on your [permissions](#api-token-permissions), you can create API tokens in the Unleash Admin UI in four ways:
|
||||
- **Admin settings > Access control > API access**: for client or frontend tokens; requires the Admin root role, or a custom root role with [API token permissions](#api-token-permissions).
|
||||
- **Admin settings > Service accounts > New service account**: for creating a service account and assigning a token.
|
||||
- **Settings > API access** inside a project: for project-specific client or frontend tokens; permitted for project Members or users with a [corresponding root role](#api-token-permissions).
|
||||
- **Profile > View profile settings > Personal API tokens**: for [personal access tokens](#personal-access-tokens).
|
||||
|
||||
|
@ -5,7 +5,7 @@ title: Legacy API Documentation
|
||||
|
||||
:::caution
|
||||
|
||||
The docs in this category are legacy documentation. You should prefer to use the [Unleash OpenAPI docs](/reference/api/unleash) instead whenever possible.
|
||||
These APIs have been deprecared. Wse the [Unleash OpenAPI docs](/api-overview) reference instead.
|
||||
|
||||
:::
|
||||
|
||||
|
@ -8,78 +8,61 @@ title: Banners
|
||||
|
||||
:::
|
||||
|
||||
Banners allow you to configure and display internal messages that all users of your Unleash instance can see and interact with. They are displayed at the top of the Unleash UI, and can be configured to be interactive.
|
||||
Banners allow you to configure and display instance-wide messages to all users of your Unleash instance. These messages appear at the top of the Unleash UI and can be configured to be interactive.
|
||||
|
||||

|
||||
|
||||
A common use case could be to have some pre-configured banners that you can enable when you need to communicate something to your users. For example, you could have a banner that you enable when you're doing maintenance on your Unleash instance, and another banner that you enable when you're running a survey.
|
||||
A common use case for banners is to pre-configure messages that you can enable when needed. For example, you might have a banner for scheduled maintenance or another to announce a user survey.
|
||||
|
||||
In order to create and display a banner, you can follow the [how to create and display banners](../how-to/how-to-create-and-display-banners.md) guide.
|
||||
Banners can be enabled or disabled at any time.
|
||||
|
||||
## Banner status
|
||||
## Create a banner
|
||||
|
||||
Banners can be enabled or disabled at any time. For more information on how to enable or disable a banner, see the section on [displaying banners](../how-to/how-to-create-and-display-banners.md#displaying-banners).
|
||||
To create a banner in the Admin UI, do the following:
|
||||
1. Go to **Admin settings > Instance config > Banners**.
|
||||
2. Click **New banner**.
|
||||
3. Configure the status, type, icon, message, action, and whether the banner should be sticky.
|
||||
4. Click **Add banner**.
|
||||
|
||||
| Option | Description |
|
||||
| ----------- | -------------------------------------------------------------------------------- |
|
||||
| **Enabled** | Whether the banner is currently displayed to all users of your Unleash instance. |
|
||||
Once created, if the banner's status is set to enabled, the banner is immediately visible to all users in your Unleash instance.
|
||||
|
||||
## Configuration
|
||||
## Configure the banner
|
||||
|
||||
Banners can be configured with the following options:
|
||||
| Option | Description | Values / Format |
|
||||
| :------ | :---------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------ |
|
||||
| Type | Sets the banner's style (color and default icon). | `Information`, `Warning`, `Error`, `Success` |
|
||||
| Status | Whether the banner is enabled and showing for all users of the instance. | Enabled or disabled |
|
||||
| Icon | Icon displayed on the banner. | `Default` (matches banner type), `None` (hidden), `Custom` ([custom icon](#use-a-custom-icon)) |
|
||||
| Message | Main text content of the banner. | Text format; supports [Markdown](https://www.markdownguide.org/basic-syntax/) |
|
||||
| Action | Adds interactivity to the banner using a link or a dialog. | `None`, `Link`, `Dialog` |
|
||||
| Sticky | Whether the banner remains fixed at the top of the Unleash UI, even when users scroll the page. | Enabled or disabled |
|
||||
|
||||
| Option | Description |
|
||||
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Type** | The type of banner, which controls the banner's color and its icon, if using the default icon option. |
|
||||
| **Icon** | The icon displayed on the banner. This can be the default for the banner type, a [custom icon](#custom-icon), or hidden by selecting "None". |
|
||||
| **Message** | The banner's message. Supports [Markdown](https://www.markdownguide.org/basic-syntax/). |
|
||||
### Use a custom icon
|
||||
|
||||
### Custom icon
|
||||
To further personalize your banner, you can use any icon from the [Material Symbols](https://fonts.google.com/icons) library.
|
||||
|
||||
To further personalize your banner, you can configure it with a custom icon.
|
||||
To use a custom icon:
|
||||
1. In the banner configuration, select **Custom** from the **Icon** dropdown menu.
|
||||
2. In the **Banner icon** field, enter the name of the desired Material Symbol. For example, to use the "Rocket Launch" icon, enter `rocket_launch`.
|
||||
|
||||
To use a custom icon in your banner:
|
||||
1. Select "Custom" in the icon dropdown.
|
||||
2. Enter the name of the desired [Material Symbol](https://fonts.google.com/icons).
|
||||
- For example, for the "Rocket Launch" icon, enter `rocket_launch` in the custom icon field.
|
||||
### Configure a link action
|
||||
|
||||
| Option | Description |
|
||||
| --------------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| **Custom icon** | The custom icon to be displayed on the banner, using [Material Symbols](https://fonts.google.com/icons). |
|
||||
This action displays a link on the banner that directs users to a specified URL.
|
||||
|
||||
## Banner action
|
||||
- **Absolute URLs** (for example, `https://docs.getunleash.io/`) open in a new browser tab.
|
||||
- **Relative URLs** (for example, `/admin/network`) open in the same tab.
|
||||
|
||||
You can set up an action for your banner:
|
||||
| Option | Description |
|
||||
| :----- | :---------------------------------------------- |
|
||||
| URL | The URL the banner link should navigate to. |
|
||||
| Text | The text displayed for the link on the banner. |
|
||||
|
||||
| Option | Description |
|
||||
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Banner action** | The action activated when a user interacts with the banner link. Defaults to "None". Options include a [link](#link) or a [dialog](#dialog). |
|
||||
### Configure a dialog action
|
||||
|
||||
### Link
|
||||
This action displays a link on the banner that, when clicked, opens a dialog box with additional information.
|
||||
|
||||
When choosing the link action, a link will be displayed on the banner that directs users to a specified URL.
|
||||
|
||||
The configured URL can be absolute, as in e.g. `https://docs.getunleash.io/`, or relative as in e.g. `/admin/network`. Absolute URLs will open in a new tab.
|
||||
|
||||
| Option | Description |
|
||||
| -------- | --------------------------------------------------------- |
|
||||
| **URL** | The URL to open when the user uses the banner link. |
|
||||
| **Text** | The text to display on the banner link. |
|
||||
|
||||
### Dialog
|
||||
|
||||
When opting for a dialog action, an interactable link appears on the banner which opens a dialog with additional information.
|
||||
|
||||
| Option | Description |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------- |
|
||||
| **Text** | The text to display on the banner link. |
|
||||
| **Dialog title** | The title to display on the dialog. |
|
||||
| **Dialog content** | The content to display on the dialog. Supports [Markdown](https://www.markdownguide.org/basic-syntax/). |
|
||||
|
||||
## Sticky banner
|
||||
|
||||
For added visibility, banners can be configured to be "sticky," ensuring they remain at the top of the Unleash UI, even after scrolling the page. This is useful for banners that you want to make sure that your users see and interact with.
|
||||
|
||||
| Option | Description |
|
||||
| ---------- | ---------------------------------------------------------- |
|
||||
| **Sticky** | Whether the banner is sticky on the screen when scrolling. |
|
||||
| Option | Description |
|
||||
| :--------------- | :-------------------------------------------------------------------------- |
|
||||
| Text | The text displayed for the link on the banner. |
|
||||
| Dialog title | The title displayed at the top of the dialog box. |
|
||||
| Dialog content | The main content displayed within the dialog box. Supports [Markdown](https://www.markdownguide.org/basic-syntax/). |
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Change Requests
|
||||
title: Change requests
|
||||
---
|
||||
|
||||
import VideoContent from '@site/src/components/VideoContent.jsx';
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Command Menu
|
||||
title: Command menu
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Custom Activation Strategies
|
||||
title: Custom activation strategies
|
||||
---
|
||||
|
||||
**Custom activation strategies** let you define your own activation strategies to use with Unleash. When the [built-in activation strategies](../reference/activation-strategies.md) aren't enough, custom activation strategies are there to provide you with the flexibility you need.
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'Import & Export'
|
||||
title: Import and export
|
||||
---
|
||||
import ApiRequest from '@site/src/components/ApiRequest'
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Feature Flag Variants (deprecated)
|
||||
title: Feature flag variants (deprecated)
|
||||
---
|
||||
:::warning
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Feature Flags
|
||||
title: Feature flags
|
||||
pagination_next: reference/activation-strategies
|
||||
---
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Impression Data
|
||||
title: Impression data
|
||||
pagination_next: reference/events
|
||||
---
|
||||
|
||||
@ -82,7 +82,7 @@ This table describes all the properties on the impression events:
|
||||
|
||||
Impression data is strictly an **opt-in** feature and must be enabled on a **per-flag basis**. You can enable and disable it both when you create a flag and when you edit a flag.
|
||||
|
||||
You can enable impression data via the impression data flag in the admin UI's flag creation form. You can also go via the [the API, using the `impressionData` option](/reference/api/legacy/unleash/admin/features-v2#create-toggle). For more detailed instructions, see [the section on enabling impression data in the how-to guide for capturing impression data](../how-to/how-to-capture-impression-data#step-1).
|
||||
You can enable impression data via the impression data flag in the admin UI's flag creation form. You can also go via the [the API, using the `impressionData` option](/reference/api/legacy/unleash/admin/features-v2#create-toggle). For more detailed instructions, see [the section on enabling impression data in the how-to guide for capturing impression data](/feature-flag-tutorials/use-cases/how-to-capture-impression-data).
|
||||
|
||||

|
||||
|
||||
|
@ -1,42 +0,0 @@
|
||||
---
|
||||
title: Login History
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
|
||||
**Plan**: [Enterprise](https://www.getunleash.io/pricing) | **Version**: `4.22+`
|
||||
|
||||
:::
|
||||
|
||||
Unleash's login history lets you track login events in your Unleash instance, and whether the attempts were successful in logging in or not.
|
||||
|
||||

|
||||
|
||||
For each login event, it lists:
|
||||
|
||||
- **Created**: When it happened
|
||||
- **Username**: The username that was used
|
||||
- **Authentication**: The authentication type that was used
|
||||
- **IP address**: The IP address that made the attempt
|
||||
- **Success**: Whether the attempt was successful or not
|
||||
- **Failure reason**: If the attempt was not successful, the reason why
|
||||
|
||||
You can see the failure reason by hovering over the "False" badge in the "Success" column.
|
||||
|
||||

|
||||
|
||||
Use the login history to:
|
||||
|
||||
- Audit login events in your Unleash instance
|
||||
- Identify failed login attempts and investigate the cause
|
||||
- Debug misconfigured authentication providers
|
||||
|
||||
The login history is mutable: You can remove individual login events or clear the entire history by deleting all of them.
|
||||
|
||||
Finally, the login history can be downloaded ([how do I download my Unleash login history](../how-to/how-to-download-login-history.mdx)) for external backups, audits, and the like.
|
||||
|
||||
## Retention
|
||||
|
||||
Events in the login history are retained for 336 hours (14 days).
|
||||
|
||||
Events older than the retention period are automatically deleted, and you won't be able to recover them. If you would like to collect login event information past the retention period, we suggest periodically downloading the login history.
|
31
website/docs/reference/login-history.mdx
Normal file
31
website/docs/reference/login-history.mdx
Normal file
@ -0,0 +1,31 @@
|
||||
---
|
||||
title: Login history
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
|
||||
**Plan**: [Enterprise](https://www.getunleash.io/pricing) | **Version**: `4.22+`
|
||||
|
||||
:::
|
||||
|
||||
Login history helps you monitor access to your Unleash instance by tracking login events. It records both successful and unsuccessful login attempts. You can use this information to audit user activity within your instance, identify and investigate failed login attempts, or debug issues related to misconfigured authentication providers.
|
||||
|
||||
You can find the login history in the Admin UI by navigating to **Admin settings > User config > Login history**.
|
||||
|
||||
For each login event, it lists:
|
||||
|
||||
- **Created**: The date and time the login event occurred.
|
||||
- **Username**: The username used for the login attempt.
|
||||
- **Authentication**: The type of authentication method used (for example, `password`, `open-id-connect`).
|
||||
- **IP address**: The IP address from which the login attempt was made.
|
||||
- **Success**: Whether the attempt was successful (`True` for successful).
|
||||
- **Failure reason**: If a login attempt failed, this provides the reason. To see the failure reason, hover over the `False` label in the **Success** column.
|
||||
|
||||
## Data retention
|
||||
|
||||
Login history events are kept for 336 hours (14 days). Events older than this retention period are automatically deleted and cannot be recovered. If you need to retain login event information for a longer period, consider periodically [downloading login history](#download-login-history).
|
||||
|
||||
## Download login history
|
||||
|
||||
To download the login history, go to the login history page and click **Download login history**.
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Maintenance Mode
|
||||
title: Maintenance mode
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Project Collaboration Mode
|
||||
title: Project collaboration mode
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
|
@ -1,25 +1,15 @@
|
||||
---
|
||||
title: Public Invite Links
|
||||
title: Public invite links
|
||||
---
|
||||
|
||||
Public invite links let you invite team members to your Unleash instance. Any user with an invite link can sign up to Unleash instance that created the link. The user will get the **viewer** role (refer to the [predefined roles_ section of the RBAC document](../reference/rbac.md#predefined-roles) for more information on roles).
|
||||
Public invite links allow you to invite new team members to your Unleash instance. Any user who receives an invite link can use it to sign up for the Unleash instance that generated the link. When users sign up using an invite link, they are automatically assigned the [Viewer](../reference/rbac.md#predefined-roles) role.
|
||||
|
||||
User who follow the invite link are taken directly to the Unleash sign-up page, where they can create an account.
|
||||
A token becomes active as soon as you create it, and remains valid until it expires or is deleted. Once a token is invalid, users can no longer sign up using an invite link containing that token.
|
||||
|
||||
Only **Unleash instance admins** can create public invite links.
|
||||
You can have only one active invite token at any given time. If an active token already exists, you must delete it before you can [create a new one](#manage-the-public-invite-token).
|
||||
|
||||

|
||||
## Manage the public invite token
|
||||
|
||||
## Public sign-up tokens
|
||||
As an Admin, you can create, update, and delete invite tokens through the Unleash Admin UI in **Admin settings > User config > Users > Create invite link**.
|
||||
|
||||
The most important part of a public sign-up link is the sign-up token. The token is added as the `invite` query parameter to the invite link.
|
||||
|
||||
Each token has an **expiry date**. After this expiry date, the token will stop working and users can no longer sign up using an invite link with that token.
|
||||
|
||||
## Creating, updating, and deleting tokens
|
||||
|
||||
You can [create, update and delete tokens via the Unleash Admin UI](../how-to/how-to-manage-public-invite-tokens.mdx) or via the [Unleash API](../reference/api/unleash/public-signup-tokens.tag.mdx "Public sign-up tokens API documentation").
|
||||
|
||||
A token is active as soon as it's created and stops working as soon as it's deleted or expired.
|
||||
|
||||
You can only have one active invite token at a time. If you already have an active token, you must delete it to create a new one.
|
||||
Alternatively, you can use the [Admin API](../reference/api/unleash/public-signup-tokens.tag.mdx) to manage the public invite token.
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
id: rbac
|
||||
title: Role-based Access Control
|
||||
title: Role-based access control
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
@ -40,8 +40,7 @@ own [custom root roles](#custom-root-roles) and [custom project roles](#custom-p
|
||||
|
||||
Custom root roles let you define your own root roles with a specific set of root permissions. The roles can then be
|
||||
assigned to entities (users, service accounts, and groups) at the root level. This allows you to control access to
|
||||
resources in a more precise, fine-grained way. For a step-by-step walkthrough of how to create and assign custom root
|
||||
roles, refer to [_how to create and assign custom root roles_](../how-to/how-to-create-and-assign-custom-root-roles.md).
|
||||
resources in a more precise, fine-grained way.
|
||||
|
||||
Each custom root role consists of:
|
||||
|
||||
@ -49,6 +48,21 @@ Each custom root role consists of:
|
||||
- a **role description** (required)
|
||||
- a set of **root permissions** (required)
|
||||
|
||||
### Create and assign a custom root role
|
||||
|
||||
To create a custom root role in the Admin UI, do the following:
|
||||
|
||||
1. In **Admin settings > User config > Root roles**, click **New root role**.
|
||||
2. Give the role a name and description and select all permissions you want to assign to the role.
|
||||
3. Click **Add role** to save.
|
||||
|
||||
Once you have the role set up, you can assign it a user:
|
||||
|
||||
1. In **Admin settings > User config > Users**, select the user you want to assign the role to.
|
||||
2. Click **Edit user**.
|
||||
3. For **Role**, select the root role you want the user to have.
|
||||
4. Click **Save**.
|
||||
|
||||
### Root permissions
|
||||
|
||||
You can assign the following root permissions:
|
||||
@ -104,7 +118,7 @@ You can assign the following root permissions:
|
||||
| Change instance banners | Change instance [banners](./banners). |
|
||||
| Change maintenance mode state | Change [maintenance mode](./maintenance-mode) state. |
|
||||
| Update CORS settings | Update [CORS settings](./front-end-api#configure-cross-origin-resource-sharing-cors). |
|
||||
| Read instance logs and login history | Read instance logs and [login history](./login-history.md). |
|
||||
| Read instance logs and login history | Read instance logs and [login history](./login-history). |
|
||||
|
||||
#### Integration permissions
|
||||
|
||||
@ -173,9 +187,7 @@ You can assign the following root permissions:
|
||||
|
||||
Custom project roles let you define your own project roles with a specific set of project permissions down to the
|
||||
environment level. The roles can then be assigned to users in specific projects. All users have viewer access to all
|
||||
projects and resources but must be assigned a project role to be allowed to edit a project's resources. For a
|
||||
step-by-step walkthrough of how to create and assign custom project roles, see [_how to create and assign custom project
|
||||
roles_](../how-to/how-to-create-and-assign-custom-project-roles).
|
||||
projects and resources but must be assigned a project role to be allowed to edit a project's resources.
|
||||
|
||||
Each custom project role consists of:
|
||||
|
||||
@ -183,10 +195,23 @@ Each custom project role consists of:
|
||||
- a **role description** (required)
|
||||
- a set of **project and environment permissions** (required)
|
||||
|
||||
### Create and assign a custom project role
|
||||
|
||||
To create a custom project role in the Admin UI, do the following:
|
||||
|
||||
1. In **Admin settings > User config > Project roles**, click **New project role**.
|
||||
2. Give the role a name and description and select all permissions you want to assign to the role.
|
||||
3. Click **Add role** to save.
|
||||
|
||||
Once you have the role set up, you can assign it to individual users inside a project:
|
||||
|
||||
1. In **Settings > User access**, click **Edit**.
|
||||
2. For **Role**, select the custom project roles you want to apply.
|
||||
3. Click **Save**.
|
||||
|
||||
### Project permissions
|
||||
|
||||
You can assign the following project permissions. These permissions are valid across all of the [project](./projects)'s
|
||||
environments.
|
||||
You can assign the following project permissions. These permissions are valid across all of the [project](./projects)'s environments.
|
||||
|
||||
#### API tokens
|
||||
| Permission Name | Description |
|
||||
@ -278,31 +303,26 @@ To view a user’s permissions, go to **Admin settings > User config > Users**.
|
||||
|
||||
:::
|
||||
|
||||
User groups allow you to assign roles to a group of users within a project, rather than to a user directly. This allows
|
||||
you to manage your user permissions more easily when there's lots of users in the system. For a guide on how to create
|
||||
and manage user groups see [_how to create and manage user groups_](../how-to/how-to-create-and-manage-user-groups.md).
|
||||
User groups allow you to manage user permissions efficiently by assigning roles to a collection of users instead of individually. This is particularly useful for projects with many users.
|
||||
|
||||
A user group consists of the following:
|
||||
You can create and manage user groups in the Admin UI at **Admin settings > User config > Groups**.
|
||||
|
||||
- a **name** (required)
|
||||
- a **description** (optional)
|
||||
- a **list of users** (required)
|
||||
- a list of SSO groups to sync from (optional)
|
||||
- a root role associated with the group (optional; available in v5.1+)
|
||||
When creating a user group, you can define the following:
|
||||
|
||||
Groups do nothing on their own. They must either be given a root role directly or a role on a project to assign
|
||||
permissions.
|
||||
- **Name**: A unique identifier for the group.
|
||||
- **Description**: A brief explanation of the group's purpose.
|
||||
- **Users**: A list of users who are members of this group.
|
||||
- **SSO groups** to sync from: A list of single sign-on (SSO) groups to synchronize members from.
|
||||
- **Root role**: A role assigned to the group at the root level. (Available in v5.1+)
|
||||
|
||||
Groups that do not have a root role need to be assigned a role on a project to be useful. You can assign both predefined
|
||||
roles and custom project roles to groups.
|
||||
Groups themselves do not grant permissions. To be functional, a group must either:
|
||||
- Be assigned a root role. Members of this group will inherit the root role's permissions globally.
|
||||
- Be assigned a role on a specific project. This grants the group's members the specified permissions within that project. You can assign both predefined and custom project roles to groups.
|
||||
|
||||
Any user that is a member of a group with a root role will inherit that root role's permissions on the root level.
|
||||
A user can belong to multiple groups, and each group a user belongs to can have a different role assigned to it on a specific project.
|
||||
If a user gains permissions for a project through multiple groups, they will inherit the most permissive set of permissions from all their assigned group roles for that project.
|
||||
|
||||
While a user can only have one role in a given project, a user may belong to multiple groups, and each of those groups
|
||||
may be given a role on a project. In the case where a given user is given permissions through more than one group, the
|
||||
user will inherit the most permissive permissions of all their groups in that project.
|
||||
|
||||
## User group SSO integration
|
||||
## Set up group SSO syncing
|
||||
|
||||
:::note Availability
|
||||
|
||||
@ -310,23 +330,17 @@ user will inherit the most permissive permissions of all their groups in that pr
|
||||
|
||||
:::
|
||||
|
||||
User groups also support integration with your Single Sign-On (SSO) provider. This allows you to automatically assign
|
||||
users to groups when they log in through SSO. Check out [_how to set up group SSO
|
||||
sync_](../how-to/how-to-set-up-group-sso-sync.md) for a step-by-step walkthrough.
|
||||
You can integrate user groups with your single sign-on (SSO) provider to automatically manage user assignments.
|
||||
Note that this just-in-time process updates groups only when a user logs in, which differs from a full provisioning system like [SCIM](/how-to/how-to-setup-provisioning-with-okta) that syncs all user information proactively.
|
||||
|
||||
Users that have been added to a group through your SSO provider will be automatically removed next time they log in if
|
||||
they've been removed from the SSO group. Users that have been manually added to the group will not be affected.
|
||||
When a user logs in through SSO, they are automatically added to or removed from a user group based on their SSO group membership. Manually added users are not affected by the SSO sync.
|
||||
|
||||
To enable group sync, you'll need to set two fields in your SSO provider configuration options:
|
||||
To enable group syncing, you configure two settings in your SSO provider configuration:
|
||||
|
||||
- **enable group syncing**:
|
||||
- **Enable group syncing**: Turns the feature on.
|
||||
- **Group field JSON path**: A JSON path expression that points to the field in your SSO token response that contains the user's groups.
|
||||
|
||||
Turns on group syncing. This is disabled by default.
|
||||
|
||||
- **group field JSON path**
|
||||
|
||||
A JSON path that should point to the groups field in your token response. This should match the exact field returned
|
||||
by the provider. For example, if your token looks like this:
|
||||
For example, if your token response looks like this, you would set the Group field JSON path to `groups`:
|
||||
|
||||
```json
|
||||
{
|
||||
@ -345,12 +359,26 @@ To enable group sync, you'll need to set two fields in your SSO provider configu
|
||||
"nonce": "0394852-3190485-2490358"
|
||||
}
|
||||
```
|
||||
You need to set the "Group Field JSON path" to "groups".
|
||||
|
||||
After you enable syncing, you must link the SSO group names to the corresponding user group.
|
||||
|
||||
Once you've enabled group syncing and set an appropriate path, you'll need to add the SSO group names to the Unleash
|
||||
group. This can be done by navigating to the Unleash group you want to enable sync for and adding the SSO group names to
|
||||
the "SSO group ID/name" property.
|
||||
|
||||
### Configure SSO group sync
|
||||
|
||||
You must be an Admin in Unleash to perform these steps.
|
||||
|
||||
1. Go to **Admin settings > Single sign-on**. Select your integration and click **Enable Group Syncing**.
|
||||
2. in **Group Field JSON Path**, enter the JSON path for the groups field in your token response.
|
||||
3. Click **Save**.
|
||||
4. Go to **User config > Groups** and select the user group you want to sync and click **Edit**.
|
||||
5. Add the exact SSO group names or IDs you want to link to the group.
|
||||
6. Click **Save**.
|
||||
|
||||
The next time a user who belongs to one of the linked SSO groups logs in, they are automatically added to the user group. If they have been removed from the SSO group, their access will be revoked on their next login.
|
||||
|
||||
[^1]: The project-level permission is still required for the [**create/overwrite variants
|
||||
** (PUT)](/reference/api/unleash/overwrite-feature-variants) and [**update variants
|
||||
** (PATCH)](/reference/api/unleash/patch-feature-variants) API endpoints, but it is not used for anything
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Resource Limits
|
||||
title: Resource limits
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
|
@ -17,7 +17,7 @@ See our how to guides on setting up provisioning for [Okta](../how-to/how-to-set
|
||||
|
||||
**Deprovisioning**
|
||||
|
||||
Deprovisioning can be setup on the provider side and allow for automatic clean up of users in a single place. This is especially useful if you're trying to manage the cost of your Unleash instance, since deprovisioned users will not count towards the seat count of your license. See our [how to guides](../how-to/provisioning) for specific provider configurations.
|
||||
Deprovisioning can be setup on the provider side and allow for automatic clean up of users in a single place. This is especially useful if you're trying to manage the cost of your Unleash instance, since deprovisioned users will not count towards the seat count of your license.
|
||||
|
||||
**Group syncing**
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Service Accounts
|
||||
title: Service accounts
|
||||
---
|
||||
|
||||
:::note Availability
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Strategy Variants
|
||||
title: Strategy variants
|
||||
---
|
||||
|
||||
import VideoContent from '@site/src/components/VideoContent.jsx'
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Technical Debt
|
||||
title: Technical debt
|
||||
pagination_next: reference/insights
|
||||
---
|
||||
|
||||
|
@ -65,7 +65,7 @@ resource "unleash_api_token" "client_token" {
|
||||
### Single sign-on protocols
|
||||
|
||||
- `unleash_oidc`: Manage your [OpenID Connect configuration](../how-to/how-to-add-sso-open-id-connect).
|
||||
- `unleash_saml`: Manage your [SAML configuration](../how-to/sso).
|
||||
- `unleash_saml`: Manage your [SAML configuration](../how-to/how-to-add-sso-saml).
|
||||
|
||||
### Context fields
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
---
|
||||
title: Unleash Context
|
||||
title: Unleash context
|
||||
---
|
||||
|
||||
The **Unleash Context** contains information related to the current feature flag request. Unleash uses this context to evaluate [activation strategies](activation-strategies) and [strategy constraints](../reference/activation-strategies#constraints) and to calculate [flag stickiness](../reference/stickiness). The Unleash Context is an important feature of all the [Unleash client SDKs](../reference/sdks).
|
||||
The **Unleash context** contains information related to the current feature flag request. Unleash uses this context to evaluate [activation strategies](activation-strategies) and [strategy constraints](../reference/activation-strategies#constraints) and to calculate [flag stickiness](../reference/stickiness). The Unleash Context is an important feature of all the [Unleash client SDKs](../reference/sdks).
|
||||
|
||||
## Overview
|
||||
|
||||
|
@ -40,13 +40,11 @@ In version 4 we added support for [OpenID Connect](https://openid.net/connect/)
|
||||
|
||||
In version 4 we improved the User Management and made it available for Unleash Open-Source and Unleash Enterprise. Starting in v4 all users accessing Unleash needs to exist in Unleash in order to gain access (because they need to have the proper permission from RBAC.)
|
||||
|
||||
[Read more](../how-to/how-to-add-users-to-unleash)
|
||||
|
||||
### API access {#api-access}
|
||||
|
||||
In version 4 we improved the API Access and made it available for Unleash Open-Source and Unleash Enterprise. Starting from Unleash v4 we require all SDKs to use an access token in order to connect to Unleash.
|
||||
|
||||
[Read more](../how-to/how-to-use-the-admin-api)
|
||||
[Read more](../api-overview#admin-api)
|
||||
|
||||
### Custom stickiness {#custom-stickiness}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
title: "11 principles for building and scaling feature flag systems"
|
||||
description: Build a scalable, secure feature flag system with 11 key principles. Improve DevOps metrics, ensure reliability, and enhance developer experience."
|
||||
toc_max_heading_level: 2
|
||||
pagination_next: topics/feature-flags/best-practices-using-feature-flags-at-scale
|
||||
---
|
||||
|
||||
import VideoContent from '@site/src/components/VideoContent.jsx';
|
||||
|
@ -2,6 +2,7 @@
|
||||
title: What is a feature flag and why are feature flags used?
|
||||
slug: /what-is-a-feature-flag
|
||||
description: Feature flags let you control software features in real time, enabling safer deployments, better testing, and faster innovation.
|
||||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
Feature flags allow you to release, test, and manage features and functionality across your application without changing the source code. Organizations use added control and flexibility to deliver more and higher quality features with reduced cost, time, and risk.
|
||||
@ -56,7 +57,7 @@ Moreover, feature flags enable quick mitigation of issues by allowing teams to i
|
||||
|
||||
Feature flags significantly accelerate operational release cycles by enabling rapid release, testing, and rollback of features. This speed allows teams to adopt a more action-oriented and experimental approach, quickly iterating on new ideas without the risk of complex code integrations or burdensome deployments. Even when multiple teams are working on overlapping components of complex applications, feature flags streamline the process by reducing dependencies and conflicts.
|
||||
|
||||
Additionally, automated feature flags can [dynamically enable or disable features based on user behavior or system events](../../reference/actions.md), further speeding up the adaptation process. By embracing a CI/CD (continuous integration and continuous deployment) workflow with feature flags, teams can deliver improvements to their applications more frequently and reliably, ensuring a faster, more agile development cycle.
|
||||
Additionally, automated feature flags can [dynamically enable or disable features based on user behavior or system events](/reference/actions.md), further speeding up the adaptation process. By embracing a CI/CD (continuous integration and continuous deployment) workflow with feature flags, teams can deliver improvements to their applications more frequently and reliably, ensuring a faster, more agile development cycle.
|
||||
|
||||
### Enable testing and experimenting
|
||||
|
||||
@ -210,7 +211,7 @@ The risk and cost of building software the old way are too high. When developers
|
||||
Unleash is on a mission to make developers’ lives easier. Individual developers love Unleash because it removes the pain of testing and deploying new features so they have more time and energy to innovate. Unleash is trusted by thousands of companies in production including Visa, Wayfair, Lloyd’s Banking Group, and Samsung. While we serve the needs of some of the world’s largest and most security-conscious organizations, we are also rated the *Easiest to Use in Feature Management software* by G2.
|
||||
|
||||
If you want to learn more about how to implement feature flags at scale, check out the following resources:
|
||||
- [Feature Flag Tutorials](/feature-flag-tutorials)
|
||||
- [Feature Flag Tutorials](/feature-flag-tutorials/use-cases/gradual-rollout)
|
||||
- [Best practices for using feature flags at scale](./topics/feature-flags/best-practices-using-feature-flags-at-scale)
|
||||
- [Best practices for building and scaling feature flags](./topics/feature-flags/feature-flag-best-practices)
|
||||
- [Try Unleash for Free](https://www.getunleash.io/pricing)
|
136
website/docs/troubleshooting.mdx
Normal file
136
website/docs/troubleshooting.mdx
Normal file
@ -0,0 +1,136 @@
|
||||
---
|
||||
title: Troubleshooting
|
||||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
This guide helps you troubleshoot various situations you might encounter when working with Unleash feature flags, including flags not being returned, users not being exposed as expected, unexpected A/B test results, and CORS errors.
|
||||
|
||||
## My feature flag is not returned or my users are not exposed
|
||||
|
||||
If a feature flag isn't being returned by the Frontend API or Edge, or if users are not being exposed to a flag you believe is enabled, consider the following. By default, these endpoints do not return feature flags that are not enabled to save bandwidth.
|
||||
|
||||
### Initial checks
|
||||
|
||||
#### Verify feature configuration
|
||||
- Ensure the feature flag has an [activation strategy](/reference/activation-strategies) associated with it that will evaluate to `true` for your given context.
|
||||
- Confirm that the feature flag has been **enabled** in the specific environment your client application is using. (ref: [enabling a feature flag](/reference/feature-toggles))
|
||||
|
||||
#### SDK `ready` event
|
||||
- Ensure your application, especially frontend clients, waits for the SDK to emit the `ready` event before calling `isEnabled('feature-flag')` or `getVariant('feature-flag')`. Calling these functions too early might mean the client hasn't yet received the latest flag configurations from the server.
|
||||
|
||||
### Token configuration
|
||||
|
||||
#### Check token type
|
||||
- To connect to the Frontend API or Edge, you **must** use a [Front-end API token](/reference/api-tokens-and-client-keys#frontend-tokens). Other token types will not work.
|
||||
|
||||
#### Check token access
|
||||
- The token's access configuration is **immutable after creation** and defines which feature flags it can access. The format of the token indicates its scope:
|
||||
- **Access to all projects (current and future):** Tokens starting with `*:` (e.g., `*:production:xyz123etc...`) provide access to flags in the specified environment across all projects.
|
||||
- **Access to a discrete list of projects:** Tokens starting with `[]:` (e.g., `[]:production:xyz123etc...`) grant access to a specific subset of projects in the given environment. You can see which projects a token has access to on the API Tokens page in the Unleash admin UI.
|
||||
- **Single project access:** Tokens starting with a project name (e.g., `my_fullstack_app:production:xyz123etc...`) are restricted to that project and environment.
|
||||
|
||||
### Context and stickiness
|
||||
|
||||
#### Gradual rollout strategy and stickiness
|
||||
- When using a **gradual rollout** strategy, pay close attention to the [stickiness](/reference/stickiness) configuration.
|
||||
- If the context provided by your SDK during flag evaluation **does not include the field specified for stickiness** (e.g., `userId`, `sessionId`, or a custom field), the gradual rollout strategy will evaluate to `false`. Consequently, the flag (or the "on" state for that user) will not be returned by the API.
|
||||
|
||||
### Using the Unleash playground
|
||||
|
||||
- Feature activation strategies can be combined in complex ways. The [Unleash Playground](/reference/playground.mdx) is an invaluable tool. You can use an access token along with various context values (input via the UI) to simulate how a flag will be resolved for different users and scenarios, helping you verify your configuration.
|
||||
|
||||
### Alternative: Using variants for disabled/enabled states
|
||||
|
||||
If you need to know about a flag regardless of whether it's "on" or "off" for a user (e.g., for analytics or UI rendering logic), consider using variants:
|
||||
|
||||
- First, enable the feature flag itself in the desired environment.
|
||||
- Next, configure [strategy variants](/reference/strategy-variants) to represent "enabled" and "disabled" states. You can assign percentages to these variants (e.g., 50% "enabled", 50% "disabled").
|
||||
|
||||

|
||||
*This flag itself is enabled in development and adds a 50%/50% split between disabled/enabled variants. This is essentially the same as a gradual rollout of 50% but using variants.*
|
||||
|
||||
- Then, in your SDK, use the `getVariant()` call (or equivalent) instead of `isEnabled()`.
|
||||
- This approach can also be combined with more complex constraint-based targeting.
|
||||
|
||||

|
||||
*This flag returns an "enabled" variant for clients with a specific `semver` and performs a percentage split for the remaining clients.*
|
||||
|
||||
## My A/B tests are producing unexpected results
|
||||
|
||||
If your A/B tests or experiments are producing unexpected results:
|
||||
|
||||
#### Prerequisite check
|
||||
- First, ensure the feature flag is being returned correctly by following the guidance in the "[My feature flag is not returned or my users are not exposed](#my-feature-flag-is-not-returned-or-my-users-are-not-exposed)" section above.
|
||||
|
||||
#### Verify gradual rollout percentage
|
||||
- Check the rollout percentage of your [gradual rollout activation strategy](/reference/activation-strategies). If you intend to include 100% of your user base in the A/B/n test, ensure the rollout percentage is set to 100%.
|
||||
|
||||
#### Check stickiness and context
|
||||
- Revisit the [stickiness](/reference/stickiness) configuration.
|
||||
- If using default stickiness, confirm that either `userId` or `sessionId` (or both, depending on your setup) is consistently provided in the Unleash context from your application.
|
||||
- If the context provided during flag evaluation does not include the field used for stickiness, the gradual rollout strategy will evaluate to `false`, and the user will not be part of the A/B test population for that flag.
|
||||
|
||||
#### Ensure variants are correctly configured
|
||||
- Refer to the documentation on [feature flag variants](/reference/feature-toggle-variants).
|
||||
- For a simple 50-50 A/B test, your variants should be configured accordingly (e.g., two variants, "A" and "B", with appropriate weighting or rollout distribution if not handled by a parent strategy).
|
||||
|
||||

|
||||
|
||||
#### Double-check SDK code for variant handling
|
||||
- Verify that your application code correctly handles the feature flag variant response. Consult your specific SDK's documentation.
|
||||
- For example, using the [Unleash React SDK](/reference/sdks/react), you might follow the [check variants](/reference/sdks/react#check-variants) section. Given the example variants "A" and "B", your code might look like this:
|
||||
|
||||
```tsx
|
||||
import { useVariant } from '@unleash/proxy-client-react';
|
||||
|
||||
export const TestComponent = () => {
|
||||
const variant = useVariant('ab-test-flag'); // 'ab-test-flag' is your feature flag name
|
||||
|
||||
if (variant.name === 'A') {
|
||||
return <AComponent />;
|
||||
} else if (variant.name === 'B') {
|
||||
return <BComponent />;
|
||||
}
|
||||
// Fallback or default component if the flag is off or variant is not recognized
|
||||
return <DefaultComponent />;
|
||||
};
|
||||
```
|
||||
|
||||
#### Use the Unleash playground
|
||||
- If results are still unexpected, use the [Playground](/reference/playground.mdx) to simulate different user contexts and verify that the feature flag and its variants are resolving as intended.
|
||||
|
||||
## My requests are blocked due to CORS issues
|
||||
|
||||
Cross-Origin Resource Sharing (CORS) issues can prevent your client-side application from communicating with the Unleash API or Edge. Browsers enforce CORS as a security measure. If you see errors like "No 'Access-Control-Policy' header is present on the requested resource," it's likely a CORS misconfiguration.
|
||||
|
||||
### Configuring CORS in the Unleash admin UI (for Unleash server)
|
||||
|
||||
- Navigate to **Settings** in the Unleash Admin Dashboard.
|
||||
- Select **CORS Origins**.
|
||||
- Define the allowed origins (e.g., `https://your-app.com`).
|
||||
- **For troubleshooting:** You can temporarily set the allowed origin to `*` (a single asterisk) to allow all origins. This helps confirm if CORS is the root cause.
|
||||
- **Important Security Note:** Using `*` in production is generally discouraged. Always restrict origins to only those that require access.
|
||||
- These settings can also be managed via the [Unleash API](https://docs.getunleash.io/reference/api/unleash).
|
||||
|
||||
### Configuring CORS for Unleash Edge
|
||||
|
||||
If you are using Unleash Edge, CORS headers are typically configured via command-line flags when starting the Edge instance:
|
||||
|
||||
- To allow a specific origin:
|
||||
```bash
|
||||
unleash-edge edge --cors-origin "[https://your-application.com](https://your-application.com)"
|
||||
```
|
||||
- You can specify multiple domains as a comma-separated list or by using the `--cors-origin` flag multiple times.
|
||||
- Other CORS-related headers (e.g., `Access-Control-Allow-Headers`, `Access-Control-Allow-Methods`) can also be set via command-line arguments. Refer to the Unleash Edge deployment documentation for details.
|
||||
|
||||
### Verifying the `Access-Control-Allow-Origin` header
|
||||
|
||||
You can use the `curl` command-line tool to inspect the response headers from your Unleash instance and verify the CORS configuration. Replace `<host>` and `<endpoint>` with your Unleash server URL and a relevant API endpoint (e.g., the frontend API endpoint).
|
||||
|
||||
|
||||
## I don't see an Unleash feature in the Admin UI
|
||||
|
||||
If a documented Unleash feature isn't showing up in your Admin UI, check the following:
|
||||
|
||||
- Is the feature included in your [Unleash plan and version](/availability)?
|
||||
- Is the feature in beta? If so, reach out to us to get early access.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user