From 89f5f79836acb111bd2559dbef37d1b7a3655123 Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Thu, 17 Jul 2025 18:36:25 +0200 Subject: [PATCH 01/14] feat: aggregation mode selection (#10367) --- .../impact-metrics/ChartConfigModal.tsx | 2 +- .../component/impact-metrics/ChartItem.tsx | 10 +++- .../impact-metrics/ImpactMetricsChart.tsx | 16 +++--- .../ImpactMetricsChartPreview.tsx | 7 +-- .../ImpactMetricsControls.tsx | 20 +++----- .../components/ModeSelector.tsx | 50 +++++++++++++++++++ .../impact-metrics/hooks/useChartFormState.ts | 37 ++++++++++---- .../hooks/useImpactMetricsState.test.tsx | 8 +-- .../src/component/impact-metrics/types.ts | 4 +- .../src/component/impact-metrics/utils.ts | 6 +++ .../useImpactMetricsData.ts | 6 +-- 11 files changed, 124 insertions(+), 42 deletions(-) create mode 100644 frontend/src/component/impact-metrics/ImpactMetricsControls/components/ModeSelector.tsx diff --git a/frontend/src/component/impact-metrics/ChartConfigModal.tsx b/frontend/src/component/impact-metrics/ChartConfigModal.tsx index 1e8202e118..17a1db9fea 100644 --- a/frontend/src/component/impact-metrics/ChartConfigModal.tsx +++ b/frontend/src/component/impact-metrics/ChartConfigModal.tsx @@ -120,7 +120,7 @@ export const ChartConfigModal: FC = ({ selectedRange={formData.selectedRange} selectedLabels={formData.selectedLabels} beginAtZero={formData.beginAtZero} - showRate={formData.showRate} + aggregationMode={formData.aggregationMode} /> diff --git a/frontend/src/component/impact-metrics/ChartItem.tsx b/frontend/src/component/impact-metrics/ChartItem.tsx index 7223f0f740..7d69d67d61 100644 --- a/frontend/src/component/impact-metrics/ChartItem.tsx +++ b/frontend/src/component/impact-metrics/ChartItem.tsx @@ -21,8 +21,14 @@ const getConfigDescription = (config: DisplayChartConfig): string => { parts.push(`last ${config.selectedRange}`); - if (config.showRate) { + if (config.aggregationMode === 'rps') { parts.push('rate per second'); + } else if (config.aggregationMode === 'count') { + parts.push('count'); + } else if (config.aggregationMode === 'avg') { + parts.push('average'); + } else if (config.aggregationMode === 'sum') { + parts.push('sum'); } const labelCount = Object.keys(config.selectedLabels).length; @@ -127,7 +133,7 @@ export const ChartItem: FC = ({ config, onEdit, onDelete }) => ( selectedRange={config.selectedRange} selectedLabels={config.selectedLabels} beginAtZero={config.beginAtZero} - showRate={config.showRate} + aggregationMode={config.aggregationMode} aspectRatio={1.5} overrideOptions={{ maintainAspectRatio: false }} emptyDataDescription='Send impact metrics using Unleash SDK for this series to view the chart.' diff --git a/frontend/src/component/impact-metrics/ImpactMetricsChart.tsx b/frontend/src/component/impact-metrics/ImpactMetricsChart.tsx index ff18d44b83..9d0280a27d 100644 --- a/frontend/src/component/impact-metrics/ImpactMetricsChart.tsx +++ b/frontend/src/component/impact-metrics/ImpactMetricsChart.tsx @@ -10,13 +10,14 @@ import { usePlaceholderData } from '../insights/hooks/usePlaceholderData.js'; import { getDisplayFormat, getTimeUnit, formatLargeNumbers } from './utils.ts'; import { fromUnixTime } from 'date-fns'; import { useChartData } from './hooks/useChartData.ts'; +import type { AggregationMode } from './types.ts'; type ImpactMetricsChartProps = { selectedSeries: string; selectedRange: 'hour' | 'day' | 'week' | 'month'; selectedLabels: Record; beginAtZero: boolean; - showRate?: boolean; + aggregationMode?: AggregationMode; aspectRatio?: number; overrideOptions?: Record; errorTitle?: string; @@ -30,7 +31,7 @@ export const ImpactMetricsChart: FC = ({ selectedRange, selectedLabels, beginAtZero, - showRate, + aggregationMode, aspectRatio, overrideOptions = {}, errorTitle = 'Failed to load impact metrics.', @@ -47,7 +48,7 @@ export const ImpactMetricsChart: FC = ({ ? { series: selectedSeries, range: selectedRange, - showRate, + aggregationMode, labels: Object.keys(selectedLabels).length > 0 ? selectedLabels @@ -121,14 +122,17 @@ export const ImpactMetricsChart: FC = ({ y: { beginAtZero, title: { - display: !!showRate, - text: showRate ? 'Rate per second' : '', + display: aggregationMode === 'rps', + text: + aggregationMode === 'rps' + ? 'Rate per second' + : '', }, ticks: { precision: 0, callback: (value: unknown): string | number => typeof value === 'number' - ? `${formatLargeNumbers(value)}${showRate ? '/s' : ''}` + ? `${formatLargeNumbers(value)}${aggregationMode === 'rps' ? '/s' : ''}` : (value as number), }, }, diff --git a/frontend/src/component/impact-metrics/ImpactMetricsChartPreview.tsx b/frontend/src/component/impact-metrics/ImpactMetricsChartPreview.tsx index fc07edd009..1280ae06b8 100644 --- a/frontend/src/component/impact-metrics/ImpactMetricsChartPreview.tsx +++ b/frontend/src/component/impact-metrics/ImpactMetricsChartPreview.tsx @@ -2,13 +2,14 @@ import type { FC } from 'react'; import { Typography } from '@mui/material'; import { StyledChartContainer } from 'component/insights/InsightsCharts.styles'; import { ImpactMetricsChart } from './ImpactMetricsChart.tsx'; +import type { AggregationMode } from './types.ts'; type ImpactMetricsChartPreviewProps = { selectedSeries: string; selectedRange: 'hour' | 'day' | 'week' | 'month'; selectedLabels: Record; beginAtZero: boolean; - showRate?: boolean; + aggregationMode?: AggregationMode; }; export const ImpactMetricsChartPreview: FC = ({ @@ -16,7 +17,7 @@ export const ImpactMetricsChartPreview: FC = ({ selectedRange, selectedLabels, beginAtZero, - showRate, + aggregationMode, }) => ( <> @@ -35,7 +36,7 @@ export const ImpactMetricsChartPreview: FC = ({ selectedRange={selectedRange} selectedLabels={selectedLabels} beginAtZero={beginAtZero} - showRate={showRate} + aggregationMode={aggregationMode} isPreview /> diff --git a/frontend/src/component/impact-metrics/ImpactMetricsControls/ImpactMetricsControls.tsx b/frontend/src/component/impact-metrics/ImpactMetricsControls/ImpactMetricsControls.tsx index 2a4f68fb24..96f60ceaec 100644 --- a/frontend/src/component/impact-metrics/ImpactMetricsControls/ImpactMetricsControls.tsx +++ b/frontend/src/component/impact-metrics/ImpactMetricsControls/ImpactMetricsControls.tsx @@ -5,7 +5,9 @@ import type { ImpactMetricsLabels } from 'hooks/api/getters/useImpactMetricsData import { SeriesSelector } from './components/SeriesSelector.tsx'; import { RangeSelector } from './components/RangeSelector.tsx'; import { LabelsFilter } from './components/LabelsFilter.tsx'; +import { ModeSelector } from './components/ModeSelector.tsx'; import type { ChartFormState } from '../hooks/useChartFormState.ts'; +import { getMetricType } from '../utils.ts'; export type ImpactMetricsControlsProps = { formData: ChartFormState['formData']; @@ -15,7 +17,7 @@ export type ImpactMetricsControlsProps = { | 'setSelectedRange' | 'setBeginAtZero' | 'setSelectedLabels' - | 'setShowRate' + | 'setAggregationMode' >; metricSeries: (ImpactMetricsSeries & { name: string })[]; loading?: boolean; @@ -68,17 +70,11 @@ export const ImpactMetricsControls: FC = ({ label='Begin at zero' /> - {formData.selectedSeries.startsWith('unleash_counter_') ? ( - - actions.setShowRate(e.target.checked) - } - /> - } - label='Show rate per second' + {formData.selectedSeries ? ( + ) : null} diff --git a/frontend/src/component/impact-metrics/ImpactMetricsControls/components/ModeSelector.tsx b/frontend/src/component/impact-metrics/ImpactMetricsControls/components/ModeSelector.tsx new file mode 100644 index 0000000000..77223cf894 --- /dev/null +++ b/frontend/src/component/impact-metrics/ImpactMetricsControls/components/ModeSelector.tsx @@ -0,0 +1,50 @@ +import type { FC } from 'react'; +import { FormControl, InputLabel, Select, MenuItem } from '@mui/material'; +import type { AggregationMode } from '../../types.ts'; + +export type ModeSelectorProps = { + value: AggregationMode; + onChange: (mode: AggregationMode) => void; + seriesType: 'counter' | 'gauge' | 'unknown'; +}; + +export const ModeSelector: FC = ({ + value, + onChange, + seriesType, +}) => { + if (seriesType === 'unknown') return null; + return ( + + Mode + + + ); +}; diff --git a/frontend/src/component/impact-metrics/hooks/useChartFormState.ts b/frontend/src/component/impact-metrics/hooks/useChartFormState.ts index 6526e1a35d..93a7854001 100644 --- a/frontend/src/component/impact-metrics/hooks/useChartFormState.ts +++ b/frontend/src/component/impact-metrics/hooks/useChartFormState.ts @@ -1,7 +1,8 @@ import { useState, useEffect } from 'react'; import { useImpactMetricsData } from 'hooks/api/getters/useImpactMetricsData/useImpactMetricsData'; -import type { ChartConfig } from '../types.ts'; +import type { AggregationMode, ChartConfig } from '../types.ts'; import type { ImpactMetricsLabels } from 'hooks/api/getters/useImpactMetricsData/useImpactMetricsData'; +import { getMetricType } from '../utils.ts'; type UseChartConfigParams = { open: boolean; @@ -14,7 +15,7 @@ export type ChartFormState = { selectedSeries: string; selectedRange: 'hour' | 'day' | 'week' | 'month'; beginAtZero: boolean; - showRate: boolean; + aggregationMode: AggregationMode; selectedLabels: Record; }; actions: { @@ -22,7 +23,7 @@ export type ChartFormState = { setSelectedSeries: (series: string) => void; setSelectedRange: (range: 'hour' | 'day' | 'week' | 'month') => void; setBeginAtZero: (beginAtZero: boolean) => void; - setShowRate: (showRate: boolean) => void; + setAggregationMode: (mode: AggregationMode) => void; setSelectedLabels: (labels: Record) => void; handleSeriesChange: (series: string) => void; getConfigToSave: () => Omit; @@ -48,7 +49,12 @@ export const useChartFormState = ({ const [selectedLabels, setSelectedLabels] = useState< Record >(initialConfig?.selectedLabels || {}); - const [showRate, setShowRate] = useState(initialConfig?.showRate || false); + const [aggregationMode, setAggregationMode] = useState( + (initialConfig?.aggregationMode || getMetricType(selectedSeries)) === + 'counter' + ? 'count' + : 'avg', + ); const { data: { labels: currentAvailableLabels }, @@ -57,7 +63,7 @@ export const useChartFormState = ({ ? { series: selectedSeries, range: selectedRange, - showRate, + aggregationMode, } : undefined, ); @@ -69,20 +75,31 @@ export const useChartFormState = ({ setSelectedRange(initialConfig.selectedRange); setBeginAtZero(initialConfig.beginAtZero); setSelectedLabels(initialConfig.selectedLabels); - setShowRate(initialConfig.showRate || false); + setAggregationMode( + initialConfig.aggregationMode || + (getMetricType(initialConfig.selectedSeries) === 'counter' + ? 'count' + : 'avg'), + ); } else if (open && !initialConfig) { setTitle(''); setSelectedSeries(''); setSelectedRange('day'); setBeginAtZero(false); setSelectedLabels({}); - setShowRate(false); + setAggregationMode('count'); } }, [open, initialConfig]); const handleSeriesChange = (series: string) => { setSelectedSeries(series); setSelectedLabels({}); + const metric = getMetricType(series); + if (metric === 'counter') { + setAggregationMode('count'); + } else if (metric === 'gauge') { + setAggregationMode('avg'); + } }; const getConfigToSave = (): Omit => ({ @@ -91,7 +108,7 @@ export const useChartFormState = ({ selectedRange, beginAtZero, selectedLabels, - showRate, + aggregationMode, }); const isValid = selectedSeries.length > 0; @@ -102,7 +119,7 @@ export const useChartFormState = ({ selectedSeries, selectedRange, beginAtZero, - showRate, + aggregationMode, selectedLabels, }, actions: { @@ -110,7 +127,7 @@ export const useChartFormState = ({ setSelectedSeries, setSelectedRange, setBeginAtZero, - setShowRate, + setAggregationMode, setSelectedLabels, handleSeriesChange, getConfigToSave, diff --git a/frontend/src/component/impact-metrics/hooks/useImpactMetricsState.test.tsx b/frontend/src/component/impact-metrics/hooks/useImpactMetricsState.test.tsx index f113df5347..13b936ada3 100644 --- a/frontend/src/component/impact-metrics/hooks/useImpactMetricsState.test.tsx +++ b/frontend/src/component/impact-metrics/hooks/useImpactMetricsState.test.tsx @@ -37,7 +37,7 @@ const TestComponent: FC<{ selectedSeries: 'test-series', selectedRange: 'day', beginAtZero: true, - showRate: false, + aggregationMode: 'count', selectedLabels: {}, title: 'Test Chart', }) @@ -79,7 +79,7 @@ const mockSettings: ImpactMetricsState = { selectedSeries: 'test-series', selectedRange: 'day' as const, beginAtZero: true, - showRate: false, + aggregationMode: 'count', selectedLabels: {}, title: 'Test Chart', }, @@ -182,7 +182,7 @@ describe('useImpactMetricsState', () => { selectedSeries: 'test-series', selectedRange: 'day', beginAtZero: true, - showRate: false, + mode: 'count', selectedLabels: {}, title: 'Test Chart', }, @@ -215,7 +215,7 @@ describe('useImpactMetricsState', () => { selectedSeries: 'test-series', selectedRange: 'day', beginAtZero: true, - showRate: false, + mode: 'count', selectedLabels: {}, title: 'Test Chart', }, diff --git a/frontend/src/component/impact-metrics/types.ts b/frontend/src/component/impact-metrics/types.ts index 96abb0c3bc..2b945e6957 100644 --- a/frontend/src/component/impact-metrics/types.ts +++ b/frontend/src/component/impact-metrics/types.ts @@ -3,11 +3,13 @@ export type ChartConfig = { selectedSeries: string; // e.g. unleash_counter_my_metric selectedRange: 'hour' | 'day' | 'week' | 'month'; beginAtZero: boolean; - showRate: boolean; + aggregationMode: AggregationMode; selectedLabels: Record; title?: string; }; +export type AggregationMode = 'rps' | 'count' | 'avg' | 'sum'; + export type DisplayChartConfig = ChartConfig & { type: 'counter' | 'gauge'; displayName: string; // e.g. my_metric with unleash_counter stripped diff --git a/frontend/src/component/impact-metrics/utils.ts b/frontend/src/component/impact-metrics/utils.ts index 47a580a735..db50dfcf15 100644 --- a/frontend/src/component/impact-metrics/utils.ts +++ b/frontend/src/component/impact-metrics/utils.ts @@ -60,3 +60,9 @@ export const formatLargeNumbers = (value: number): string => { } return value.toString(); }; + +export const getMetricType = (seriesName: string) => { + if (seriesName.startsWith('unleash_counter_')) return 'counter'; + if (seriesName.startsWith('unleash_gauge_')) return 'gauge'; + return 'unknown'; +}; diff --git a/frontend/src/hooks/api/getters/useImpactMetricsData/useImpactMetricsData.ts b/frontend/src/hooks/api/getters/useImpactMetricsData/useImpactMetricsData.ts index 737d62a44b..fae49fa1b8 100644 --- a/frontend/src/hooks/api/getters/useImpactMetricsData/useImpactMetricsData.ts +++ b/frontend/src/hooks/api/getters/useImpactMetricsData/useImpactMetricsData.ts @@ -25,7 +25,7 @@ export type ImpactMetricsQuery = { series: string; range: 'hour' | 'day' | 'week' | 'month'; labels?: Record; - showRate?: boolean; + aggregationMode?: 'rps' | 'count' | 'avg' | 'sum'; }; export const useImpactMetricsData = (query?: ImpactMetricsQuery) => { @@ -38,8 +38,8 @@ export const useImpactMetricsData = (query?: ImpactMetricsQuery) => { range: query.range, }); - if (query.showRate !== undefined) { - params.append('showRate', query.showRate.toString()); + if (query.aggregationMode !== undefined) { + params.append('aggregationMode', query.aggregationMode); } if (query.labels && Object.keys(query.labels).length > 0) { From b581b2393d7fa8dabd9d0fb36098f2985a28f826 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Fri, 18 Jul 2025 09:46:32 +0200 Subject: [PATCH 02/14] fix: improve impact metrics preview chart (#10368) --- .../ImpactMetricsChartPreview.tsx | 52 +++++++++++-------- .../ImpactMetricsControls.tsx | 1 - 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/frontend/src/component/impact-metrics/ImpactMetricsChartPreview.tsx b/frontend/src/component/impact-metrics/ImpactMetricsChartPreview.tsx index 1280ae06b8..32fd2d9755 100644 --- a/frontend/src/component/impact-metrics/ImpactMetricsChartPreview.tsx +++ b/frontend/src/component/impact-metrics/ImpactMetricsChartPreview.tsx @@ -1,6 +1,5 @@ import type { FC } from 'react'; -import { Typography } from '@mui/material'; -import { StyledChartContainer } from 'component/insights/InsightsCharts.styles'; +import { Box, Typography, useMediaQuery, useTheme } from '@mui/material'; import { ImpactMetricsChart } from './ImpactMetricsChart.tsx'; import type { AggregationMode } from './types.ts'; @@ -18,27 +17,34 @@ export const ImpactMetricsChartPreview: FC = ({ selectedLabels, beginAtZero, aggregationMode, -}) => ( - <> - - Preview - +}) => { + const theme = useTheme(); + const screenBreakpoint = useMediaQuery(theme.breakpoints.down('lg')); + const key = screenBreakpoint ? 'small' : 'large'; - {!selectedSeries ? ( - - Select a metric series to view the preview + return ( + <> + + Preview - ) : null} - - - - -); + {!selectedSeries ? ( + + Select a metric series to view the preview + + ) : null} + + ({ padding: theme.spacing(1) })}> + + + + ); +}; diff --git a/frontend/src/component/impact-metrics/ImpactMetricsControls/ImpactMetricsControls.tsx b/frontend/src/component/impact-metrics/ImpactMetricsControls/ImpactMetricsControls.tsx index 96f60ceaec..b2e3d1ce30 100644 --- a/frontend/src/component/impact-metrics/ImpactMetricsControls/ImpactMetricsControls.tsx +++ b/frontend/src/component/impact-metrics/ImpactMetricsControls/ImpactMetricsControls.tsx @@ -36,7 +36,6 @@ export const ImpactMetricsControls: FC = ({ display: 'flex', flexDirection: 'column', gap: theme.spacing(3), - maxWidth: 400, })} > From 2ff1aa78a06fe01c76eb114b6c3ac4ef0dde542a Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Fri, 18 Jul 2025 14:28:56 +0200 Subject: [PATCH 03/14] refactor: color assignment in useChartData (#10372) --- .../impact-metrics/ImpactMetricsChart.tsx | 2 +- .../impact-metrics/hooks/useChartData.ts | 26 +++++++++++++++---- .../impact-metrics/hooks/useSeriesColor.ts | 17 ------------ 3 files changed, 22 insertions(+), 23 deletions(-) delete mode 100644 frontend/src/component/impact-metrics/hooks/useSeriesColor.ts diff --git a/frontend/src/component/impact-metrics/ImpactMetricsChart.tsx b/frontend/src/component/impact-metrics/ImpactMetricsChart.tsx index 9d0280a27d..ca19837ac0 100644 --- a/frontend/src/component/impact-metrics/ImpactMetricsChart.tsx +++ b/frontend/src/component/impact-metrics/ImpactMetricsChart.tsx @@ -62,7 +62,7 @@ export const ImpactMetricsChart: FC = ({ type: 'constant', }); - const data = useChartData(timeSeriesData); + const data = useChartData(timeSeriesData, debug?.query); const hasError = !!dataError; const isLoading = dataLoading; diff --git a/frontend/src/component/impact-metrics/hooks/useChartData.ts b/frontend/src/component/impact-metrics/hooks/useChartData.ts index f7ea329d5e..0519908c67 100644 --- a/frontend/src/component/impact-metrics/hooks/useChartData.ts +++ b/frontend/src/component/impact-metrics/hooks/useChartData.ts @@ -1,14 +1,30 @@ import { useMemo } from 'react'; import { useTheme } from '@mui/material'; import type { ImpactMetricsSeries } from 'hooks/api/getters/useImpactMetricsData/useImpactMetricsData'; -import { useSeriesColor } from './useSeriesColor.ts'; import { getSeriesLabel } from '../utils.ts'; +const getColorStartingIndex = (modulo: number, series?: string): number => { + if (!series || series.length === 0 || modulo <= 0) { + return 0; + } + + let hash = 0; + for (let i = 0; i < series.length; i++) { + const char = series.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; + } + + return Math.abs(hash) % modulo; +}; + export const useChartData = ( timeSeriesData: ImpactMetricsSeries[] | undefined, + colorIndexBy?: string, ) => { const theme = useTheme(); - const getSeriesColor = useSeriesColor(); + const colors = theme.palette.charts.series; + const startColorIndex = getColorStartingIndex(colors.length, colorIndexBy); return useMemo(() => { if (!timeSeriesData || timeSeriesData.length === 0) { @@ -66,9 +82,9 @@ export const useChartData = ( (timestamp) => new Date(timestamp * 1000), ); - const datasets = timeSeriesData.map((series) => { + const datasets = timeSeriesData.map((series, index) => { const seriesLabel = getSeriesLabel(series.metric); - const color = getSeriesColor(seriesLabel); + const color = colors[(index + startColorIndex) % colors.length]; const dataMap = new Map(series.data); @@ -90,5 +106,5 @@ export const useChartData = ( datasets, }; } - }, [timeSeriesData, theme, getSeriesColor]); + }, [timeSeriesData, theme]); }; diff --git a/frontend/src/component/impact-metrics/hooks/useSeriesColor.ts b/frontend/src/component/impact-metrics/hooks/useSeriesColor.ts deleted file mode 100644 index a8a0f3f6e5..0000000000 --- a/frontend/src/component/impact-metrics/hooks/useSeriesColor.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useTheme } from '@mui/material'; - -export const useSeriesColor = () => { - const theme = useTheme(); - const colors = theme.palette.charts.series; - - return (seriesLabel: string): string => { - let hash = 0; - for (let i = 0; i < seriesLabel.length; i++) { - const char = seriesLabel.charCodeAt(i); - hash = (hash << 5) - hash + char; - hash = hash & hash; // Convert to 32-bit integer - } - const index = Math.abs(hash) % colors.length; - return colors[index]; - }; -}; From 253c2d71b360c4352b780ca747da4b6effd1ba16 Mon Sep 17 00:00:00 2001 From: Melinda Fekete Date: Fri, 18 Jul 2025 17:09:35 +0200 Subject: [PATCH 04/14] docs: prioritize search results (#10369) --- website/README.md | 20 +++++ website/docs/api-overview.mdx | 4 + .../use-cases/a-b-testing.md | 4 + .../use-cases/gradual-rollout.md | 4 + .../use-cases/scaling-unleash.mdx | 3 + .../use-cases/security-compliance.md | 4 + .../use-cases/trunk-based-development.md | 4 + .../user-management-access-controls.md | 6 ++ website/docs/how-to/how-to-add-sso-google.md | 6 ++ .../docs/how-to/how-to-create-api-tokens.mdx | 4 + .../how-to-environment-import-export.mdx | 5 ++ .../how-to/how-to-run-the-unleash-proxy.mdx | 6 ++ ...end-feature-updates-to-slack-deprecated.md | 84 ------------------- website/docs/quickstart.mdx | 8 +- website/docs/reference/actions.md | 4 + .../docs/reference/activation-strategies.md | 3 + .../reference/api-tokens-and-client-keys.mdx | 4 + .../api/legacy/unleash/admin/addons.md | 4 + .../api/legacy/unleash/admin/archive.md | 4 + .../api/legacy/unleash/admin/context.md | 5 ++ .../api/legacy/unleash/admin/events.mdx | 4 + .../api/legacy/unleash/admin/feature-types.md | 4 + .../api/legacy/unleash/admin/features-v2.mdx | 4 + .../api/legacy/unleash/admin/features.md | 4 + .../api/legacy/unleash/admin/metrics.md | 4 + .../api/legacy/unleash/admin/projects.md | 4 + .../api/legacy/unleash/admin/segments.mdx | 4 + .../api/legacy/unleash/admin/state.md | 4 + .../api/legacy/unleash/admin/strategies.md | 4 + .../api/legacy/unleash/admin/tags.md | 4 + .../api/legacy/unleash/admin/user-admin.md | 4 + .../api/legacy/unleash/basic-auth.md | 4 + .../api/legacy/unleash/client/features.md | 4 + .../api/legacy/unleash/client/metrics.md | 4 + .../api/legacy/unleash/client/register.md | 4 + .../reference/api/legacy/unleash/index.md | 4 + .../api/legacy/unleash/internal/health.md | 4 + .../api/legacy/unleash/internal/prometheus.md | 5 ++ website/docs/reference/applications.mdx | 4 + website/docs/reference/banners.md | 4 + website/docs/reference/change-requests.mdx | 3 + website/docs/reference/command-menu.md | 4 + .../reference/custom-activation-strategies.md | 4 + .../reference/environment-import-export.mdx | 3 + website/docs/reference/environments.md | 5 ++ website/docs/reference/events.mdx | 4 + .../docs/reference/feature-toggle-variants.md | 5 ++ website/docs/reference/feature-toggles.mdx | 4 + website/docs/reference/front-end-api.md | 4 + website/docs/reference/impression-data.md | 4 + website/docs/reference/insights.mdx | 4 + .../docs/reference/integrations/datadog.md | 4 + .../reference/integrations/integrations.mdx | 3 + .../jira-cloud-plugin-installation.mdx | 5 ++ .../integrations/jira-cloud-plugin-usage.mdx | 4 + .../jira-server-plugin-installation.md | 4 + .../integrations/jira-server-plugin-usage.md | 4 + .../docs/reference/integrations/slack-app.mdx | 4 + website/docs/reference/integrations/slack.md | 4 + website/docs/reference/integrations/teams.md | 4 + .../docs/reference/integrations/webhook.md | 4 + website/docs/reference/login-history.mdx | 4 + website/docs/reference/maintenance-mode.mdx | 4 + website/docs/reference/network-view.mdx | 4 + website/docs/reference/playground.mdx | 3 + .../reference/project-collaboration-mode.md | 4 + website/docs/reference/projects.mdx | 4 + website/docs/reference/public-signup.mdx | 4 + website/docs/reference/rbac.md | 4 + website/docs/reference/release-templates.mdx | 4 + website/docs/reference/resource-limits.mdx | 4 + website/docs/reference/scim.md | 4 + website/docs/reference/sdks/index.mdx | 4 + website/docs/reference/search-operators.md | 4 + website/docs/reference/segments.mdx | 2 + website/docs/reference/service-accounts.md | 4 + website/docs/reference/signals.md | 4 + website/docs/reference/sso.md | 3 + website/docs/reference/stickiness.md | 4 + website/docs/reference/strategy-variants.mdx | 3 + website/docs/reference/technical-debt.md | 4 + website/docs/reference/terraform.mdx | 4 + website/docs/reference/unleash-context.md | 4 + ...practices-using-feature-flags-at-scale.mdx | 3 + .../feature-flag-best-practices.mdx | 3 + .../understanding-unleash/hosting-options.mdx | 4 + .../the-anatomy-of-unleash.mdx | 4 + .../understanding-unleash/unleash-overview.md | 4 + .../compliance/compliance-overview.mdx | 4 + .../deploy/configuring-unleash.mdx | 4 + .../using-unleash/deploy/getting-started.md | 3 + website/docusaurus.config.ts | 4 - website/src/components/SearchPriority.jsx | 35 ++++++++ website/vercel.json | 5 -- 94 files changed, 408 insertions(+), 99 deletions(-) delete mode 100644 website/docs/how-to/how-to-send-feature-updates-to-slack-deprecated.md create mode 100644 website/src/components/SearchPriority.jsx diff --git a/website/README.md b/website/README.md index 2c25b14b6b..59cb504cd7 100644 --- a/website/README.md +++ b/website/README.md @@ -49,6 +49,26 @@ rm -rf yarn.lock touch yarn.lock ``` +## Search + +This website uses Algolia DocSearch v3 with a dedicated Algolia application and a hosted crawler. All configuration is managed directly through the Algolia dashboard. + +### Search prioritization + +Conceptual and reference documentation pages should rank over specific API endpoints, while still allowing Algolia's relevance algorithm to find the most accurate results. + +#### The configuration + +**Inside the Algolia crawler**: +The crawler configuration has been updated to look for a tag in the HTML of each page. +It extracts the numerical value and saves it as a priority attribute on the search record. Pages without this tag are automatically assigned a default priority of 0. +Algolia is configured to use the `priority` attribute for custom ranking in descending order. + +**Within the docs**: +We have a reusable React component, `` -> `src/components/SearchPriority.jsx`. This component provides a simple shortcut to add the correct tag to any .mdx page. + +For high priority pages, use ``. For pages referencing deprecated features use ``. + ## Troubleshooting ### `TypeError: source_default(...).bold is not a function` diff --git a/website/docs/api-overview.mdx b/website/docs/api-overview.mdx index acc9ffcfcc..9dc0ea4528 100644 --- a/website/docs/api-overview.mdx +++ b/website/docs/api-overview.mdx @@ -4,6 +4,10 @@ description: "An overview of the three main Unleash APIs: Client API, Frontend A displayed_sidebar: documentation --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + ## 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. diff --git a/website/docs/feature-flag-tutorials/use-cases/a-b-testing.md b/website/docs/feature-flag-tutorials/use-cases/a-b-testing.md index ff29652dda..1e93a73c18 100644 --- a/website/docs/feature-flag-tutorials/use-cases/a-b-testing.md +++ b/website/docs/feature-flag-tutorials/use-cases/a-b-testing.md @@ -4,6 +4,10 @@ slug: /feature-flag-tutorials/use-cases/a-b-testing pagination_next: feature-flag-tutorials/use-cases/ai --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + 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 diff --git a/website/docs/feature-flag-tutorials/use-cases/gradual-rollout.md b/website/docs/feature-flag-tutorials/use-cases/gradual-rollout.md index da000000a0..367e602b10 100644 --- a/website/docs/feature-flag-tutorials/use-cases/gradual-rollout.md +++ b/website/docs/feature-flag-tutorials/use-cases/gradual-rollout.md @@ -4,6 +4,10 @@ slug: /feature-flag-tutorials/use-cases/gradual-rollout pagination_next: feature-flag-tutorials/use-cases/a-b-testing --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + ## 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. diff --git a/website/docs/feature-flag-tutorials/use-cases/scaling-unleash.mdx b/website/docs/feature-flag-tutorials/use-cases/scaling-unleash.mdx index 4a4b31c05b..58e958fc2c 100644 --- a/website/docs/feature-flag-tutorials/use-cases/scaling-unleash.mdx +++ b/website/docs/feature-flag-tutorials/use-cases/scaling-unleash.mdx @@ -4,6 +4,9 @@ title: Scaling Unleash for enterprise workloads import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; +import SearchPriority from '@site/src/components/SearchPriority'; + + When evaluating Unleash for [enterprise-wide adoption](https://www.getunleash.io/blog/feature-ops-is-the-next-frontier), your primary concerns likely revolve around scalability, performance, and availability. You need assurance that your chosen [feature management system](https://www.getunleash.io/) can handle potentially tens of millions of users and millions of flags across a number of regions, without compromising user experience or introducing system fragility. diff --git a/website/docs/feature-flag-tutorials/use-cases/security-compliance.md b/website/docs/feature-flag-tutorials/use-cases/security-compliance.md index 3aab9ad9b4..17fd0925ee 100644 --- a/website/docs/feature-flag-tutorials/use-cases/security-compliance.md +++ b/website/docs/feature-flag-tutorials/use-cases/security-compliance.md @@ -3,6 +3,10 @@ title: Feature flag security and compliance for enterprises slug: /feature-flag-tutorials/use-cases/security-and-compliance --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + Security and compliance are important aspects of building and managing complex software in large enterprises. For software architects, engineering leaders, and technical decision-makers, every tool in your tech stack needs to pass security reviews. The weakest link in your software bill of materials, known as the SBOM, can be the one that compromises your security, so every dependency and integration must meet strict standards. Security isn't just about individual components—it’s about the entire system working together without introducing risk. In the modern security landscape, compliance frameworks like FedRAMP, SOC 2, and ISO 27001 set strict standards for proving good security posture in your software tool implementations. Feature flag management systems are no exception. diff --git a/website/docs/feature-flag-tutorials/use-cases/trunk-based-development.md b/website/docs/feature-flag-tutorials/use-cases/trunk-based-development.md index cc5fd5ab7d..11717acef3 100644 --- a/website/docs/feature-flag-tutorials/use-cases/trunk-based-development.md +++ b/website/docs/feature-flag-tutorials/use-cases/trunk-based-development.md @@ -3,6 +3,10 @@ title: Implement trunk-based development using feature flags slug: /feature-flag-tutorials/use-cases/trunk-based-development --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + Developers are increasingly adopting trunk-based development to accelerate software delivery and improve efficiency and reliability. A key principle of trunk-based development is merging code into the main branch (aka "trunk") as quickly as possible. This practice reduces the complexity of long-lived feature branches, minimizes merge conflicts, and ensures that teams can continuously integrate and test their code. However, it also means unfinished or experimental features may exist in production. This is where feature flags become essential. Unleash provides a powerful mechanism for safely managing and controlling these features in production, enabling enterprises to deliver software faster and with greater reliability. Effective feature flag management ensures that trunk-based development supports continuous delivery without compromising stability. In this tutorial, we’ll use Unleash to manage trunk-based development in your codebase. diff --git a/website/docs/feature-flag-tutorials/use-cases/user-management-access-controls.md b/website/docs/feature-flag-tutorials/use-cases/user-management-access-controls.md index 5751de4daa..bd97880c58 100644 --- a/website/docs/feature-flag-tutorials/use-cases/user-management-access-controls.md +++ b/website/docs/feature-flag-tutorials/use-cases/user-management-access-controls.md @@ -4,6 +4,12 @@ slug: /feature-flag-tutorials/use-cases/user-management-access-controls-auditing pagination_next: feature-flag-tutorials/use-cases/security-compliance --- + +import SearchPriority from '@site/src/components/SearchPriority'; + + + + 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. Imagine a large banking platform company with hundreds of engineering teams across multiple continents. Their software development lifecycle is complex and dynamic. Feature flags simplify their processes, but managing all those users is an additional layer of complexity. Unleash solves this with user management capabilities, role-based access controls, and auditing features to help organizations release code with confidence while maintaining security and compliance. diff --git a/website/docs/how-to/how-to-add-sso-google.md b/website/docs/how-to/how-to-add-sso-google.md index 1e81959595..d01114eb90 100644 --- a/website/docs/how-to/how-to-add-sso-google.md +++ b/website/docs/how-to/how-to-add-sso-google.md @@ -3,6 +3,12 @@ title: 'Set up SSO with Google' description: Set up SSO for Unleash with Google. --- + +import SearchPriority from '@site/src/components/SearchPriority'; + + + + :::caution Deprecation notice Single Sign-on via the Google Authenticator provider has been removed in Unleash v5 (deprecated in v4). We recommend using [OpenID Connect](./how-to-add-sso-open-id-connect.md) instead. If you're running a self hosted version of Unleash and you need to temporarily re-enable Google SSO, you can do so by setting the `GOOGLE_AUTH_ENABLED` environment variable to `true`. If you're running a hosted version of Unleash, you'll need to reach out to us and ask us to re-enable the flag. Note that this code will be removed in a future release and this is not safe to depend on. diff --git a/website/docs/how-to/how-to-create-api-tokens.mdx b/website/docs/how-to/how-to-create-api-tokens.mdx index 0ff6ed310b..a351d0b27a 100644 --- a/website/docs/how-to/how-to-create-api-tokens.mdx +++ b/website/docs/how-to/how-to-create-api-tokens.mdx @@ -2,6 +2,10 @@ title: How to create API Tokens --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + 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 diff --git a/website/docs/how-to/how-to-environment-import-export.mdx b/website/docs/how-to/how-to-environment-import-export.mdx index 2d10b67637..ffef95a82e 100644 --- a/website/docs/how-to/how-to-environment-import-export.mdx +++ b/website/docs/how-to/how-to-environment-import-export.mdx @@ -3,6 +3,11 @@ title: Environment import and export --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + + import VideoContent from '@site/src/components/VideoContent.jsx' :::note Availability diff --git a/website/docs/how-to/how-to-run-the-unleash-proxy.mdx b/website/docs/how-to/how-to-run-the-unleash-proxy.mdx index 839a154a13..8001e6d147 100644 --- a/website/docs/how-to/how-to-run-the-unleash-proxy.mdx +++ b/website/docs/how-to/how-to-run-the-unleash-proxy.mdx @@ -2,6 +2,12 @@ title: How to run the Unleash Proxy --- + +import SearchPriority from '@site/src/components/SearchPriority'; + + + + import ApiRequest from '@site/src/components/ApiRequest' :::warning diff --git a/website/docs/how-to/how-to-send-feature-updates-to-slack-deprecated.md b/website/docs/how-to/how-to-send-feature-updates-to-slack-deprecated.md deleted file mode 100644 index db58103ae5..0000000000 --- a/website/docs/how-to/how-to-send-feature-updates-to-slack-deprecated.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -title: Feature Updates To Slack ---- - -:::caution - -This guide is deprecated. If you're looking for ways to integrate with Slack, you should refer to [the Slack integration guide](../reference/integrations/slack.md) instead. - -Event hook option is removed in Unleash v5 - -::: - -## Create a custom Slack WebHook URL: {#create-a-custom-slack-webhook-url} - -1. Go to [https://slack.com/apps/manage/custom-integrations](https://slack.com/apps/manage/custom-integrations) -1. Click Incoming WebHooks -1. Click “Add Configuration” -1. This is Slack's help page on how to do this: https://api.slack.com/messaging/webhooks - - Choose a channel, follow the wizard, get the custom URL. - -## Send data to Slack using an event hook function {#send-data-to-slack-using-an-event-hook-function} - -Using the `eventHook` option, create a function that will send the data you'd like into Slack when mutation events happen. - -```javascript -const unleash = require('unleash-server'); -const axios = require('axios'); - -function onEventHook(event, eventData) { - const { createdBy: user, data } = eventData; - let text = ''; - - const unleashUrl = 'http://your.unleash.host.com'; - const feature = `<${unleashUrl}/#/features/strategies/${data.name}|${data.name}>`; - - switch (event) { - case 'feature-created': - case 'feature-updated': { - const verb = - event === 'feature-created' ? 'created a new' : 'updated the'; - text = `${user} ${verb} feature ${feature}\ndescription: ${ - data.description - }\nenabled: ${data.enabled}\nstrategies: \`${JSON.stringify( - data.strategies, - )}\``; - break; - } - case 'feature-archived': - case 'feature-revived': { - const verb = event === 'feature-archived' ? 'archived' : 'revived'; - text = `${user} ${verb} the feature ${feature}`; - break; - } - default: { - console.error(`Unknown event ${event}`); - return; - } - } - - axios - .post( - 'https://hooks.slack.com/services/THIS_IS_WHERE_THE_CUSTOM_URL_GOES', - { - username: 'Unleash', - icon_emoji: ':unleash:', // if you added a custom emoji, otherwise you can remove this field. - text: text, - }, - ) - .then((res) => { - console.log(`Slack post statusCode: ${res.status}. Text: ${text}`); - }) - .catch((error) => { - console.error(error); - }); -} - -const options = { - eventHook: onEventHook, -}; - -unleash.start(options).then((server) => { - console.log(`Unleash started on http://localhost:${server.app.get('port')}`); -}); -``` diff --git a/website/docs/quickstart.mdx b/website/docs/quickstart.mdx index 011d83340a..e8f68a460a 100644 --- a/website/docs/quickstart.mdx +++ b/website/docs/quickstart.mdx @@ -4,14 +4,10 @@ pagination_next: topics/what-is-a-feature-flag --- import Tabs from '@theme/Tabs'; - import TabItem from '@theme/TabItem'; +import SearchPriority from '@site/src/components/SearchPriority'; -import Head from '@docusaurus/Head'; - - - - + The easiest way to get started with Unleash is through a [cloud-hosted free trial](https://www.getunleash.io/plans/enterprise-payg). This gives you a ready-to-use instance, so you can explore all Unleash features without any local setup. diff --git a/website/docs/reference/actions.md b/website/docs/reference/actions.md index 64a42ebf06..70ebb55689 100644 --- a/website/docs/reference/actions.md +++ b/website/docs/reference/actions.md @@ -2,6 +2,10 @@ title: Actions --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability diff --git a/website/docs/reference/activation-strategies.md b/website/docs/reference/activation-strategies.md index 31eed1e9ee..364e672a0e 100644 --- a/website/docs/reference/activation-strategies.md +++ b/website/docs/reference/activation-strategies.md @@ -3,6 +3,9 @@ title: Activation strategies --- import VideoContent from '@site/src/components/VideoContent.jsx' +import SearchPriority from '@site/src/components/SearchPriority'; + + ## Overview diff --git a/website/docs/reference/api-tokens-and-client-keys.mdx b/website/docs/reference/api-tokens-and-client-keys.mdx index f291e41577..436c4f0828 100644 --- a/website/docs/reference/api-tokens-and-client-keys.mdx +++ b/website/docs/reference/api-tokens-and-client-keys.mdx @@ -3,6 +3,10 @@ title: API tokens and client keys pagination_next: reference/front-end-api --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + ## Overview Unleash uses API keys to facilitate communication between consuming clients such as [SDKs](../reference/sdks), [Unleash Edge](../reference/unleash-edge), or other tools and automation. diff --git a/website/docs/reference/api/legacy/unleash/admin/addons.md b/website/docs/reference/api/legacy/unleash/admin/addons.md index db279d9ca6..2e7a4c8bfc 100644 --- a/website/docs/reference/api/legacy/unleash/admin/addons.md +++ b/website/docs/reference/api/legacy/unleash/admin/addons.md @@ -2,6 +2,10 @@ title: /api/admin/addons --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + > In order to access the admin API endpoints you need to identify yourself. Unless you're using the `none` authentication method, you'll need to [create an ADMIN token](/how-to/how-to-create-api-tokens) and add an Authorization header using the token. ### List integrations and providers {#list-integrations-and-providers} diff --git a/website/docs/reference/api/legacy/unleash/admin/archive.md b/website/docs/reference/api/legacy/unleash/admin/archive.md index bbc3c3412c..2c4c140a98 100644 --- a/website/docs/reference/api/legacy/unleash/admin/archive.md +++ b/website/docs/reference/api/legacy/unleash/admin/archive.md @@ -2,6 +2,10 @@ title: /api/admin/archive --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + > In order to access the admin API endpoints you need to identify yourself. Unless you're using the `none` authentication method, you'll need to [create an ADMIN token](/how-to/how-to-create-api-tokens) and add an Authorization header using the token. diff --git a/website/docs/reference/api/legacy/unleash/admin/context.md b/website/docs/reference/api/legacy/unleash/admin/context.md index 7a47978f7a..cd9669dfed 100644 --- a/website/docs/reference/api/legacy/unleash/admin/context.md +++ b/website/docs/reference/api/legacy/unleash/admin/context.md @@ -2,6 +2,11 @@ title: /api/admin/context --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + + > The context feature is only available as part of Unleash Enterprise. In order to access the API programmatically you need to make sure you [obtain a API token](/how-to/how-to-create-api-tokens) with admin permissions. ### List context fields defined in Unleash {#list-context-fields-defined-in-unleash} diff --git a/website/docs/reference/api/legacy/unleash/admin/events.mdx b/website/docs/reference/api/legacy/unleash/admin/events.mdx index 34fa81a155..791e923ebd 100644 --- a/website/docs/reference/api/legacy/unleash/admin/events.mdx +++ b/website/docs/reference/api/legacy/unleash/admin/events.mdx @@ -3,6 +3,10 @@ title: /api/admin/events --- import ApiRequest from '@site/src/components/ApiRequest' +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note diff --git a/website/docs/reference/api/legacy/unleash/admin/feature-types.md b/website/docs/reference/api/legacy/unleash/admin/feature-types.md index c6ed58bcec..8478e34aab 100644 --- a/website/docs/reference/api/legacy/unleash/admin/feature-types.md +++ b/website/docs/reference/api/legacy/unleash/admin/feature-types.md @@ -3,6 +3,10 @@ id: feature-types title: /api/admin/feature-types --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + > In order to access the admin API endpoints you need to identify yourself. Unless you're using the `none` authentication method, you'll need to [create an ADMIN token](/how-to/how-to-create-api-tokens) and add an Authorization header using the token. # Feature Types API diff --git a/website/docs/reference/api/legacy/unleash/admin/features-v2.mdx b/website/docs/reference/api/legacy/unleash/admin/features-v2.mdx index 749eaf34e5..a54127b01d 100644 --- a/website/docs/reference/api/legacy/unleash/admin/features-v2.mdx +++ b/website/docs/reference/api/legacy/unleash/admin/features-v2.mdx @@ -2,6 +2,10 @@ title: /api/admin/projects/:projectId --- import ApiRequest from '@site/src/components/ApiRequest' +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::info In order to access the admin API endpoints you need to identify yourself. Unless you're using the `none` authentication method, you'll need to [create an **admin** token](/how-to/how-to-create-api-tokens) and add an Authorization header using the token. diff --git a/website/docs/reference/api/legacy/unleash/admin/features.md b/website/docs/reference/api/legacy/unleash/admin/features.md index 131ddf3661..09b9c1268b 100644 --- a/website/docs/reference/api/legacy/unleash/admin/features.md +++ b/website/docs/reference/api/legacy/unleash/admin/features.md @@ -2,6 +2,10 @@ title: /api/admin/features --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::caution Deprecation notice Most of this API was removed in Unleash v5 (after being deprecated since Unleash v4.3). You should use [the project-based API (/api/admin/projects/:projectId)](/reference/api/legacy/unleash/admin/features-v2.mdx) instead. diff --git a/website/docs/reference/api/legacy/unleash/admin/metrics.md b/website/docs/reference/api/legacy/unleash/admin/metrics.md index b245e1747b..53244803ef 100644 --- a/website/docs/reference/api/legacy/unleash/admin/metrics.md +++ b/website/docs/reference/api/legacy/unleash/admin/metrics.md @@ -3,6 +3,10 @@ id: metrics title: /api/admin/metrics --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + > In order to access the admin API endpoints you need to identify yourself. Unless you're using the `none` authentication method, you'll need to [create an ADMIN token](/how-to/how-to-create-api-tokens) and add an Authorization header using the token. # This document describes the metrics endpoint for admin ui diff --git a/website/docs/reference/api/legacy/unleash/admin/projects.md b/website/docs/reference/api/legacy/unleash/admin/projects.md index d003e65e82..c62ac5ff59 100644 --- a/website/docs/reference/api/legacy/unleash/admin/projects.md +++ b/website/docs/reference/api/legacy/unleash/admin/projects.md @@ -2,6 +2,10 @@ title: /api/admin/projects --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + > The context feature is only available as part of Unleash Enterprise. In order to access the API programmatically you need to make sure you [obtain an API token](/how-to/how-to-create-api-tokens) with admin permissions. ### List projects in Unleash {#list-projects-in-unleash} diff --git a/website/docs/reference/api/legacy/unleash/admin/segments.mdx b/website/docs/reference/api/legacy/unleash/admin/segments.mdx index 894de27747..2ac382c0a6 100644 --- a/website/docs/reference/api/legacy/unleash/admin/segments.mdx +++ b/website/docs/reference/api/legacy/unleash/admin/segments.mdx @@ -2,6 +2,10 @@ title: /api/admin/segments --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + import ApiRequest from '@site/src/components/ApiRequest'; export const basePath = "api/admin/segments"; export const path = (p) => `${basePath}/${p}`; :::note Availability diff --git a/website/docs/reference/api/legacy/unleash/admin/state.md b/website/docs/reference/api/legacy/unleash/admin/state.md index ec3457da0c..1688a11338 100644 --- a/website/docs/reference/api/legacy/unleash/admin/state.md +++ b/website/docs/reference/api/legacy/unleash/admin/state.md @@ -3,6 +3,10 @@ id: state title: /api/admin/state --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::caution Removal notice Api admin state is deprecated from version 5 and removed in version 6. We recommend using the new [Environment Import & Export](https://docs.getunleash.io/reference/deploy/environment-import-export). diff --git a/website/docs/reference/api/legacy/unleash/admin/strategies.md b/website/docs/reference/api/legacy/unleash/admin/strategies.md index 70aec153d4..fe44997dae 100644 --- a/website/docs/reference/api/legacy/unleash/admin/strategies.md +++ b/website/docs/reference/api/legacy/unleash/admin/strategies.md @@ -2,6 +2,10 @@ title: /api/admin/strategies --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + > In order to access the admin API endpoints you need to identify yourself. Unless you're using the `none` authentication method, you'll need to [create an ADMIN token](/how-to/how-to-create-api-tokens) and add an Authorization header using the token. ### Fetch Strategies {#fetch-strategies} diff --git a/website/docs/reference/api/legacy/unleash/admin/tags.md b/website/docs/reference/api/legacy/unleash/admin/tags.md index cc43207348..755da9d96b 100644 --- a/website/docs/reference/api/legacy/unleash/admin/tags.md +++ b/website/docs/reference/api/legacy/unleash/admin/tags.md @@ -3,6 +3,10 @@ id: tags title: /api/admin/tags --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + > In order to access the admin API endpoints you need to identify yourself. Unless you're using the `none` authentication method, you'll need to [create an ADMIN token](/how-to/how-to-create-api-tokens) and add an Authorization header using the token. ### Create a new tag {#create-a-new-tag} diff --git a/website/docs/reference/api/legacy/unleash/admin/user-admin.md b/website/docs/reference/api/legacy/unleash/admin/user-admin.md index 20c83cd4a1..a1bc915dee 100644 --- a/website/docs/reference/api/legacy/unleash/admin/user-admin.md +++ b/website/docs/reference/api/legacy/unleash/admin/user-admin.md @@ -2,6 +2,10 @@ title: /api/admin/user-admin --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + > In order to access the admin API endpoints you need to identify yourself. Unless you're using the `none` authentication method, you'll need to [create an ADMIN token](/how-to/how-to-create-api-tokens) and add an Authorization header using the token. ### List all users {#list-all-users} diff --git a/website/docs/reference/api/legacy/unleash/basic-auth.md b/website/docs/reference/api/legacy/unleash/basic-auth.md index 8768efb9a9..81e1a89325 100644 --- a/website/docs/reference/api/legacy/unleash/basic-auth.md +++ b/website/docs/reference/api/legacy/unleash/basic-auth.md @@ -2,6 +2,10 @@ title: Basic Auth --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + # Basic auth When using the `insecure` authentication method, identifying using basic auth against the API is enough. Since the `insecure` method doesn't require a password, it is enough to define the username when making HTTP requests. diff --git a/website/docs/reference/api/legacy/unleash/client/features.md b/website/docs/reference/api/legacy/unleash/client/features.md index 1cd17977ae..26d05aab98 100644 --- a/website/docs/reference/api/legacy/unleash/client/features.md +++ b/website/docs/reference/api/legacy/unleash/client/features.md @@ -2,6 +2,10 @@ title: /api/client/features --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + > In order to access the client API endpoints you need to identify yourself. Unless you're using the `none` authentication method, you'll need to [create a CLIENT token](/how-to/how-to-create-api-tokens.mdx) and add an Authorization header using the token. ### Fetching Feature Flags {#fetching-feature-toggles} diff --git a/website/docs/reference/api/legacy/unleash/client/metrics.md b/website/docs/reference/api/legacy/unleash/client/metrics.md index 0ee9378a99..d3015451b6 100644 --- a/website/docs/reference/api/legacy/unleash/client/metrics.md +++ b/website/docs/reference/api/legacy/unleash/client/metrics.md @@ -3,6 +3,10 @@ id: metrics title: /api/client/metrics --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + > In order to access the client API endpoints you need to identify yourself. Unless you're using the `none` authentication method, you'll need to [create a CLIENT token](/how-to/how-to-create-api-tokens) and add an Authorization header using the token. ### Send metrics {#send-metrics} diff --git a/website/docs/reference/api/legacy/unleash/client/register.md b/website/docs/reference/api/legacy/unleash/client/register.md index d47e97950e..4dd5d55736 100644 --- a/website/docs/reference/api/legacy/unleash/client/register.md +++ b/website/docs/reference/api/legacy/unleash/client/register.md @@ -3,6 +3,10 @@ id: register title: /api/client/register --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + > In order to access the client API endpoints you need to identify yourself. Unless you're using the `none` authentication method, you'll need to [create a CLIENT token](/how-to/how-to-create-api-tokens) and add an Authorization header using the token. ### Client registration {#client-registration} diff --git a/website/docs/reference/api/legacy/unleash/index.md b/website/docs/reference/api/legacy/unleash/index.md index c8c86c13db..733fa38cdf 100644 --- a/website/docs/reference/api/legacy/unleash/index.md +++ b/website/docs/reference/api/legacy/unleash/index.md @@ -3,6 +3,10 @@ id: index title: Legacy API Documentation --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::caution These APIs have been deprecared. Wse the [Unleash OpenAPI docs](/api-overview) reference instead. diff --git a/website/docs/reference/api/legacy/unleash/internal/health.md b/website/docs/reference/api/legacy/unleash/internal/health.md index 1cbe3409ec..ebbb11fda2 100644 --- a/website/docs/reference/api/legacy/unleash/internal/health.md +++ b/website/docs/reference/api/legacy/unleash/internal/health.md @@ -2,6 +2,10 @@ title: /health --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + # Health API `GET http://unleash.host.com/health` diff --git a/website/docs/reference/api/legacy/unleash/internal/prometheus.md b/website/docs/reference/api/legacy/unleash/internal/prometheus.md index 3f5811d6a4..5597662e8b 100644 --- a/website/docs/reference/api/legacy/unleash/internal/prometheus.md +++ b/website/docs/reference/api/legacy/unleash/internal/prometheus.md @@ -2,6 +2,11 @@ title: /internal-backstage/prometheus --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + + # Internal Backstage API `GET http://unleash.host.com/internal-backstage/prometheus` diff --git a/website/docs/reference/applications.mdx b/website/docs/reference/applications.mdx index 0e4a88dfea..9400bb4251 100644 --- a/website/docs/reference/applications.mdx +++ b/website/docs/reference/applications.mdx @@ -3,6 +3,10 @@ title: Applications pagination_next: reference/service-accounts --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Version**: `5.11+` diff --git a/website/docs/reference/banners.md b/website/docs/reference/banners.md index 3eb1384135..a2c50db271 100644 --- a/website/docs/reference/banners.md +++ b/website/docs/reference/banners.md @@ -2,6 +2,10 @@ title: Banners --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Plan**: [Enterprise](https://www.getunleash.io/pricing) | **Version**: `5.7+` diff --git a/website/docs/reference/change-requests.mdx b/website/docs/reference/change-requests.mdx index 860d3a0c88..d1df7df8e0 100644 --- a/website/docs/reference/change-requests.mdx +++ b/website/docs/reference/change-requests.mdx @@ -3,6 +3,9 @@ title: Change requests --- import VideoContent from '@site/src/components/VideoContent.jsx'; +import SearchPriority from '@site/src/components/SearchPriority'; + + :::note Availability diff --git a/website/docs/reference/command-menu.md b/website/docs/reference/command-menu.md index c5d369b79e..b7d2857372 100644 --- a/website/docs/reference/command-menu.md +++ b/website/docs/reference/command-menu.md @@ -2,6 +2,10 @@ title: Command menu --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Version**: `6.2+` diff --git a/website/docs/reference/custom-activation-strategies.md b/website/docs/reference/custom-activation-strategies.md index 8c8dd1cebf..1d0ec38d64 100644 --- a/website/docs/reference/custom-activation-strategies.md +++ b/website/docs/reference/custom-activation-strategies.md @@ -2,6 +2,10 @@ title: Custom activation strategies --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + **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. Custom activation strategies work exactly like the built-in activation strategies when working in the admin UI. diff --git a/website/docs/reference/environment-import-export.mdx b/website/docs/reference/environment-import-export.mdx index f28e662650..4b5b4a6f5e 100644 --- a/website/docs/reference/environment-import-export.mdx +++ b/website/docs/reference/environment-import-export.mdx @@ -2,6 +2,9 @@ title: Import and export --- import ApiRequest from '@site/src/components/ApiRequest' +import SearchPriority from '@site/src/components/SearchPriority'; + + :::note Availability diff --git a/website/docs/reference/environments.md b/website/docs/reference/environments.md index ca06deaa46..6e9ed63f0c 100644 --- a/website/docs/reference/environments.md +++ b/website/docs/reference/environments.md @@ -3,11 +3,16 @@ id: environments title: Environments --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Version**: `4.3+` ::: + ## Overview Environments represent different stages in your development lifecycle. They allow you to manage your product releases from local development to production. [Projects](/reference/projects) and [feature flags](/reference/feature-toggles) are accessible in all environments, but each environment has different feature flag configurations. This allows you to enable a flag in development or test without enabling it in production. diff --git a/website/docs/reference/events.mdx b/website/docs/reference/events.mdx index cd4e057d81..c9735ab59e 100644 --- a/website/docs/reference/events.mdx +++ b/website/docs/reference/events.mdx @@ -1,6 +1,10 @@ --- title: Events --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + ## Overview diff --git a/website/docs/reference/feature-toggle-variants.md b/website/docs/reference/feature-toggle-variants.md index 69cfbb7c3e..c8aca6d1e4 100644 --- a/website/docs/reference/feature-toggle-variants.md +++ b/website/docs/reference/feature-toggle-variants.md @@ -1,6 +1,11 @@ --- title: Feature flag variants (deprecated) --- + +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::warning Feature Flag Variants at the environment level are deprecated in favor of the [strategy variants](./strategy-variants). diff --git a/website/docs/reference/feature-toggles.mdx b/website/docs/reference/feature-toggles.mdx index 3651126035..97daad49b8 100644 --- a/website/docs/reference/feature-toggles.mdx +++ b/website/docs/reference/feature-toggles.mdx @@ -3,6 +3,10 @@ title: Feature flags pagination_next: reference/activation-strategies --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + ## Overview Feature flags are a core concept of Unleash. They allow you to release, test, and manage features and functionality across your application without changing the source code. diff --git a/website/docs/reference/front-end-api.md b/website/docs/reference/front-end-api.md index 321a4c32c8..b1727bc1ee 100644 --- a/website/docs/reference/front-end-api.md +++ b/website/docs/reference/front-end-api.md @@ -2,6 +2,10 @@ title: Frontend API --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Version**: `4.18+` diff --git a/website/docs/reference/impression-data.md b/website/docs/reference/impression-data.md index 922218f567..a31cbe9ec0 100644 --- a/website/docs/reference/impression-data.md +++ b/website/docs/reference/impression-data.md @@ -3,6 +3,10 @@ title: Impression data pagination_next: reference/events --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Version**: `4.7+`. Requires [SDK compatibility](../reference/sdks#feature-compatibility-in-server-side-sdks). diff --git a/website/docs/reference/insights.mdx b/website/docs/reference/insights.mdx index 10dda53bef..72e08e56ea 100644 --- a/website/docs/reference/insights.mdx +++ b/website/docs/reference/insights.mdx @@ -2,6 +2,10 @@ title: Analytics --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Plan**: [Pro](/availability#plans) and [Enterprise](https://www.getunleash.io/pricing) | **Version**: `6.0+` diff --git a/website/docs/reference/integrations/datadog.md b/website/docs/reference/integrations/datadog.md index 16488cd696..ff3cfb57d2 100644 --- a/website/docs/reference/integrations/datadog.md +++ b/website/docs/reference/integrations/datadog.md @@ -3,6 +3,10 @@ id: datadog title: Datadog --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Version**: `4.0+` diff --git a/website/docs/reference/integrations/integrations.mdx b/website/docs/reference/integrations/integrations.mdx index 3184b1ffdb..1fcf34ece8 100644 --- a/website/docs/reference/integrations/integrations.mdx +++ b/website/docs/reference/integrations/integrations.mdx @@ -3,6 +3,9 @@ id: index title: Integrations --- import DocCardList from '@theme/DocCardList'; +import SearchPriority from '@site/src/components/SearchPriority'; + + :::note Availability diff --git a/website/docs/reference/integrations/jira-cloud-plugin-installation.mdx b/website/docs/reference/integrations/jira-cloud-plugin-installation.mdx index faca997cb9..5412990171 100644 --- a/website/docs/reference/integrations/jira-cloud-plugin-installation.mdx +++ b/website/docs/reference/integrations/jira-cloud-plugin-installation.mdx @@ -2,6 +2,11 @@ title: Jira Cloud Integration - Installation --- + +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Plan**: [Pro](/availability#plans) and [Enterprise](https://www.getunleash.io/pricing) | **Version**: `4.0+` diff --git a/website/docs/reference/integrations/jira-cloud-plugin-usage.mdx b/website/docs/reference/integrations/jira-cloud-plugin-usage.mdx index f44ec1bd17..734672cf21 100644 --- a/website/docs/reference/integrations/jira-cloud-plugin-usage.mdx +++ b/website/docs/reference/integrations/jira-cloud-plugin-usage.mdx @@ -2,6 +2,10 @@ title: Jira Cloud Integration - Usage --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + With the Unleash Jira Cloud Plugin you can create, view and manage Unleash feature flags directly from a Jira issue. The plugin also shows you current status of connected flags. diff --git a/website/docs/reference/integrations/jira-server-plugin-installation.md b/website/docs/reference/integrations/jira-server-plugin-installation.md index f1d36c1ed1..9be2b9cc1c 100644 --- a/website/docs/reference/integrations/jira-server-plugin-installation.md +++ b/website/docs/reference/integrations/jira-server-plugin-installation.md @@ -2,6 +2,10 @@ title: Jira Server Integration - Installation --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::info Deprecated The Jira Server plugin is **deprecated**, please use the [Unleash Jira Cloud plugin](https://docs.getunleash.io/reference/integrations/jira-cloud-plugin-installation) instead diff --git a/website/docs/reference/integrations/jira-server-plugin-usage.md b/website/docs/reference/integrations/jira-server-plugin-usage.md index 0cbe62cd5d..7ab2cb6fb1 100644 --- a/website/docs/reference/integrations/jira-server-plugin-usage.md +++ b/website/docs/reference/integrations/jira-server-plugin-usage.md @@ -2,6 +2,10 @@ title: Jira Server Integration - Usage --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::info Deprecated The Jira Server plugin is **deprecated**, please use the [Unleash Jira Cloud plugin](https://docs.getunleash.io/reference/integrations/jira-cloud-plugin-installation) instead diff --git a/website/docs/reference/integrations/slack-app.mdx b/website/docs/reference/integrations/slack-app.mdx index 0b63f902e4..0ffbc5b252 100644 --- a/website/docs/reference/integrations/slack-app.mdx +++ b/website/docs/reference/integrations/slack-app.mdx @@ -2,6 +2,10 @@ title: App for Slack --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Version**: `5.5+` diff --git a/website/docs/reference/integrations/slack.md b/website/docs/reference/integrations/slack.md index 07cfcfab1f..3f554a2451 100644 --- a/website/docs/reference/integrations/slack.md +++ b/website/docs/reference/integrations/slack.md @@ -3,6 +3,10 @@ id: slack title: Slack (deprecated) --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::caution Deprecation notice This Slack integration is deprecated and will be removed in a future release. We recommend using the new [App for Slack](./slack-app) integration instead. diff --git a/website/docs/reference/integrations/teams.md b/website/docs/reference/integrations/teams.md index cecf854a6b..2555f2c302 100644 --- a/website/docs/reference/integrations/teams.md +++ b/website/docs/reference/integrations/teams.md @@ -3,6 +3,10 @@ id: teams title: Microsoft Teams --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Version**: `4.0+` diff --git a/website/docs/reference/integrations/webhook.md b/website/docs/reference/integrations/webhook.md index 74c0e10186..9cce273846 100644 --- a/website/docs/reference/integrations/webhook.md +++ b/website/docs/reference/integrations/webhook.md @@ -3,6 +3,10 @@ id: webhook title: Webhook --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Version**: `3.11+` diff --git a/website/docs/reference/login-history.mdx b/website/docs/reference/login-history.mdx index 5a8c69dd06..146dca0b1e 100644 --- a/website/docs/reference/login-history.mdx +++ b/website/docs/reference/login-history.mdx @@ -2,6 +2,10 @@ title: Login history --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Plan**: [Enterprise](https://www.getunleash.io/pricing) | **Version**: `4.22+` diff --git a/website/docs/reference/maintenance-mode.mdx b/website/docs/reference/maintenance-mode.mdx index fa58371165..2de17ac167 100644 --- a/website/docs/reference/maintenance-mode.mdx +++ b/website/docs/reference/maintenance-mode.mdx @@ -2,6 +2,10 @@ title: Maintenance mode --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Version**: `4.22+` diff --git a/website/docs/reference/network-view.mdx b/website/docs/reference/network-view.mdx index ab3508c002..4bf9c5bd97 100644 --- a/website/docs/reference/network-view.mdx +++ b/website/docs/reference/network-view.mdx @@ -2,6 +2,10 @@ title: Network --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Plan**: [Pro](/availability#plans) and [Enterprise](https://www.getunleash.io/pricing) | **Version**: `4.21+` diff --git a/website/docs/reference/playground.mdx b/website/docs/reference/playground.mdx index 1fac57d04d..87b0d2aee8 100644 --- a/website/docs/reference/playground.mdx +++ b/website/docs/reference/playground.mdx @@ -2,6 +2,9 @@ title: Playground --- import VideoContent from '@site/src/components/VideoContent.jsx' +import SearchPriority from '@site/src/components/SearchPriority'; + + :::note Availability diff --git a/website/docs/reference/project-collaboration-mode.md b/website/docs/reference/project-collaboration-mode.md index 49d3de12c3..289c138288 100644 --- a/website/docs/reference/project-collaboration-mode.md +++ b/website/docs/reference/project-collaboration-mode.md @@ -2,6 +2,10 @@ title: Project collaboration mode --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Plan**: [Enterprise](https://www.getunleash.io/pricing) | **Version**: `4.22+` diff --git a/website/docs/reference/projects.mdx b/website/docs/reference/projects.mdx index 794c301aee..7c40583cb0 100644 --- a/website/docs/reference/projects.mdx +++ b/website/docs/reference/projects.mdx @@ -4,6 +4,10 @@ title: Projects pagination_next: reference/project-collaboration-mode --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + ## Overview Projects help you organize feature flags within Unleash. For example, you can use projects to group feature flags by development teams or different functional modules within your application. diff --git a/website/docs/reference/public-signup.mdx b/website/docs/reference/public-signup.mdx index 88dc592aba..521447ad90 100644 --- a/website/docs/reference/public-signup.mdx +++ b/website/docs/reference/public-signup.mdx @@ -2,6 +2,10 @@ title: Public invite links --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + 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. 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. diff --git a/website/docs/reference/rbac.md b/website/docs/reference/rbac.md index a77d99c9a5..3bc66427ca 100644 --- a/website/docs/reference/rbac.md +++ b/website/docs/reference/rbac.md @@ -3,6 +3,10 @@ id: rbac title: Role-based access control --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Version**: `4.0+` diff --git a/website/docs/reference/release-templates.mdx b/website/docs/reference/release-templates.mdx index b60854a853..a82f70081b 100644 --- a/website/docs/reference/release-templates.mdx +++ b/website/docs/reference/release-templates.mdx @@ -2,6 +2,10 @@ title: Release templates --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Plan**: [Enterprise](https://www.getunleash.io/pricing) cloud-hosted | **Version**: `6.8+` in [BETA](/availability#beta-features). diff --git a/website/docs/reference/resource-limits.mdx b/website/docs/reference/resource-limits.mdx index 32e0fc0ca8..fe290fa9c4 100644 --- a/website/docs/reference/resource-limits.mdx +++ b/website/docs/reference/resource-limits.mdx @@ -2,6 +2,10 @@ title: Resource limits --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Version**: `6.2+` diff --git a/website/docs/reference/scim.md b/website/docs/reference/scim.md index f8c6f685c5..bb64cd3cc5 100644 --- a/website/docs/reference/scim.md +++ b/website/docs/reference/scim.md @@ -3,6 +3,10 @@ id: scim title: Provisioning --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Plan**: [Enterprise](https://www.getunleash.io/pricing) | **Version**: `6.1+` diff --git a/website/docs/reference/sdks/index.mdx b/website/docs/reference/sdks/index.mdx index a053d87d76..7e471a46c7 100644 --- a/website/docs/reference/sdks/index.mdx +++ b/website/docs/reference/sdks/index.mdx @@ -2,6 +2,10 @@ title: SDK overview --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + Unleash offers a number of client libraries (SDKs) designed to help you integrate Unleash into your applications. The SDKs provide an interface for fetching and evaluating feature flags. With [Unleash's architecture](../understanding-unleash/unleash-overview#system-overview), feature flags can be evaluated within the SDKs or [Unleash Edge](./unleash-edge), making evaluations incredibly fast. SDKs cache feature flag data in memory, providing high reliability. diff --git a/website/docs/reference/search-operators.md b/website/docs/reference/search-operators.md index c612d07998..7157eb61f1 100644 --- a/website/docs/reference/search-operators.md +++ b/website/docs/reference/search-operators.md @@ -2,6 +2,10 @@ title: Search --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Version**: `5.9+` diff --git a/website/docs/reference/segments.mdx b/website/docs/reference/segments.mdx index 9507782578..a3adb58fc1 100644 --- a/website/docs/reference/segments.mdx +++ b/website/docs/reference/segments.mdx @@ -3,7 +3,9 @@ title: Segments --- import VideoContent from '@site/src/components/VideoContent.jsx' +import SearchPriority from '@site/src/components/SearchPriority'; + :::note Availability diff --git a/website/docs/reference/service-accounts.md b/website/docs/reference/service-accounts.md index 6145828cf3..28acb74662 100644 --- a/website/docs/reference/service-accounts.md +++ b/website/docs/reference/service-accounts.md @@ -2,6 +2,10 @@ title: Service accounts --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Plan**: [Enterprise](https://www.getunleash.io/pricing) | **Version**: `4.21+` diff --git a/website/docs/reference/signals.md b/website/docs/reference/signals.md index a3c3dd9e8f..bc1e55934a 100644 --- a/website/docs/reference/signals.md +++ b/website/docs/reference/signals.md @@ -2,6 +2,10 @@ title: Signals --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Plan**: [Enterprise](https://www.getunleash.io/pricing) | **Version**: `5.11+` in BETA diff --git a/website/docs/reference/sso.md b/website/docs/reference/sso.md index 6f65c587cd..74582015b7 100644 --- a/website/docs/reference/sso.md +++ b/website/docs/reference/sso.md @@ -2,6 +2,9 @@ title: Single Sign-On --- +import SearchPriority from '@site/src/components/SearchPriority'; + + :::note Availability diff --git a/website/docs/reference/stickiness.md b/website/docs/reference/stickiness.md index f24912f844..430cad1a5a 100644 --- a/website/docs/reference/stickiness.md +++ b/website/docs/reference/stickiness.md @@ -2,6 +2,10 @@ title: Stickiness --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + _Stickiness_ is how Unleash guarantees that the same user gets the same features every time. Stickiness is useful in any scenario where you want to either show a feature to only a subset of users or give users a variant of a feature. ## Calculation diff --git a/website/docs/reference/strategy-variants.mdx b/website/docs/reference/strategy-variants.mdx index a615d91e48..41854d05ba 100644 --- a/website/docs/reference/strategy-variants.mdx +++ b/website/docs/reference/strategy-variants.mdx @@ -3,6 +3,9 @@ title: Strategy variants --- import VideoContent from '@site/src/components/VideoContent.jsx' +import SearchPriority from '@site/src/components/SearchPriority'; + + :::note Availability diff --git a/website/docs/reference/technical-debt.md b/website/docs/reference/technical-debt.md index 21e81f8781..7cd69d2a49 100644 --- a/website/docs/reference/technical-debt.md +++ b/website/docs/reference/technical-debt.md @@ -3,6 +3,10 @@ title: Technical debt pagination_next: reference/insights --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + ## Overview Feature flag technical debt accumulates when you don’t manage or retire feature flags after their intended use. Over time, the codebase becomes cluttered with outdated flags, making the code more complex and harder to maintain. This can slow productivity as developers spend more time understanding and navigating the code. diff --git a/website/docs/reference/terraform.mdx b/website/docs/reference/terraform.mdx index 6090eef42a..f1267eee54 100644 --- a/website/docs/reference/terraform.mdx +++ b/website/docs/reference/terraform.mdx @@ -3,6 +3,10 @@ title: Using Unleash through Terraform description: "Set up and configure your Unleash instance using infastructure as code." --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + :::note Availability **Version**: `5.6+` diff --git a/website/docs/reference/unleash-context.md b/website/docs/reference/unleash-context.md index 9a63c32ee4..419a156241 100644 --- a/website/docs/reference/unleash-context.md +++ b/website/docs/reference/unleash-context.md @@ -2,6 +2,10 @@ title: Unleash context --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + 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 diff --git a/website/docs/topics/feature-flags/best-practices-using-feature-flags-at-scale.mdx b/website/docs/topics/feature-flags/best-practices-using-feature-flags-at-scale.mdx index c74858e99e..303d0958c7 100644 --- a/website/docs/topics/feature-flags/best-practices-using-feature-flags-at-scale.mdx +++ b/website/docs/topics/feature-flags/best-practices-using-feature-flags-at-scale.mdx @@ -3,6 +3,9 @@ title: 'Feature flag management: Best practices' description: 'A guide for feature flag management. Best practices for organization, lifecycle management, and avoiding common pitfalls to keep your system efficient and secure.' toc_max_heading_level: 2 --- +import SearchPriority from '@site/src/components/SearchPriority'; + + # Feature flag management at scale: best practices diff --git a/website/docs/topics/feature-flags/feature-flag-best-practices.mdx b/website/docs/topics/feature-flags/feature-flag-best-practices.mdx index 2f789e38ed..61810fa8a8 100644 --- a/website/docs/topics/feature-flags/feature-flag-best-practices.mdx +++ b/website/docs/topics/feature-flags/feature-flag-best-practices.mdx @@ -6,6 +6,9 @@ pagination_next: topics/feature-flags/best-practices-using-feature-flags-at-scal --- import VideoContent from '@site/src/components/VideoContent.jsx'; +import SearchPriority from '@site/src/components/SearchPriority'; + + Feature flags, [sometimes called feature toggles or feature switches](../../what-is-a-feature-flag), are a powerful software development technique that allows engineering teams to decouple the release of new functionality from software deployments. diff --git a/website/docs/understanding-unleash/hosting-options.mdx b/website/docs/understanding-unleash/hosting-options.mdx index 1df9e812f1..5cbf203c3c 100644 --- a/website/docs/understanding-unleash/hosting-options.mdx +++ b/website/docs/understanding-unleash/hosting-options.mdx @@ -3,6 +3,10 @@ title: Unleash hosting options description: "Explore the flexible hosting options for Unleash and Unleash Edge, including cloud-hosted, hybrid, and self-hosted deployments. Compare features, scalability, and support to find the right setup for your team." --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + Unleash provides flexible hosting options for Unleash and [Unleash Edge](../generated/unleash-edge.md), allowing you to meet specific requirements for scaling, privacy, and infrastructure control. This document covers the main hosting models for the Unleash API server and Unleash Edge. Choosing a hosting model is a key architectural decision. Before considering hosting options, we recommend that you explore the [Unleash architecture and its key components](./unleash-overview). diff --git a/website/docs/understanding-unleash/the-anatomy-of-unleash.mdx b/website/docs/understanding-unleash/the-anatomy-of-unleash.mdx index 193fc534d2..5527ec061b 100644 --- a/website/docs/understanding-unleash/the-anatomy-of-unleash.mdx +++ b/website/docs/understanding-unleash/the-anatomy-of-unleash.mdx @@ -2,6 +2,10 @@ title: Core concepts --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + This guide's purpose is to give you a conceptual overview of how Unleash works. It covers the various components that exist within our system and how they interact with each other and with external applications. The diagrams help you understand the fundamental building blocks, such as [projects](../reference/projects), [environments](../reference/environments), [variants](../reference/strategy-variants) and of course, [feature flags](../reference/feature-toggles). The end of this guide presents a [short use case, explaining how you might configure Unleash](#use-case) to start working with feature flags. diff --git a/website/docs/understanding-unleash/unleash-overview.md b/website/docs/understanding-unleash/unleash-overview.md index 4afa1a8cd4..3f7f5fd524 100644 --- a/website/docs/understanding-unleash/unleash-overview.md +++ b/website/docs/understanding-unleash/unleash-overview.md @@ -3,6 +3,10 @@ title: Unleash architecture overview pagination_next: understanding-unleash/the-anatomy-of-unleash --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + Unleash is designed for privacy, speed, and resilience, enabling feature flag evaluations to occur locally within your applications. The architecture provides: - **Fast feature flag evaluations**: Feature flags are evaluated within the [SDKs](#unleash-sdks) or [Unleash Edge](#unleash-edge), making evaluations incredibly fast (nanoseconds). - **Privacy and security**: No user data is shared with the Unleash server, ensuring [privacy and security](/understanding-unleash/data-collection). diff --git a/website/docs/using-unleash/compliance/compliance-overview.mdx b/website/docs/using-unleash/compliance/compliance-overview.mdx index 2fc080def4..332c85f8ce 100644 --- a/website/docs/using-unleash/compliance/compliance-overview.mdx +++ b/website/docs/using-unleash/compliance/compliance-overview.mdx @@ -3,6 +3,10 @@ title: Compliance for feature flags description: 'Secure and compliant feature flags at scale with Unleash.' --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + # Compliance ## Overview diff --git a/website/docs/using-unleash/deploy/configuring-unleash.mdx b/website/docs/using-unleash/deploy/configuring-unleash.mdx index eae29c3431..38e4be8742 100644 --- a/website/docs/using-unleash/deploy/configuring-unleash.mdx +++ b/website/docs/using-unleash/deploy/configuring-unleash.mdx @@ -4,6 +4,10 @@ description: "Steps and options for configuring your self-hosted Unleash instanc toc_max_heading_level: 3 --- +import SearchPriority from '@site/src/components/SearchPriority'; + + + import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; This guide explains how to configure your self-hosted Unleash instance when running it via [Docker Compose or the Docker CLI](/using-unleash/deploy/getting-started). diff --git a/website/docs/using-unleash/deploy/getting-started.md b/website/docs/using-unleash/deploy/getting-started.md index 320f1cc706..44f1a0b851 100644 --- a/website/docs/using-unleash/deploy/getting-started.md +++ b/website/docs/using-unleash/deploy/getting-started.md @@ -7,6 +7,9 @@ pagination_next: using-unleash/deploy/configuring-unleash import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; +import SearchPriority from '@site/src/components/SearchPriority'; + + Unleash offers several [hosting options](/understanding-unleash/hosting-options), including fully self-hosted setups. This guide helps you set up Unleash Open Source or Unleash Enterprise in your own environment using Docker. diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index 4be3cb8a97..52be66bd12 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -541,10 +541,6 @@ class="header-github-link" ], to: '/reference/integrations/webhook', }, - { - from: '/guides/feature_updates_to_slack', - to: '/how-to/how-to-send-feature-updates-to-slack-deprecated', - }, { from: [ '/integrations/integrations', diff --git a/website/src/components/SearchPriority.jsx b/website/src/components/SearchPriority.jsx new file mode 100644 index 0000000000..7da84c13f4 --- /dev/null +++ b/website/src/components/SearchPriority.jsx @@ -0,0 +1,35 @@ +// src/components/SearchPriority.js +import React from 'react'; +import Head from '@docusaurus/Head'; + +// Define the mapping from level names to numbers +const priorityMap = { + low: 1, + medium: 2, + high: 3, +}; + +export default function SearchPriority({ level }) { + // If no level is provided, render nothing. + if (!level) { + return null; + } + + // If level is 'noindex', render the robots tag. + if (level === 'noindex') { + return ( + + + + ); + } + + // If a valid level was found, render the priority tag + const priorityValue = priorityMap[level]; + + return priorityValue ? ( + + + + ) : null; +} diff --git a/website/vercel.json b/website/vercel.json index f49394785d..0813d9e50d 100644 --- a/website/vercel.json +++ b/website/vercel.json @@ -311,11 +311,6 @@ "destination": "/reference/integrations/webhook", "permanent": true }, - { - "source": "/guides/feature_updates_to_slack", - "destination": "/how-to/how-to-send-feature-updates-to-slack-deprecated", - "permanent": true - }, { "source": "/integrations/integrations", "destination": "/reference/integrations", From c1df04548dd8742804d50efe3f8038a2423128cf Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Mon, 21 Jul 2025 10:50:14 +0200 Subject: [PATCH 05/14] feat: add impact metrics wildcard label (#10373) --- .../components/LabelsFilter.tsx | 118 +++++++++++++----- .../impact-metrics/hooks/useChartData.ts | 1 + 2 files changed, 85 insertions(+), 34 deletions(-) diff --git a/frontend/src/component/impact-metrics/ImpactMetricsControls/components/LabelsFilter.tsx b/frontend/src/component/impact-metrics/ImpactMetricsControls/components/LabelsFilter.tsx index b65d09f22a..caa5f233a8 100644 --- a/frontend/src/component/impact-metrics/ImpactMetricsControls/components/LabelsFilter.tsx +++ b/frontend/src/component/impact-metrics/ImpactMetricsControls/components/LabelsFilter.tsx @@ -1,5 +1,13 @@ import type { FC } from 'react'; -import { Box, Autocomplete, TextField, Typography, Chip } from '@mui/material'; +import { + Box, + Autocomplete, + TextField, + Typography, + Chip, + Checkbox, + FormControlLabel, +} from '@mui/material'; import type { ImpactMetricsLabels } from 'hooks/api/getters/useImpactMetricsData/useImpactMetricsData'; export type LabelsFilterProps = { @@ -23,6 +31,16 @@ export const LabelsFilter: FC = ({ onChange(newLabels); }; + const handleAllToggle = (labelKey: string, checked: boolean) => { + const newLabels = { ...selectedLabels }; + if (checked) { + newLabels[labelKey] = ['*']; + } else { + delete newLabels[labelKey]; + } + onChange(newLabels); + }; + const clearAllLabels = () => { onChange({}); }; @@ -45,42 +63,74 @@ export const LabelsFilter: FC = ({ )} - {Object.entries(availableLabels).map(([labelKey, values]) => ( - - handleLabelChange(labelKey, newValues) - } - renderTags={(value, getTagProps) => - value.map((option, index) => { - const { key, ...chipProps } = getTagProps({ - index, - }); - return ( - { + const currentSelection = selectedLabels[labelKey] || []; + const isAllSelected = currentSelection.includes('*'); + + return ( + ({ + display: 'flex', + alignItems: 'center', + gap: theme.spacing(3), + })} + > + { + handleLabelChange(labelKey, newValues); + }} + disabled={isAllSelected} + renderTags={(value, getTagProps) => + value.map((option, index) => { + const { key, ...chipProps } = getTagProps({ + index, + }); + return ( + + ); + }) + } + renderInput={(params) => ( + - ); - }) - } - renderInput={(params) => ( - - )} - sx={{ minWidth: 300 }} - /> - ))} + + handleAllToggle( + labelKey, + e.target.checked, + ) + } + /> + } + label='All' + /> + + ); + })} ); }; diff --git a/frontend/src/component/impact-metrics/hooks/useChartData.ts b/frontend/src/component/impact-metrics/hooks/useChartData.ts index 0519908c67..786778f6ce 100644 --- a/frontend/src/component/impact-metrics/hooks/useChartData.ts +++ b/frontend/src/component/impact-metrics/hooks/useChartData.ts @@ -8,6 +8,7 @@ const getColorStartingIndex = (modulo: number, series?: string): number => { return 0; } + // https://stackoverflow.com/a/7616484/1729641 let hash = 0; for (let i = 0; i < series.length; i++) { const char = series.charCodeAt(i); From 8f0c0ccef3fa593eadf593d5ca0a0e7e12d7d719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20G=C3=B3is?= Date: Mon, 21 Jul 2025 12:40:41 +0100 Subject: [PATCH 06/14] chore: sift metrics on both endpoints (#10375) https://linear.app/unleash/issue/2-3696/report-unknown-flags-when-sent-to-the-bulk-metrics-endpoint Unifies metrics sifting logic across both metrics endpoints: - `/metrics` - `/metrics/bulk` This PR improves consistency between the `/metrics` and `/metrics/bulk` endpoints by introducing a shared `siftMetrics` method, now used within `registerBulkMetrics`. Both endpoints already call this method at the end of their respective logic flows, ensuring that metrics are sifted in the same way regardless of the path taken. While the primary goal was to enable reporting of unknown flags via the `/metrics/bulk` endpoint, this change also improves bulk processing by consistently dropping invalid or unknown flags before insertion, just like in the regular `/metrics` endpoint. --- .../client-metrics/metrics-service-v2.test.ts | 2 +- .../client-metrics/metrics-service-v2.ts | 129 ++++++++------ .../features/metrics/instance/metrics.test.ts | 5 + .../unknown-flags/unknown-flags.e2e.test.ts | 158 ++++++++++++++++++ 4 files changed, 245 insertions(+), 49 deletions(-) create mode 100644 src/lib/features/metrics/unknown-flags/unknown-flags.e2e.test.ts diff --git a/src/lib/features/metrics/client-metrics/metrics-service-v2.test.ts b/src/lib/features/metrics/client-metrics/metrics-service-v2.test.ts index 29e4d2148a..5e74b2ae9f 100644 --- a/src/lib/features/metrics/client-metrics/metrics-service-v2.test.ts +++ b/src/lib/features/metrics/client-metrics/metrics-service-v2.test.ts @@ -113,7 +113,7 @@ test('process metrics properly even when some names are not url friendly, filter ); // only toggle with a bad name gets filtered out - expect(eventBus.emit).not.toHaveBeenCalled(); + expect(eventBus.emit).toHaveBeenCalledTimes(1); expect(lastSeenService.updateLastSeen).not.toHaveBeenCalled(); }); diff --git a/src/lib/features/metrics/client-metrics/metrics-service-v2.ts b/src/lib/features/metrics/client-metrics/metrics-service-v2.ts index 7d51b1a7b4..dbb7fb3dd0 100644 --- a/src/lib/features/metrics/client-metrics/metrics-service-v2.ts +++ b/src/lib/features/metrics/client-metrics/metrics-service-v2.ts @@ -32,6 +32,7 @@ import { MetricsTranslator, } from '../impact/metrics-translator.js'; import { impactRegister } from '../impact/impact-register.js'; +import type { UnknownFlag } from '../unknown-flags/unknown-flags-store.js'; export default class ClientMetricsServiceV2 { private config: IUnleashConfig; @@ -178,12 +179,78 @@ export default class ClientMetricsServiceV2 { return toggleNames; } + private async siftMetrics( + metrics: IClientMetricsEnv[], + ): Promise { + if (!metrics.length) return []; + + const metricsByToggle = new Map(); + for (const m of metrics) { + if (m.yes === 0 && m.no === 0) continue; + let arr = metricsByToggle.get(m.featureName); + if (!arr) { + arr = []; + metricsByToggle.set(m.featureName, arr); + } + arr.push(m); + } + if (metricsByToggle.size === 0) return []; + + const toggleNames = Array.from(metricsByToggle.keys()); + + const { validatedToggleNames, unknownToggleNames } = + await this.filterExistingToggleNames(toggleNames); + + const validatedSet = new Set(validatedToggleNames); + const unknownSet = new Set(unknownToggleNames); + + const invalidCount = toggleNames.length - validatedSet.size; + this.logger.debug( + `Got ${toggleNames.length} metrics (${invalidCount > 0 ? `${invalidCount} invalid` : 'all valid'}).`, + ); + + const unknownFlags: UnknownFlag[] = []; + for (const [featureName, group] of metricsByToggle) { + if (unknownSet.has(featureName)) { + for (const m of group) { + unknownFlags.push({ + name: featureName, + appName: m.appName, + seenAt: m.timestamp, + environment: m.environment, + }); + } + } + } + if (unknownFlags.length) { + const sample = unknownFlags + .slice(0, 10) + .map((f) => `"${f.name}"`) + .join(', '); + this.logger.debug( + `Registering ${unknownFlags.length} unknown flags; sample: ${sample}`, + ); + this.unknownFlagsService.register(unknownFlags); + } + + const siftedMetrics: IClientMetricsEnv[] = []; + for (const [featureName, group] of metricsByToggle) { + if (validatedSet.has(featureName)) { + siftedMetrics.push(...group); + } + } + return siftedMetrics; + } + async registerBulkMetrics(metrics: IClientMetricsEnv[]): Promise { + const siftedMetrics = await this.siftMetrics(metrics); + if (siftedMetrics.length === 0) return; + this.unsavedMetrics = collapseHourlyMetrics([ ...this.unsavedMetrics, - ...metrics, + ...siftedMetrics, ]); - this.lastSeenService.updateLastSeen(metrics); + this.lastSeenService.updateLastSeen(siftedMetrics); } async registerImpactMetrics(impactMetrics: Metric[]) { @@ -202,22 +269,6 @@ export default class ClientMetricsServiceV2 { clientIp: string, ): Promise { const value = await clientMetricsSchema.validateAsync(data); - const toggleNames = Object.keys(value.bucket.toggles).filter( - (name) => - !( - value.bucket.toggles[name].yes === 0 && - value.bucket.toggles[name].no === 0 - ), - ); - - const { validatedToggleNames, unknownToggleNames } = - await this.filterExistingToggleNames(toggleNames); - - const invalidToggleNames = - toggleNames.length - validatedToggleNames.length; - this.logger.debug( - `Got ${toggleNames.length} (${invalidToggleNames > 0 ? `${invalidToggleNames} invalid ones` : 'all valid'}) metrics from ${value.appName}`, - ); if (data.sdkVersion) { const [sdkName, sdkVersion] = data.sdkVersion.split(':'); @@ -235,38 +286,20 @@ export default class ClientMetricsServiceV2 { this.config.eventBus.emit(CLIENT_REGISTER, heartbeatEvent); } - const environment = value.environment ?? 'default'; + const clientMetrics: IClientMetricsEnv[] = Object.keys( + value.bucket.toggles, + ).map((name) => ({ + featureName: name, + appName: value.appName, + environment: value.environment ?? 'default', + timestamp: value.bucket.stop, //we might need to approximate between start/stop... + yes: value.bucket.toggles[name].yes ?? 0, + no: value.bucket.toggles[name].no ?? 0, + variants: value.bucket.toggles[name].variants, + })); - if (unknownToggleNames.length > 0) { - const unknownFlags = unknownToggleNames.map((name) => ({ - name, - appName: value.appName, - seenAt: value.bucket.stop, - environment, - })); - 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}"`) - .join(', ')}`, - ); - this.unknownFlagsService.register(unknownFlags); - } - - if (validatedToggleNames.length > 0) { - const clientMetrics: IClientMetricsEnv[] = validatedToggleNames.map( - (name) => ({ - featureName: name, - appName: value.appName, - environment, - timestamp: value.bucket.stop, //we might need to approximate between start/stop... - yes: value.bucket.toggles[name].yes ?? 0, - no: value.bucket.toggles[name].no ?? 0, - variants: value.bucket.toggles[name].variants, - }), - ); + if (clientMetrics.length) { await this.registerBulkMetrics(clientMetrics); - this.config.eventBus.emit(CLIENT_METRICS, clientMetrics); } } diff --git a/src/lib/features/metrics/instance/metrics.test.ts b/src/lib/features/metrics/instance/metrics.test.ts index a3935b1c1e..adfbdcb105 100644 --- a/src/lib/features/metrics/instance/metrics.test.ts +++ b/src/lib/features/metrics/instance/metrics.test.ts @@ -414,6 +414,11 @@ describe('bulk metrics', () => { test('without access to production environment due to no auth setup, we can only access the default env', async () => { const now = new Date(); + // @ts-expect-error - cachedFeatureNames is a private property in ClientMetricsServiceV2 + services.clientMetricsServiceV2.cachedFeatureNames = vi + .fn<() => Promise>() + .mockResolvedValue(['test_feature_one', 'test_feature_two']); + await request .post('/api/client/metrics/bulk') .send({ diff --git a/src/lib/features/metrics/unknown-flags/unknown-flags.e2e.test.ts b/src/lib/features/metrics/unknown-flags/unknown-flags.e2e.test.ts new file mode 100644 index 0000000000..ee2c8d71ac --- /dev/null +++ b/src/lib/features/metrics/unknown-flags/unknown-flags.e2e.test.ts @@ -0,0 +1,158 @@ +import supertest, { type Test } from 'supertest'; +import getApp from '../../../app.js'; +import { createTestConfig } from '../../../../test/config/test-config.js'; +import { + type IUnleashServices, + createServices, +} from '../../../services/index.js'; +import type { + IUnleashConfig, + IUnleashOptions, + IUnleashStores, +} from '../../../types/index.js'; +import dbInit, { + type ITestDb, +} from '../../../../test/e2e/helpers/database-init.js'; +import { startOfHour } from 'date-fns'; +import type TestAgent from 'supertest/lib/agent.d.ts'; +import type { BulkRegistrationSchema } from '../../../openapi/index.js'; + +let db: ITestDb; +let config: IUnleashConfig; + +async function getSetup(opts?: IUnleashOptions) { + config = createTestConfig(opts); + db = await dbInit('unknown_flags', config.getLogger); + + const services = createServices(db.stores, config, db.rawDatabase); + const app = await getApp(config, db.stores, services); + return { + request: supertest(app), + stores: db.stores, + services, + db: db.rawDatabase, + destroy: db.destroy, + }; +} + +let request: TestAgent; +let stores: IUnleashStores; +let services: IUnleashServices; +let destroy: () => Promise; + +beforeAll(async () => { + const setup = await getSetup({ + experimental: { + flags: { + reportUnknownFlags: true, + }, + }, + }); + request = setup.request; + stores = setup.stores; + destroy = setup.destroy; + services = setup.services; +}); + +afterAll(async () => { + await destroy(); +}); + +afterEach(async () => { + await stores.unknownFlagsStore.deleteAll(); +}); + +describe('should register unknown flags', () => { + test('/metrics endpoint', async () => { + // @ts-expect-error - cachedFeatureNames is a private property in ClientMetricsServiceV2 + services.clientMetricsServiceV2.cachedFeatureNames = vi + .fn<() => Promise>() + .mockResolvedValue(['existing_flag']); + + await request + .post('/api/client/metrics') + .send({ + appName: 'demo', + instanceId: '1', + bucket: { + start: Date.now(), + stop: Date.now(), + toggles: { + existing_flag: { + yes: 200, + no: 0, + }, + unknown_flag: { + yes: 100, + no: 50, + }, + }, + }, + }) + .expect(202); + + await services.unknownFlagsService.flush(); + const unknownFlags = await services.unknownFlagsService.getAll(); + + expect(unknownFlags).toHaveLength(1); + expect(unknownFlags[0]).toMatchObject({ + name: 'unknown_flag', + environment: 'development', + appName: 'demo', + seenAt: expect.any(Date), + }); + }); + + test('/metrics/bulk endpoint', async () => { + // @ts-expect-error - cachedFeatureNames is a private property in ClientMetricsServiceV2 + services.clientMetricsServiceV2.cachedFeatureNames = vi + .fn<() => Promise>() + .mockResolvedValue(['existing_flag']); + + const unknownFlag: BulkRegistrationSchema = { + appName: 'demo', + instanceId: '1', + environment: 'development', + sdkVersion: 'unleash-client-js:1.0.0', + sdkType: 'frontend', + }; + + await request + .post('/api/client/metrics/bulk') + .send({ + applications: [unknownFlag], + metrics: [ + { + featureName: 'existing_flag', + environment: 'development', + appName: 'demo', + timestamp: startOfHour(new Date()), + yes: 200, + no: 0, + variants: {}, + }, + { + featureName: 'unknown_flag', + environment: 'development', + appName: 'demo', + timestamp: startOfHour(new Date()), + yes: 100, + no: 50, + variants: {}, + }, + ], + }) + .expect(202); + + await services.unknownFlagsService.flush(); + const unknownFlags = await services.unknownFlagsService.getAll(); + + expect(unknownFlags).toHaveLength(1); + expect(unknownFlags[0]).toMatchObject({ + name: 'unknown_flag', + environment: 'development', + appName: 'demo', + seenAt: expect.any(Date), + }); + }); +}); From 51f8244a5dc6e5e96deafb341e4affdeaad64dae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20G=C3=B3is?= Date: Mon, 21 Jul 2025 15:40:02 +0100 Subject: [PATCH 07/14] chore: emit CLIENT_METRICS event after sifting (#10376) https://linear.app/unleash/issue/2-3705/only-emit-client-metrics-after-sifting-metrics Only emits the `CLIENT_METRICS` event after metric sifting. This ensures the event is emitted only for valid metrics tied to known flags, instead of all flags included in the metrics payload. See: https://github.com/Unleash/unleash/pull/10375#discussion_r2218974109 --- .../client-metrics/metrics-service-v2.test.ts | 2 +- .../client-metrics/metrics-service-v2.ts | 2 +- src/lib/features/metrics/instance/metrics.ts | 2 - .../unknown-flags/unknown-flags.e2e.test.ts | 40 +++++++++++++++---- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/lib/features/metrics/client-metrics/metrics-service-v2.test.ts b/src/lib/features/metrics/client-metrics/metrics-service-v2.test.ts index 5e74b2ae9f..29e4d2148a 100644 --- a/src/lib/features/metrics/client-metrics/metrics-service-v2.test.ts +++ b/src/lib/features/metrics/client-metrics/metrics-service-v2.test.ts @@ -113,7 +113,7 @@ test('process metrics properly even when some names are not url friendly, filter ); // only toggle with a bad name gets filtered out - expect(eventBus.emit).toHaveBeenCalledTimes(1); + expect(eventBus.emit).not.toHaveBeenCalled(); expect(lastSeenService.updateLastSeen).not.toHaveBeenCalled(); }); diff --git a/src/lib/features/metrics/client-metrics/metrics-service-v2.ts b/src/lib/features/metrics/client-metrics/metrics-service-v2.ts index dbb7fb3dd0..4b430f526f 100644 --- a/src/lib/features/metrics/client-metrics/metrics-service-v2.ts +++ b/src/lib/features/metrics/client-metrics/metrics-service-v2.ts @@ -251,6 +251,7 @@ export default class ClientMetricsServiceV2 { ...siftedMetrics, ]); this.lastSeenService.updateLastSeen(siftedMetrics); + this.config.eventBus.emit(CLIENT_METRICS, siftedMetrics); } async registerImpactMetrics(impactMetrics: Metric[]) { @@ -300,7 +301,6 @@ export default class ClientMetricsServiceV2 { if (clientMetrics.length) { await this.registerBulkMetrics(clientMetrics); - this.config.eventBus.emit(CLIENT_METRICS, clientMetrics); } } diff --git a/src/lib/features/metrics/instance/metrics.ts b/src/lib/features/metrics/instance/metrics.ts index 29507bbfd9..ca7c9c782a 100644 --- a/src/lib/features/metrics/instance/metrics.ts +++ b/src/lib/features/metrics/instance/metrics.ts @@ -23,7 +23,6 @@ import { customMetricsSchema, } from '../shared/schema.js'; import type { IClientMetricsEnv } from '../client-metrics/client-metrics-store-v2-type.js'; -import { CLIENT_METRICS } from '../../../events/index.js'; import type { CustomMetricsSchema } from '../../../openapi/spec/custom-metrics-schema.js'; import type { StoredCustomMetric } from '../custom/custom-metrics-store.js'; import type { CustomMetricsService } from '../custom/custom-metrics-service.js'; @@ -276,7 +275,6 @@ export default class ClientMetricsController extends Controller { promises.push( this.metricsV2.registerBulkMetrics(filteredData), ); - this.config.eventBus.emit(CLIENT_METRICS, data); } if ( diff --git a/src/lib/features/metrics/unknown-flags/unknown-flags.e2e.test.ts b/src/lib/features/metrics/unknown-flags/unknown-flags.e2e.test.ts index ee2c8d71ac..898356a3dd 100644 --- a/src/lib/features/metrics/unknown-flags/unknown-flags.e2e.test.ts +++ b/src/lib/features/metrics/unknown-flags/unknown-flags.e2e.test.ts @@ -16,9 +16,12 @@ import dbInit, { import { startOfHour } from 'date-fns'; import type TestAgent from 'supertest/lib/agent.d.ts'; import type { BulkRegistrationSchema } from '../../../openapi/index.js'; +import type { EventEmitter } from 'stream'; +import { CLIENT_METRICS } from '../../../events/index.js'; let db: ITestDb; let config: IUnleashConfig; +let eventBus: EventEmitter; async function getSetup(opts?: IUnleashOptions) { config = createTestConfig(opts); @@ -26,12 +29,16 @@ async function getSetup(opts?: IUnleashOptions) { const services = createServices(db.stores, config, db.rawDatabase); const app = await getApp(config, db.stores, services); + + config.eventBus.emit = vi.fn(); + return { request: supertest(app), stores: db.stores, services, db: db.rawDatabase, destroy: db.destroy, + eventBus: config.eventBus, }; } @@ -52,6 +59,7 @@ beforeAll(async () => { stores = setup.stores; destroy = setup.destroy; services = setup.services; + eventBus = setup.eventBus; }); afterAll(async () => { @@ -101,13 +109,22 @@ describe('should register unknown flags', () => { appName: 'demo', seenAt: expect.any(Date), }); + expect(eventBus.emit).toHaveBeenCalledWith( + CLIENT_METRICS, + expect.arrayContaining([ + expect.objectContaining({ + featureName: 'existing_flag', + yes: 200, + }), + ]), + ); }); test('/metrics/bulk endpoint', async () => { // @ts-expect-error - cachedFeatureNames is a private property in ClientMetricsServiceV2 services.clientMetricsServiceV2.cachedFeatureNames = vi .fn<() => Promise>() - .mockResolvedValue(['existing_flag']); + .mockResolvedValue(['existing_flag_bulk']); const unknownFlag: BulkRegistrationSchema = { appName: 'demo', @@ -123,21 +140,21 @@ describe('should register unknown flags', () => { applications: [unknownFlag], metrics: [ { - featureName: 'existing_flag', + featureName: 'existing_flag_bulk', environment: 'development', appName: 'demo', timestamp: startOfHour(new Date()), - yes: 200, + yes: 1337, no: 0, variants: {}, }, { - featureName: 'unknown_flag', + featureName: 'unknown_flag_bulk', environment: 'development', appName: 'demo', timestamp: startOfHour(new Date()), - yes: 100, - no: 50, + yes: 200, + no: 100, variants: {}, }, ], @@ -149,10 +166,19 @@ describe('should register unknown flags', () => { expect(unknownFlags).toHaveLength(1); expect(unknownFlags[0]).toMatchObject({ - name: 'unknown_flag', + name: 'unknown_flag_bulk', environment: 'development', appName: 'demo', seenAt: expect.any(Date), }); + expect(eventBus.emit).toHaveBeenCalledWith( + CLIENT_METRICS, + expect.arrayContaining([ + expect.objectContaining({ + featureName: 'existing_flag_bulk', + yes: 1337, + }), + ]), + ); }); }); From f54305c8b7517ff1fcc0eed93630d05fdffa4edc Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Tue, 22 Jul 2025 10:08:29 +0200 Subject: [PATCH 08/14] refactor: impact metrics modal - label filtering and UX (#10377) Modal for editing a chart now follows design of other parts of the app more closely. --- .../ChartConfigModal.tsx | 42 ++++-- .../ImpactMetricsControls.tsx | 81 +++++++++++ .../ModeSelector}/ModeSelector.tsx | 8 +- .../RangeSelector}/RangeSelector.tsx | 0 .../SeriesSelector}/SeriesSelector.tsx | 1 + .../LabelFilterItem/LabelFilterItem.tsx | 104 ++++++++++++++ .../LabelFilter/LabelsFilter.tsx | 102 +++++++++++++ .../impact-metrics/ImpactMetrics.tsx | 2 +- .../ImpactMetricsChartPreview.tsx | 50 ------- .../ImpactMetricsControls.tsx | 88 ------------ .../components/LabelsFilter.tsx | 136 ------------------ 11 files changed, 321 insertions(+), 293 deletions(-) rename frontend/src/component/impact-metrics/{ => ChartConfigModal}/ChartConfigModal.tsx (71%) create mode 100644 frontend/src/component/impact-metrics/ChartConfigModal/ImpactMetricsControls/ImpactMetricsControls.tsx rename frontend/src/component/impact-metrics/{ImpactMetricsControls/components => ChartConfigModal/ImpactMetricsControls/ModeSelector}/ModeSelector.tsx (88%) rename frontend/src/component/impact-metrics/{ImpactMetricsControls/components => ChartConfigModal/ImpactMetricsControls/RangeSelector}/RangeSelector.tsx (100%) rename frontend/src/component/impact-metrics/{ImpactMetricsControls/components => ChartConfigModal/ImpactMetricsControls/SeriesSelector}/SeriesSelector.tsx (98%) create mode 100644 frontend/src/component/impact-metrics/ChartConfigModal/LabelFilter/LabelFilterItem/LabelFilterItem.tsx create mode 100644 frontend/src/component/impact-metrics/ChartConfigModal/LabelFilter/LabelsFilter.tsx delete mode 100644 frontend/src/component/impact-metrics/ImpactMetricsChartPreview.tsx delete mode 100644 frontend/src/component/impact-metrics/ImpactMetricsControls/ImpactMetricsControls.tsx delete mode 100644 frontend/src/component/impact-metrics/ImpactMetricsControls/components/LabelsFilter.tsx diff --git a/frontend/src/component/impact-metrics/ChartConfigModal.tsx b/frontend/src/component/impact-metrics/ChartConfigModal/ChartConfigModal.tsx similarity index 71% rename from frontend/src/component/impact-metrics/ChartConfigModal.tsx rename to frontend/src/component/impact-metrics/ChartConfigModal/ChartConfigModal.tsx index 17a1db9fea..04624c60ff 100644 --- a/frontend/src/component/impact-metrics/ChartConfigModal.tsx +++ b/frontend/src/component/impact-metrics/ChartConfigModal/ChartConfigModal.tsx @@ -8,12 +8,16 @@ import { TextField, Box, styled, + useTheme, + useMediaQuery, + Divider, } from '@mui/material'; import { ImpactMetricsControls } from './ImpactMetricsControls/ImpactMetricsControls.tsx'; -import { ImpactMetricsChartPreview } from './ImpactMetricsChartPreview.tsx'; -import { useChartFormState } from './hooks/useChartFormState.ts'; -import type { ChartConfig } from './types.ts'; +import { useChartFormState } from '../hooks/useChartFormState.ts'; +import type { ChartConfig } from '../types.ts'; import type { ImpactMetricsSeries } from 'hooks/api/getters/useImpactMetricsMetadata/useImpactMetricsMetadata'; +import { LabelsFilter } from './LabelFilter/LabelsFilter.tsx'; +import { ImpactMetricsChart } from '../ImpactMetricsChart.tsx'; export const StyledConfigPanel = styled(Box)(({ theme }) => ({ display: 'flex', @@ -62,6 +66,8 @@ export const ChartConfigModal: FC = ({ open, initialConfig, }); + const theme = useTheme(); + const screenBreakpoint = useMediaQuery(theme.breakpoints.down('lg')); const handleSave = () => { if (!isValid) return; @@ -111,21 +117,33 @@ export const ChartConfigModal: FC = ({ actions={actions} metricSeries={metricSeries} loading={loading} - availableLabels={currentAvailableLabels} /> - + ({ padding: theme.spacing(1) })}> + + + + {currentAvailableLabels ? ( + + ) : null} - + + ({ margin: theme.spacing(2, 3, 3) })}>