mirror of
https://github.com/SigNoz/signoz.git
synced 2026-07-23 22:40:38 +01:00
Compare commits
4 Commits
nv/sql-das
...
nv/remove-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2da60505d | ||
|
|
488b7e2f5e | ||
|
|
0ab59be88e | ||
|
|
fbc61aa071 |
@@ -89,15 +89,6 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
Route: "",
|
||||
})
|
||||
|
||||
useDashboardV2 := ah.Signoz.Flagger.BooleanOrEmpty(ctx, flagger.FeatureUseDashboardV2, evalCtx)
|
||||
featureSet = append(featureSet, &licensetypes.Feature{
|
||||
Name: valuer.NewString(flagger.FeatureUseDashboardV2.String()),
|
||||
Active: useDashboardV2,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
})
|
||||
|
||||
aiObservability := ah.Signoz.Flagger.BooleanOrEmpty(ctx, flagger.FeatureEnableAIObservability, evalCtx)
|
||||
featureSet = append(featureSet, &licensetypes.Feature{
|
||||
Name: valuer.NewString(flagger.FeatureEnableAIObservability.String()),
|
||||
|
||||
@@ -10,7 +10,6 @@ export enum FeatureKeys {
|
||||
DOT_METRICS_ENABLED = 'dot_metrics_enabled',
|
||||
USE_JSON_BODY = 'use_json_body',
|
||||
USE_FINE_GRAINED_AUTHZ = 'use_fine_grained_authz',
|
||||
USE_DASHBOARD_V2 = 'use_dashboard_v2',
|
||||
USE_INFRA_MONITORING_V2 = 'use_infra_monitoring_v2',
|
||||
ENABLE_AI_OBSERVABILITY = 'enable_ai_observability',
|
||||
ENABLE_METRICS_REDUCTION = 'enable_metrics_reduction',
|
||||
|
||||
@@ -9,8 +9,6 @@ import {
|
||||
DashboardtypesListSortDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
|
||||
import { useIsDashboardV2 } from 'hooks/useIsDashboardV2';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import { ArrowRight, ArrowUpRight, Plus } from '@signozhq/icons';
|
||||
import Card from 'periscope/components/Card/Card';
|
||||
@@ -22,7 +20,7 @@ import dialsUrl from '@/assets/Icons/dials.svg';
|
||||
|
||||
import { getItemIcon } from '../constants';
|
||||
|
||||
// The five most-recent dashboards, normalised across the v1 and v2 list APIs.
|
||||
// The five most-recent dashboards from the V2 list API.
|
||||
interface RecentDashboard {
|
||||
id: string;
|
||||
title: string;
|
||||
@@ -38,52 +36,27 @@ export default function Dashboards({
|
||||
}): JSX.Element {
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
const { user } = useAppContext();
|
||||
const isDashboardV2 = useIsDashboardV2();
|
||||
|
||||
// Fetch the recent dashboards from whichever API the `use_dashboard_v2` flag
|
||||
// selects; the inactive one stays disabled so it never fires.
|
||||
const {
|
||||
data: v1List,
|
||||
isLoading: v1Loading,
|
||||
isError: v1Error,
|
||||
} = useGetAllDashboard({ enabled: !isDashboardV2 });
|
||||
|
||||
const {
|
||||
data: v2List,
|
||||
isLoading: v2Loading,
|
||||
isError: v2Error,
|
||||
} = useListDashboardsForUserV2(
|
||||
{
|
||||
sort: DashboardtypesListSortDTO.updated_at,
|
||||
order: DashboardtypesListOrderDTO.desc,
|
||||
limit: 5,
|
||||
offset: 0,
|
||||
},
|
||||
{ query: { enabled: isDashboardV2 } },
|
||||
);
|
||||
data: dashboardsList,
|
||||
isLoading: isDashboardListLoading,
|
||||
isError: isDashboardListError,
|
||||
} = useListDashboardsForUserV2({
|
||||
sort: DashboardtypesListSortDTO.updated_at,
|
||||
order: DashboardtypesListOrderDTO.desc,
|
||||
limit: 5,
|
||||
offset: 0,
|
||||
});
|
||||
|
||||
const isDashboardListLoading = isDashboardV2 ? v2Loading : v1Loading;
|
||||
const isDashboardListError = isDashboardV2 ? v2Error : v1Error;
|
||||
|
||||
const sortedDashboards = useMemo<RecentDashboard[]>(() => {
|
||||
if (isDashboardV2) {
|
||||
return (v2List?.data?.dashboards ?? []).map((d) => ({
|
||||
const sortedDashboards = useMemo<RecentDashboard[]>(
|
||||
() =>
|
||||
(dashboardsList?.data?.dashboards ?? []).map((d) => ({
|
||||
id: d.id,
|
||||
title: d.spec?.display?.name ?? d.name,
|
||||
tags: (d.tags ?? []).map((t) => (t.value ? `${t.key}:${t.value}` : t.key)),
|
||||
}));
|
||||
}
|
||||
return [...(v1List?.data ?? [])]
|
||||
.sort(
|
||||
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(),
|
||||
)
|
||||
.slice(0, 5)
|
||||
.map((d) => ({
|
||||
id: d.id,
|
||||
title: d.data.title,
|
||||
tags: d.data.tags ?? [],
|
||||
}));
|
||||
}, [isDashboardV2, v1List, v2List]);
|
||||
})),
|
||||
[dashboardsList],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (sortedDashboards.length > 0 && !loadingUserPreferences) {
|
||||
|
||||
@@ -2,15 +2,9 @@ import { ReactNode } from 'react';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import { createDashboardV2 } from 'api/generated/services/dashboard';
|
||||
import createDashboardV1 from 'api/v1/dashboards/create';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { useIsDashboardV2 } from 'hooks/useIsDashboardV2';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
|
||||
import { useCreateExportDashboard } from '../useCreateExportDashboard';
|
||||
|
||||
jest.mock('hooks/useIsDashboardV2');
|
||||
jest.mock('api/v1/dashboards/create');
|
||||
jest.mock('api/generated/services/dashboard', () => ({
|
||||
createDashboardV2: jest.fn(),
|
||||
}));
|
||||
@@ -20,10 +14,6 @@ jest.mock('providers/ErrorModalProvider', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
const mockUseIsDashboardV2 = useIsDashboardV2 as jest.MockedFunction<
|
||||
typeof useIsDashboardV2
|
||||
>;
|
||||
const mockCreateV1 = createDashboardV1 as jest.Mock;
|
||||
const mockCreateV2 = createDashboardV2 as jest.Mock;
|
||||
|
||||
function wrapper({ children }: { children: ReactNode }): JSX.Element {
|
||||
@@ -40,35 +30,7 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
describe('useCreateExportDashboard', () => {
|
||||
it('creates via the V1 endpoint and returns the created dashboard when the flag is off', async () => {
|
||||
mockUseIsDashboardV2.mockReturnValue(false);
|
||||
const v1Dashboard = {
|
||||
id: 'v1-new',
|
||||
data: { title: TITLE },
|
||||
} as unknown as Dashboard;
|
||||
mockCreateV1.mockResolvedValue({ httpStatusCode: 200, data: v1Dashboard });
|
||||
const onCreated = jest.fn();
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useCreateExportDashboard({ title: TITLE, onCreated }),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
act(() => result.current.create());
|
||||
|
||||
await waitFor(() =>
|
||||
expect(onCreated).toHaveBeenCalledWith({ id: 'v1-new', title: TITLE }),
|
||||
);
|
||||
expect(mockCreateV1).toHaveBeenCalledWith({
|
||||
title: TITLE,
|
||||
uploadedGrafana: false,
|
||||
version: ENTITY_VERSION_V5,
|
||||
});
|
||||
expect(mockCreateV2).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('creates via the V2 Perses endpoint and normalizes the response when the flag is on', async () => {
|
||||
mockUseIsDashboardV2.mockReturnValue(true);
|
||||
it('creates via the V2 Perses endpoint and normalizes the response', async () => {
|
||||
mockCreateV2.mockResolvedValue({ data: { id: 'v2-new' } });
|
||||
const onCreated = jest.fn();
|
||||
|
||||
@@ -88,6 +50,5 @@ describe('useCreateExportDashboard', () => {
|
||||
spec: expect.objectContaining({ display: { name: TITLE } }),
|
||||
}),
|
||||
);
|
||||
expect(mockCreateV1).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,44 +1,18 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useListDashboardsForUserV2 } from 'api/generated/services/dashboard';
|
||||
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
|
||||
import { useIsDashboardV2 } from 'hooks/useIsDashboardV2';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
|
||||
import { useExportDashboards } from '../useExportDashboards';
|
||||
|
||||
jest.mock('hooks/useIsDashboardV2');
|
||||
jest.mock('hooks/dashboard/useGetAllDashboard');
|
||||
jest.mock('api/generated/services/dashboard', () => ({
|
||||
useListDashboardsForUserV2: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockUseIsDashboardV2 = useIsDashboardV2 as jest.MockedFunction<
|
||||
typeof useIsDashboardV2
|
||||
>;
|
||||
const mockUseGetAllDashboard = useGetAllDashboard as jest.Mock;
|
||||
const mockUseListV2 = useListDashboardsForUserV2 as jest.Mock;
|
||||
|
||||
const v1Refetch = jest.fn();
|
||||
const v2Refetch = jest.fn();
|
||||
|
||||
const v1Dashboard = {
|
||||
id: 'v1-1',
|
||||
data: { title: 'V1 Dashboard' },
|
||||
} as unknown as Dashboard;
|
||||
|
||||
const v1Other = {
|
||||
id: 'v1-2',
|
||||
data: { title: 'Other board' },
|
||||
} as unknown as Dashboard;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUseGetAllDashboard.mockReturnValue({
|
||||
data: { data: [v1Dashboard, v1Other] },
|
||||
isLoading: false,
|
||||
isFetching: false,
|
||||
refetch: v1Refetch,
|
||||
});
|
||||
mockUseListV2.mockReturnValue({
|
||||
data: {
|
||||
data: {
|
||||
@@ -58,54 +32,21 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
describe('useExportDashboards', () => {
|
||||
it('returns the V1 list and disables the V2 query when the flag is off', () => {
|
||||
mockUseIsDashboardV2.mockReturnValue(false);
|
||||
|
||||
const { result } = renderHook(() => useExportDashboards());
|
||||
|
||||
expect(result.current.dashboards).toStrictEqual([
|
||||
{ id: 'v1-1', title: 'V1 Dashboard' },
|
||||
{ id: 'v1-2', title: 'Other board' },
|
||||
]);
|
||||
expect(mockUseGetAllDashboard).toHaveBeenCalledWith({ enabled: true });
|
||||
expect(mockUseListV2).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
query: { enabled: false, keepPreviousData: true },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('filters the V1 list in memory by title (case-insensitive)', () => {
|
||||
mockUseIsDashboardV2.mockReturnValue(false);
|
||||
|
||||
const { result } = renderHook(() => useExportDashboards('v1 dash'));
|
||||
|
||||
expect(result.current.dashboards).toStrictEqual([
|
||||
{ id: 'v1-1', title: 'V1 Dashboard' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns the V2 list normalized to the export shape when the flag is on', () => {
|
||||
mockUseIsDashboardV2.mockReturnValue(true);
|
||||
|
||||
it('returns the V2 list normalized to the export shape', () => {
|
||||
const { result } = renderHook(() => useExportDashboards());
|
||||
|
||||
expect(result.current.dashboards).toStrictEqual([
|
||||
{ id: 'v2-1', title: 'V2 Dashboard' },
|
||||
]);
|
||||
expect(mockUseGetAllDashboard).toHaveBeenCalledWith({ enabled: false });
|
||||
expect(mockUseListV2).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ query: undefined }),
|
||||
expect.objectContaining({
|
||||
query: { enabled: true, keepPreviousData: true },
|
||||
query: { keepPreviousData: true },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('passes the search term as a name-contains filter clause to the V2 query param', () => {
|
||||
mockUseIsDashboardV2.mockReturnValue(true);
|
||||
|
||||
renderHook(() => useExportDashboards('payments'));
|
||||
|
||||
expect(mockUseListV2).toHaveBeenCalledWith(
|
||||
@@ -114,12 +55,10 @@ describe('useExportDashboards', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('refetches the active source', () => {
|
||||
mockUseIsDashboardV2.mockReturnValue(true);
|
||||
it('refetches the V2 source', () => {
|
||||
const { result } = renderHook(() => useExportDashboards());
|
||||
|
||||
result.current.refetch();
|
||||
expect(v2Refetch).toHaveBeenCalledTimes(1);
|
||||
expect(v1Refetch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useIsDashboardV2 } from 'hooks/useIsDashboardV2';
|
||||
import type { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { useGetExportToDashboardLink } from '../useGetExportToDashboardLink';
|
||||
|
||||
jest.mock('hooks/useIsDashboardV2');
|
||||
const mockUseIsDashboardV2 = useIsDashboardV2 as jest.MockedFunction<
|
||||
typeof useIsDashboardV2
|
||||
>;
|
||||
|
||||
const query = { id: 'q1', queryType: 'builder' } as unknown as Query;
|
||||
const params = {
|
||||
dashboardId: 'dash-1',
|
||||
@@ -19,21 +13,7 @@ const params = {
|
||||
};
|
||||
|
||||
describe('useGetExportToDashboardLink', () => {
|
||||
it('builds a V1 new-widget link when the dashboard-v2 flag is off', () => {
|
||||
mockUseIsDashboardV2.mockReturnValue(false);
|
||||
|
||||
const { result } = renderHook(() => useGetExportToDashboardLink());
|
||||
const link = result.current(params);
|
||||
|
||||
expect(link?.startsWith('/dashboard/dash-1/new?')).toBe(true);
|
||||
expect(link).toContain('graphType=');
|
||||
expect(link).toContain('widgetId=w1');
|
||||
expect(link).toContain('compositeQuery=');
|
||||
});
|
||||
|
||||
it('builds a V2 panel/new link (ignoring widgetId) when the flag is on', () => {
|
||||
mockUseIsDashboardV2.mockReturnValue(true);
|
||||
|
||||
it('builds a V2 panel/new link (ignoring widgetId)', () => {
|
||||
const { result } = renderHook(() => useGetExportToDashboardLink());
|
||||
const link = result.current(params);
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useMutation } from 'react-query';
|
||||
import { createDashboardV2 } from 'api/generated/services/dashboard';
|
||||
import createDashboardV1 from 'api/v1/dashboards/create';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { useIsDashboardV2 } from 'hooks/useIsDashboardV2';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
@@ -20,14 +17,13 @@ interface UseCreateExportDashboardResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag-aware "create a new dashboard to export into". V2 uses the Perses-spec
|
||||
* `createDashboardV2`; V1 uses the legacy create. Both normalize to an `ExportDashboard`.
|
||||
* "Create a new dashboard to export into". Uses the Perses-spec `createDashboardV2`
|
||||
* and normalizes to an `ExportDashboard`.
|
||||
*/
|
||||
export function useCreateExportDashboard({
|
||||
title,
|
||||
onCreated,
|
||||
}: UseCreateExportDashboardParams): UseCreateExportDashboardResult {
|
||||
const isDashboardV2 = useIsDashboardV2();
|
||||
const { showErrorModal } = useErrorModal();
|
||||
|
||||
const onError = useCallback(
|
||||
@@ -35,16 +31,7 @@ export function useCreateExportDashboard({
|
||||
[showErrorModal],
|
||||
);
|
||||
|
||||
const v1 = useMutation(createDashboardV1, {
|
||||
onSuccess: (data) => {
|
||||
if (data.data) {
|
||||
onCreated({ id: data.data.id, title: data.data.data.title ?? '' });
|
||||
}
|
||||
},
|
||||
onError,
|
||||
});
|
||||
|
||||
const v2 = useMutation(
|
||||
const createDashboardMutation = useMutation(
|
||||
() =>
|
||||
createDashboardV2({
|
||||
schemaVersion: 'v6',
|
||||
@@ -66,15 +53,11 @@ export function useCreateExportDashboard({
|
||||
);
|
||||
|
||||
const create = useCallback((): void => {
|
||||
if (isDashboardV2) {
|
||||
v2.mutate();
|
||||
} else {
|
||||
v1.mutate({ title, uploadedGrafana: false, version: ENTITY_VERSION_V5 });
|
||||
}
|
||||
}, [isDashboardV2, v1, v2, title]);
|
||||
createDashboardMutation.mutate();
|
||||
}, [createDashboardMutation]);
|
||||
|
||||
return {
|
||||
create,
|
||||
isLoading: isDashboardV2 ? v2.isLoading : v1.isLoading,
|
||||
isLoading: createDashboardMutation.isLoading,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useListDashboardsForUserV2 } from 'api/generated/services/dashboard';
|
||||
import { DashboardtypesListedDashboardForUserV2DTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
|
||||
import useDebounce from 'hooks/useDebounce';
|
||||
import { useIsDashboardV2 } from 'hooks/useIsDashboardV2';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
|
||||
const V2_LIST_LIMIT = 1000;
|
||||
const SEARCH_DEBOUNCE_MS = 300;
|
||||
|
||||
/** Neutral id+title the picker uses in place of the V1/V2 dashboard entity. */
|
||||
/** Neutral id+title the picker uses in place of the dashboard entity. */
|
||||
export interface ExportDashboard {
|
||||
id: string;
|
||||
title: string;
|
||||
@@ -24,29 +21,12 @@ export interface UseExportDashboardsResult {
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
function fromV2(
|
||||
function toExportDashboard(
|
||||
item: DashboardtypesListedDashboardForUserV2DTO,
|
||||
): ExportDashboard {
|
||||
return { id: item.id, title: item.spec.display?.name || item.name };
|
||||
}
|
||||
|
||||
function fromV1(dashboard: Dashboard): ExportDashboard {
|
||||
return { id: dashboard.id, title: dashboard.data.title ?? '' };
|
||||
}
|
||||
|
||||
function filterByTitle(
|
||||
dashboards: ExportDashboard[],
|
||||
search: string,
|
||||
): ExportDashboard[] {
|
||||
const term = search.trim().toLowerCase();
|
||||
if (!term) {
|
||||
return dashboards;
|
||||
}
|
||||
return dashboards.filter((dashboard) =>
|
||||
dashboard.title.toLowerCase().includes(term),
|
||||
);
|
||||
}
|
||||
|
||||
// The V2 list `query` is a filter DSL (`key OP value`), not free text — wrap a typed term
|
||||
// as a name-contains clause (single quotes escaped).
|
||||
function toNameQuery(search: string): string | undefined {
|
||||
@@ -54,37 +34,28 @@ function toNameQuery(search: string): string | undefined {
|
||||
return term ? `name CONTAINS '${term.replace(/'/g, "\\'")}'` : undefined;
|
||||
}
|
||||
|
||||
/** Flag-aware picker source: V2 searches server-side (debounced), V1 filters in memory. */
|
||||
/** Picker source: searches the V2 dashboard list server-side (debounced). */
|
||||
export function useExportDashboards(search = ''): UseExportDashboardsResult {
|
||||
const isDashboardV2 = useIsDashboardV2();
|
||||
const debouncedSearch = useDebounce(search, SEARCH_DEBOUNCE_MS);
|
||||
|
||||
const v1 = useGetAllDashboard({ enabled: !isDashboardV2 });
|
||||
const v2 = useListDashboardsForUserV2(
|
||||
const listQuery = useListDashboardsForUserV2(
|
||||
{ limit: V2_LIST_LIMIT, query: toNameQuery(debouncedSearch) },
|
||||
{ query: { enabled: isDashboardV2, keepPreviousData: true } },
|
||||
{ query: { keepPreviousData: true } },
|
||||
);
|
||||
|
||||
const dashboards = useMemo<ExportDashboard[]>(
|
||||
() =>
|
||||
isDashboardV2
|
||||
? (v2.data?.data?.dashboards ?? []).map(fromV2)
|
||||
: filterByTitle((v1.data?.data ?? []).map(fromV1), search),
|
||||
[isDashboardV2, v1.data, v2.data, search],
|
||||
() => (listQuery.data?.data?.dashboards ?? []).map(toExportDashboard),
|
||||
[listQuery.data],
|
||||
);
|
||||
|
||||
const refetch = useCallback((): void => {
|
||||
if (isDashboardV2) {
|
||||
void v2.refetch();
|
||||
} else {
|
||||
void v1.refetch();
|
||||
}
|
||||
}, [isDashboardV2, v1, v2]);
|
||||
void listQuery.refetch();
|
||||
}, [listQuery]);
|
||||
|
||||
return {
|
||||
dashboards,
|
||||
isLoading: isDashboardV2 ? v2.isLoading : v1.isLoading,
|
||||
isFetching: isDashboardV2 ? v2.isFetching : v1.isFetching,
|
||||
isLoading: listQuery.isLoading,
|
||||
isFetching: listQuery.isFetching,
|
||||
refetch,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { useCallback } from 'react';
|
||||
import type { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useIsDashboardV2 } from 'hooks/useIsDashboardV2';
|
||||
import { buildExportPanelLink } from 'pages/DashboardPageV2/DashboardContainer/PanelEditor/newPanelRoute';
|
||||
import type { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { generateExportToDashboardLink } from 'utils/dashboard/generateExportToDashboardLink';
|
||||
|
||||
interface ExportToDashboardLinkParams {
|
||||
dashboardId: string;
|
||||
@@ -13,25 +11,15 @@ interface ExportToDashboardLinkParams {
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag-aware "Add to Dashboard" link builder for the explorers. V2 targets the panel
|
||||
* editor; V1 uses the legacy new-widget link. `null` (V2 only) when the panel type has no
|
||||
* V2 kind, so callers skip navigation.
|
||||
* "Add to Dashboard" link builder for the explorers. Targets the V2 panel editor;
|
||||
* `null` when the panel type has no V2 kind, so callers skip navigation.
|
||||
*/
|
||||
export function useGetExportToDashboardLink(): (
|
||||
params: ExportToDashboardLinkParams,
|
||||
) => string | null {
|
||||
const isDashboardV2 = useIsDashboardV2();
|
||||
|
||||
return useCallback(
|
||||
({ dashboardId, panelType, query, widgetId }: ExportToDashboardLinkParams) =>
|
||||
isDashboardV2
|
||||
? buildExportPanelLink({ dashboardId, panelType, query })
|
||||
: generateExportToDashboardLink({
|
||||
query,
|
||||
panelType,
|
||||
dashboardId,
|
||||
widgetId,
|
||||
}),
|
||||
[isDashboardV2],
|
||||
({ dashboardId, panelType, query }: ExportToDashboardLinkParams) =>
|
||||
buildExportPanelLink({ dashboardId, panelType, query }),
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
|
||||
export function useIsDashboardV2(): boolean {
|
||||
const { featureFlags } = useAppContext();
|
||||
return Boolean(
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.USE_DASHBOARD_V2)
|
||||
?.active,
|
||||
);
|
||||
}
|
||||
@@ -1,15 +1,7 @@
|
||||
import { useIsDashboardV2 } from 'hooks/useIsDashboardV2';
|
||||
import DashboardPageV2 from 'pages/DashboardPageV2';
|
||||
|
||||
import DashboardPage from './DashboardPage';
|
||||
|
||||
// Serves the V2 dashboard detail page when the `use_dashboard_v2` flag is active;
|
||||
// otherwise the existing V1 page. Lets V2 dark-ship behind the flag without
|
||||
// changing route definitions.
|
||||
function DashboardPageEntry(): JSX.Element {
|
||||
const isDashboardV2 = useIsDashboardV2();
|
||||
|
||||
return isDashboardV2 ? <DashboardPageV2 /> : <DashboardPage />;
|
||||
return <DashboardPageV2 />;
|
||||
}
|
||||
|
||||
export default DashboardPageEntry;
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
import { useIsDashboardV2 } from 'hooks/useIsDashboardV2';
|
||||
import DashboardsListPageV2 from 'pages/DashboardsListPageV2';
|
||||
|
||||
import DashboardsListPage from './DashboardsListPage';
|
||||
|
||||
// Serves the V2 dashboards list when the `use_dashboard_v2` flag is active;
|
||||
// otherwise the existing V1 list. Lets V2 dark-ship behind the flag without
|
||||
// changing route definitions.
|
||||
function DashboardsListPageEntry(): JSX.Element {
|
||||
const isDashboardV2 = useIsDashboardV2();
|
||||
|
||||
return isDashboardV2 ? <DashboardsListPageV2 /> : <DashboardsListPage />;
|
||||
return <DashboardsListPageV2 />;
|
||||
}
|
||||
|
||||
export default DashboardsListPageEntry;
|
||||
|
||||
@@ -11,7 +11,6 @@ var (
|
||||
FeatureUseMeterReporter = featuretypes.MustNewName("use_meter_reporter")
|
||||
FeatureUseJSONBody = featuretypes.MustNewName("use_json_body")
|
||||
FeatureUseFineGrainedAuthz = featuretypes.MustNewName("use_fine_grained_authz")
|
||||
FeatureUseDashboardV2 = featuretypes.MustNewName("use_dashboard_v2")
|
||||
FeatureEnableAIObservability = featuretypes.MustNewName("enable_ai_observability")
|
||||
FeatureEnableMetricsReduction = featuretypes.MustNewName("enable_metrics_reduction")
|
||||
FeatureUseInfraMonitoringV2 = featuretypes.MustNewName("use_infra_monitoring_v2")
|
||||
@@ -83,14 +82,6 @@ func MustNewRegistry() featuretypes.Registry {
|
||||
DefaultVariant: featuretypes.MustNewName("disabled"),
|
||||
Variants: featuretypes.NewBooleanVariants(),
|
||||
},
|
||||
&featuretypes.Feature{
|
||||
Name: FeatureUseDashboardV2,
|
||||
Kind: featuretypes.KindBoolean,
|
||||
Stage: featuretypes.StageExperimental,
|
||||
Description: "Controls whether dashboard v2 is enabled",
|
||||
DefaultVariant: featuretypes.MustNewName("disabled"),
|
||||
Variants: featuretypes.NewBooleanVariants(),
|
||||
},
|
||||
&featuretypes.Feature{
|
||||
Name: FeatureEnableAIObservability,
|
||||
Kind: featuretypes.KindBoolean,
|
||||
|
||||
@@ -1669,15 +1669,6 @@ func (aH *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
Route: "",
|
||||
})
|
||||
|
||||
useDashboardV2 := aH.Signoz.Flagger.BooleanOrEmpty(r.Context(), flagger.FeatureUseDashboardV2, evalCtx)
|
||||
featureSet = append(featureSet, &licensetypes.Feature{
|
||||
Name: valuer.NewString(flagger.FeatureUseDashboardV2.String()),
|
||||
Active: useDashboardV2,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
})
|
||||
|
||||
aiObservability := aH.Signoz.Flagger.BooleanOrEmpty(r.Context(), flagger.FeatureEnableAIObservability, evalCtx)
|
||||
featureSet = append(featureSet, &licensetypes.Feature{
|
||||
Name: valuer.NewString(flagger.FeatureEnableAIObservability.String()),
|
||||
|
||||
Reference in New Issue
Block a user