mirror of
https://github.com/SigNoz/signoz.git
synced 2026-03-11 07:52:04 +00:00
Compare commits
8 Commits
message-ty
...
nv/3927-hi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9338ee0a97 | ||
|
|
492cc44881 | ||
|
|
0f9f3e70c6 | ||
|
|
a139915f4e | ||
|
|
b69bcd63ba | ||
|
|
f576a86dd1 | ||
|
|
996c9a891f | ||
|
|
d1a872dadc |
@@ -1,4 +1,22 @@
|
||||
services:
|
||||
init-clickhouse:
|
||||
image: clickhouse/clickhouse-server:25.5.6
|
||||
container_name: init-clickhouse
|
||||
command:
|
||||
- bash
|
||||
- -c
|
||||
- |
|
||||
version="v0.0.1"
|
||||
node_os=$$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
node_arch=$$(uname -m | sed s/aarch64/arm64/ | sed s/x86_64/amd64/)
|
||||
echo "Fetching histogram-binary for $${node_os}/$${node_arch}"
|
||||
cd /tmp
|
||||
wget -O histogram-quantile.tar.gz "https://github.com/SigNoz/signoz/releases/download/histogram-quantile%2F$${version}/histogram-quantile_$${node_os}_$${node_arch}.tar.gz"
|
||||
tar -xvzf histogram-quantile.tar.gz
|
||||
mv histogram-quantile /var/lib/clickhouse/user_scripts/histogramQuantile
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- ${PWD}/fs/tmp/var/lib/clickhouse/user_scripts/:/var/lib/clickhouse/user_scripts/
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server:25.5.6
|
||||
container_name: clickhouse
|
||||
@@ -7,6 +25,7 @@ services:
|
||||
- ${PWD}/fs/etc/clickhouse-server/users.d/users.xml:/etc/clickhouse-server/users.d/users.xml
|
||||
- ${PWD}/fs/tmp/var/lib/clickhouse/:/var/lib/clickhouse/
|
||||
- ${PWD}/fs/tmp/var/lib/clickhouse/user_scripts/:/var/lib/clickhouse/user_scripts/
|
||||
- ${PWD}/../../../deploy/common/clickhouse/custom-function.xml:/etc/clickhouse-server/custom-function.xml
|
||||
ports:
|
||||
- '127.0.0.1:8123:8123'
|
||||
- '127.0.0.1:9000:9000'
|
||||
@@ -22,7 +41,10 @@ services:
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
depends_on:
|
||||
- zookeeper
|
||||
init-clickhouse:
|
||||
condition: service_completed_successfully
|
||||
zookeeper:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- CLICKHOUSE_SKIP_USER_SETUP=1
|
||||
zookeeper:
|
||||
|
||||
@@ -44,4 +44,5 @@
|
||||
<shard>01</shard>
|
||||
<replica>01</replica>
|
||||
</macros>
|
||||
<user_defined_executable_functions_config>*function.xml</user_defined_executable_functions_config>
|
||||
</clickhouse>
|
||||
@@ -193,7 +193,6 @@ describe('Dashboard landing page actions header tests', () => {
|
||||
handleDashboardLockToggle: jest.fn(),
|
||||
dashboardResponse: {} as IDashboardContext['dashboardResponse'],
|
||||
selectedDashboard: (getDashboardById.data as unknown) as Dashboard,
|
||||
dashboardId: '4',
|
||||
layouts: [],
|
||||
panelMap: {},
|
||||
setPanelMap: jest.fn(),
|
||||
@@ -205,8 +204,6 @@ describe('Dashboard landing page actions header tests', () => {
|
||||
updateLocalStorageDashboardVariables: jest.fn(),
|
||||
dashboardQueryRangeCalled: false,
|
||||
setDashboardQueryRangeCalled: jest.fn(),
|
||||
selectedRowWidgetId: null,
|
||||
setSelectedRowWidgetId: jest.fn(),
|
||||
isDashboardFetching: false,
|
||||
columnWidths: {},
|
||||
setColumnWidths: jest.fn(),
|
||||
|
||||
@@ -78,7 +78,6 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
isDashboardLocked,
|
||||
setSelectedDashboard,
|
||||
handleToggleDashboardSlider,
|
||||
setSelectedRowWidgetId,
|
||||
handleDashboardLockToggle,
|
||||
} = useDashboard();
|
||||
|
||||
@@ -146,7 +145,6 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
const [addPanelPermission] = useComponentPermission(permissions, userRole);
|
||||
|
||||
const onEmptyWidgetHandler = useCallback(() => {
|
||||
setSelectedRowWidgetId(null);
|
||||
handleToggleDashboardSlider(true);
|
||||
logEvent('Dashboard Detail: Add new panel clicked', {
|
||||
dashboardId: selectedDashboard?.id,
|
||||
|
||||
@@ -67,17 +67,18 @@ export const useDashboardVariableUpdate = (): UseDashboardVariableUpdateReturn =
|
||||
const oldVariables = prev?.data.variables;
|
||||
// this is added to handle case where we have two different
|
||||
// schemas for variable response
|
||||
if (oldVariables?.[id]) {
|
||||
oldVariables[id] = {
|
||||
...oldVariables[id],
|
||||
const updatedVariables = { ...oldVariables };
|
||||
if (updatedVariables?.[id]) {
|
||||
updatedVariables[id] = {
|
||||
...updatedVariables[id],
|
||||
selectedValue: value,
|
||||
allSelected,
|
||||
haveCustomValuesSelected,
|
||||
};
|
||||
}
|
||||
if (oldVariables?.[name]) {
|
||||
oldVariables[name] = {
|
||||
...oldVariables[name],
|
||||
if (updatedVariables?.[name]) {
|
||||
updatedVariables[name] = {
|
||||
...updatedVariables[name],
|
||||
selectedValue: value,
|
||||
allSelected,
|
||||
haveCustomValuesSelected,
|
||||
@@ -87,9 +88,7 @@ export const useDashboardVariableUpdate = (): UseDashboardVariableUpdateReturn =
|
||||
...prev,
|
||||
data: {
|
||||
...prev?.data,
|
||||
variables: {
|
||||
...oldVariables,
|
||||
},
|
||||
variables: updatedVariables,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { LegendPosition } from 'lib/uPlotV2/components/types';
|
||||
import ContextMenu from 'periscope/components/ContextMenu';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import uPlot from 'uplot';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
|
||||
@@ -62,7 +61,7 @@ function BarPanel(props: PanelWrapperProps): JSX.Element {
|
||||
currentQuery: widget.query,
|
||||
onClick: clickHandlerWithContextMenu,
|
||||
onDragSelect,
|
||||
apiResponse: queryResponse?.data?.payload as MetricRangePayloadProps,
|
||||
apiResponse: queryResponse?.data?.payload,
|
||||
timezone,
|
||||
panelMode,
|
||||
minTimeScale: minTimeScale,
|
||||
|
||||
@@ -11,7 +11,6 @@ import { get } from 'lodash-es';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { QueryData } from 'types/api/widgets/getQuery';
|
||||
import { AlignedData } from 'uplot';
|
||||
|
||||
import { PanelMode } from '../types';
|
||||
@@ -44,7 +43,7 @@ export function prepareBarPanelConfig({
|
||||
currentQuery: Query;
|
||||
onClick: OnClickPluginOpts['onClick'];
|
||||
onDragSelect: (startTime: number, endTime: number) => void;
|
||||
apiResponse: MetricRangePayloadProps;
|
||||
apiResponse?: MetricRangePayloadProps;
|
||||
timezone: Timezone;
|
||||
panelMode: PanelMode;
|
||||
minTimeScale?: number;
|
||||
@@ -76,13 +75,17 @@ export function prepareBarPanelConfig({
|
||||
stepInterval: minStepInterval,
|
||||
});
|
||||
|
||||
if (!(apiResponse && apiResponse?.data?.result)) {
|
||||
// if no data, return the builder without adding any series
|
||||
return builder;
|
||||
}
|
||||
|
||||
if (widget.stackedBarChart) {
|
||||
const seriesCount = (apiResponse?.data?.result?.length ?? 0) + 1; // +1 for 1-based uPlot series indices
|
||||
const seriesCount = (apiResponse.data.result.length ?? 0) + 1; // +1 for 1-based uPlot series indices
|
||||
builder.setBands(getInitialStackedBands(seriesCount));
|
||||
}
|
||||
|
||||
const seriesList: QueryData[] = apiResponse?.data?.result || [];
|
||||
seriesList.forEach((series) => {
|
||||
apiResponse.data.result.forEach((series) => {
|
||||
const baseLabelName = getLabelName(
|
||||
series.metric,
|
||||
series.queryName || '', // query
|
||||
|
||||
@@ -6,7 +6,6 @@ import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { LegendPosition } from 'lib/uPlotV2/components/types';
|
||||
import { DashboardCursorSync } from 'lib/uPlotV2/plugins/TooltipPlugin/types';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import Histogram from '../../charts/Histogram/Histogram';
|
||||
@@ -39,7 +38,7 @@ function HistogramPanel(props: PanelWrapperProps): JSX.Element {
|
||||
return prepareHistogramPanelConfig({
|
||||
widget,
|
||||
isDarkMode,
|
||||
apiResponse: queryResponse?.data?.payload as MetricRangePayloadProps,
|
||||
apiResponse: queryResponse?.data?.payload,
|
||||
panelMode,
|
||||
});
|
||||
}, [widget, isDarkMode, queryResponse?.data?.payload, panelMode]);
|
||||
@@ -49,7 +48,7 @@ function HistogramPanel(props: PanelWrapperProps): JSX.Element {
|
||||
return [];
|
||||
}
|
||||
return prepareHistogramPanelData({
|
||||
apiResponse: queryResponse?.data?.payload as MetricRangePayloadProps,
|
||||
apiResponse: queryResponse?.data?.payload,
|
||||
bucketWidth: widget?.bucketWidth,
|
||||
bucketCount: widget?.bucketCount,
|
||||
mergeAllActiveQueries: widget?.mergeAllActiveQueries,
|
||||
|
||||
@@ -149,7 +149,7 @@ export function prepareHistogramPanelConfig({
|
||||
isDarkMode,
|
||||
}: {
|
||||
widget: Widgets;
|
||||
apiResponse: MetricRangePayloadProps;
|
||||
apiResponse?: MetricRangePayloadProps;
|
||||
panelMode: PanelMode;
|
||||
isDarkMode: boolean;
|
||||
}): UPlotConfigBuilder {
|
||||
@@ -204,7 +204,7 @@ export function prepareHistogramPanelConfig({
|
||||
fillColor: '#4E74F8',
|
||||
isDarkMode,
|
||||
});
|
||||
} else {
|
||||
} else if (apiResponse && apiResponse?.data?.result) {
|
||||
apiResponse.data.result.forEach((series) => {
|
||||
const baseLabelName = getLabelName(
|
||||
series.metric,
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { LegendPosition } from 'lib/uPlotV2/components/types';
|
||||
import { ContextMenu } from 'periscope/components/ContextMenu';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import uPlot from 'uplot';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
|
||||
@@ -68,7 +67,7 @@ function TimeSeriesPanel(props: PanelWrapperProps): JSX.Element {
|
||||
currentQuery: widget.query,
|
||||
onClick: clickHandlerWithContextMenu,
|
||||
onDragSelect,
|
||||
apiResponse: queryResponse?.data?.payload as MetricRangePayloadProps,
|
||||
apiResponse: queryResponse?.data?.payload,
|
||||
timezone,
|
||||
panelMode,
|
||||
minTimeScale: minTimeScale,
|
||||
|
||||
@@ -68,11 +68,12 @@ export const prepareUPlotConfig = ({
|
||||
currentQuery: Query;
|
||||
onClick?: OnClickPluginOpts['onClick'];
|
||||
onDragSelect: (startTime: number, endTime: number) => void;
|
||||
apiResponse: MetricRangePayloadProps;
|
||||
apiResponse?: MetricRangePayloadProps;
|
||||
timezone: Timezone;
|
||||
panelMode: PanelMode;
|
||||
minTimeScale?: number;
|
||||
maxTimeScale?: number;
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
}): UPlotConfigBuilder => {
|
||||
const stepIntervals: ExecStats['stepIntervals'] = get(
|
||||
apiResponse,
|
||||
@@ -100,7 +101,12 @@ export const prepareUPlotConfig = ({
|
||||
stepInterval: minStepInterval,
|
||||
});
|
||||
|
||||
apiResponse.data?.result?.forEach((series) => {
|
||||
if (!(apiResponse && apiResponse.data.result)) {
|
||||
// if no data, return the builder without adding any series
|
||||
return builder;
|
||||
}
|
||||
|
||||
apiResponse.data.result.forEach((series) => {
|
||||
const hasSingleValidPoint = hasSingleVisiblePointForSeries(series);
|
||||
const baseLabelName = getLabelName(
|
||||
series.metric,
|
||||
|
||||
@@ -18,7 +18,7 @@ import { PanelMode } from '../types';
|
||||
export interface BaseConfigBuilderProps {
|
||||
id: string;
|
||||
thresholds?: ThresholdProps[];
|
||||
apiResponse: MetricRangePayloadProps;
|
||||
apiResponse?: MetricRangePayloadProps;
|
||||
isDarkMode: boolean;
|
||||
onClick?: OnClickPluginOpts['onClick'];
|
||||
onDragSelect?: (startTime: number, endTime: number) => void;
|
||||
|
||||
@@ -19,7 +19,6 @@ export default function DashboardEmptyState(): JSX.Element {
|
||||
selectedDashboard,
|
||||
isDashboardLocked,
|
||||
handleToggleDashboardSlider,
|
||||
setSelectedRowWidgetId,
|
||||
} = useDashboard();
|
||||
|
||||
const variablesSettingsTabHandle = useRef<VariablesSettingsTab>(null);
|
||||
@@ -42,7 +41,6 @@ export default function DashboardEmptyState(): JSX.Element {
|
||||
const [addPanelPermission] = useComponentPermission(permissions, userRole);
|
||||
|
||||
const onEmptyWidgetHandler = useCallback(() => {
|
||||
setSelectedRowWidgetId(null);
|
||||
handleToggleDashboardSlider(true);
|
||||
logEvent('Dashboard Detail: Add new panel clicked', {
|
||||
dashboardId: selectedDashboard?.id,
|
||||
|
||||
@@ -71,7 +71,6 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
||||
isDashboardLocked,
|
||||
dashboardQueryRangeCalled,
|
||||
setDashboardQueryRangeCalled,
|
||||
setSelectedRowWidgetId,
|
||||
isDashboardFetching,
|
||||
columnWidths,
|
||||
} = useDashboard();
|
||||
@@ -195,7 +194,6 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
||||
|
||||
updateDashboardMutation.mutate(updatedDashboard, {
|
||||
onSuccess: (updatedDashboard) => {
|
||||
setSelectedRowWidgetId(null);
|
||||
if (updatedDashboard.data) {
|
||||
if (updatedDashboard.data.data.layout) {
|
||||
setLayouts(sortLayout(updatedDashboard.data.data.layout));
|
||||
|
||||
@@ -5,6 +5,7 @@ import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import { EllipsisIcon, PenLine, Plus, X } from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { setSelectedRowWidgetId } from 'providers/Dashboard/helpers/selectedRowWidgetIdHelper';
|
||||
import { ROLES, USER_ROLES } from 'types/roles';
|
||||
import { ComponentTypes } from 'utils/permission';
|
||||
|
||||
@@ -37,7 +38,6 @@ export function WidgetRowHeader(props: WidgetRowHeaderProps): JSX.Element {
|
||||
handleToggleDashboardSlider,
|
||||
selectedDashboard,
|
||||
isDashboardLocked,
|
||||
setSelectedRowWidgetId,
|
||||
} = useDashboard();
|
||||
|
||||
const permissions: ComponentTypes[] = ['add_panel'];
|
||||
@@ -81,7 +81,12 @@ export function WidgetRowHeader(props: WidgetRowHeaderProps): JSX.Element {
|
||||
disabled={!editWidget && addPanelPermission && !isDashboardLocked}
|
||||
icon={<Plus size={14} />}
|
||||
onClick={(): void => {
|
||||
setSelectedRowWidgetId(id);
|
||||
// TODO: @AshwinBhatkal Simplify this check in cleanup of https://github.com/SigNoz/engineering-pod/issues/3953
|
||||
if (!selectedDashboard?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedRowWidgetId(selectedDashboard.id, id);
|
||||
handleToggleDashboardSlider(true);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -34,6 +34,10 @@ import { cloneDeep, defaultTo, isEmpty, isUndefined } from 'lodash-es';
|
||||
import { Check, X } from 'lucide-react';
|
||||
import { DashboardWidgetPageParams } from 'pages/DashboardWidget';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import {
|
||||
clearSelectedRowWidgetId,
|
||||
getSelectedRowWidgetId,
|
||||
} from 'providers/Dashboard/helpers/selectedRowWidgetIdHelper';
|
||||
import {
|
||||
getNextWidgets,
|
||||
getPreviousWidgets,
|
||||
@@ -86,8 +90,6 @@ function NewWidget({
|
||||
selectedDashboard,
|
||||
setSelectedDashboard,
|
||||
setToScrollWidgetId,
|
||||
selectedRowWidgetId,
|
||||
setSelectedRowWidgetId,
|
||||
columnWidths,
|
||||
} = useDashboard();
|
||||
|
||||
@@ -450,6 +452,8 @@ function NewWidget({
|
||||
const widgetId = query.get('widgetId') || '';
|
||||
let updatedLayout = selectedDashboard.data.layout || [];
|
||||
|
||||
const selectedRowWidgetId = getSelectedRowWidgetId(dashboardId);
|
||||
|
||||
if (isNewDashboard && isEmpty(selectedRowWidgetId)) {
|
||||
const newLayoutItem = placeWidgetAtBottom(widgetId, updatedLayout);
|
||||
updatedLayout = [...updatedLayout, newLayoutItem];
|
||||
@@ -554,7 +558,6 @@ function NewWidget({
|
||||
|
||||
updateDashboardMutation.mutateAsync(dashboard, {
|
||||
onSuccess: (updatedDashboard) => {
|
||||
setSelectedRowWidgetId(null);
|
||||
setSelectedDashboard(updatedDashboard.data);
|
||||
setToScrollWidgetId(selectedWidget?.id || '');
|
||||
safeNavigate({
|
||||
@@ -566,7 +569,6 @@ function NewWidget({
|
||||
selectedDashboard,
|
||||
query,
|
||||
isNewDashboard,
|
||||
selectedRowWidgetId,
|
||||
afterWidgets,
|
||||
selectedWidget,
|
||||
selectedTime.enum,
|
||||
@@ -577,7 +579,6 @@ function NewWidget({
|
||||
widgets,
|
||||
setSelectedDashboard,
|
||||
setToScrollWidgetId,
|
||||
setSelectedRowWidgetId,
|
||||
safeNavigate,
|
||||
dashboardId,
|
||||
]);
|
||||
@@ -681,6 +682,10 @@ function NewWidget({
|
||||
* on mount here with the currentQuery in the begining itself
|
||||
*/
|
||||
setSupersetQuery(currentQuery);
|
||||
|
||||
return (): void => {
|
||||
clearSelectedRowWidgetId(dashboardId);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
|
||||
129
frontend/src/hooks/useGetQueryLabels.test.ts
Normal file
129
frontend/src/hooks/useGetQueryLabels.test.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import {
|
||||
IBuilderFormula,
|
||||
IClickHouseQuery,
|
||||
IPromQLQuery,
|
||||
Query,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
import { useGetQueryLabels } from './useGetQueryLabels';
|
||||
|
||||
jest.mock('components/QueryBuilderV2/utils', () => ({
|
||||
getQueryLabelWithAggregation: jest.fn(() => []),
|
||||
}));
|
||||
|
||||
function buildQuery(overrides: Partial<Query> = {}): Query {
|
||||
return {
|
||||
id: 'test-id',
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
builder: {
|
||||
queryData: [],
|
||||
queryFormulas: [],
|
||||
queryTraceOperator: [],
|
||||
},
|
||||
promql: [],
|
||||
clickhouse_sql: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('useGetQueryLabels', () => {
|
||||
describe('QUERY_BUILDER type', () => {
|
||||
it('returns empty array when queryFormulas is undefined', () => {
|
||||
const query = buildQuery({
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
builder: {
|
||||
queryData: [],
|
||||
queryFormulas: (undefined as unknown) as IBuilderFormula[],
|
||||
queryTraceOperator: [],
|
||||
},
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useGetQueryLabels(query));
|
||||
|
||||
expect(result.current).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns formula labels when queryFormulas is populated', () => {
|
||||
const query = buildQuery({
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
builder: {
|
||||
queryData: [],
|
||||
queryFormulas: [
|
||||
({ queryName: 'F1' } as unknown) as IBuilderFormula,
|
||||
({ queryName: 'F2' } as unknown) as IBuilderFormula,
|
||||
],
|
||||
queryTraceOperator: [],
|
||||
},
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useGetQueryLabels(query));
|
||||
|
||||
expect(result.current).toEqual([
|
||||
{ label: 'F1', value: 'F1' },
|
||||
{ label: 'F2', value: 'F2' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CLICKHOUSE type', () => {
|
||||
it('returns empty array when clickhouse_sql is undefined', () => {
|
||||
const query = buildQuery({
|
||||
queryType: EQueryType.CLICKHOUSE,
|
||||
clickhouse_sql: (undefined as unknown) as IClickHouseQuery[],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useGetQueryLabels(query));
|
||||
|
||||
expect(result.current).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns labels from clickhouse_sql when populated', () => {
|
||||
const query = buildQuery({
|
||||
queryType: EQueryType.CLICKHOUSE,
|
||||
clickhouse_sql: [
|
||||
({ name: 'query_a' } as unknown) as IClickHouseQuery,
|
||||
({ name: 'query_b' } as unknown) as IClickHouseQuery,
|
||||
],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useGetQueryLabels(query));
|
||||
|
||||
expect(result.current).toEqual([
|
||||
{ label: 'query_a', value: 'query_a' },
|
||||
{ label: 'query_b', value: 'query_b' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PROM type (default)', () => {
|
||||
it('returns empty array when promql is undefined', () => {
|
||||
const query = buildQuery({
|
||||
queryType: EQueryType.PROM,
|
||||
promql: (undefined as unknown) as IPromQLQuery[],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useGetQueryLabels(query));
|
||||
|
||||
expect(result.current).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns labels from promql when populated', () => {
|
||||
const query = buildQuery({
|
||||
queryType: EQueryType.PROM,
|
||||
promql: [
|
||||
({ name: 'prom_1' } as unknown) as IPromQLQuery,
|
||||
({ name: 'prom_2' } as unknown) as IPromQLQuery,
|
||||
],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useGetQueryLabels(query));
|
||||
|
||||
expect(result.current).toEqual([
|
||||
{ label: 'prom_1', value: 'prom_1' },
|
||||
{ label: 'prom_2', value: 'prom_2' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,7 @@ export const useGetQueryLabels = (
|
||||
const queryLabels = getQueryLabelWithAggregation(
|
||||
currentQuery?.builder?.queryData || [],
|
||||
);
|
||||
const formulaLabels = currentQuery?.builder?.queryFormulas?.map(
|
||||
const formulaLabels = (currentQuery?.builder?.queryFormulas ?? []).map(
|
||||
(formula) => ({
|
||||
label: formula.queryName,
|
||||
value: formula.queryName,
|
||||
@@ -20,10 +20,13 @@ export const useGetQueryLabels = (
|
||||
return [...queryLabels, ...formulaLabels];
|
||||
}
|
||||
if (currentQuery?.queryType === EQueryType.CLICKHOUSE) {
|
||||
return currentQuery?.clickhouse_sql?.map((q) => ({
|
||||
return (currentQuery?.clickhouse_sql ?? []).map((q) => ({
|
||||
label: q.name,
|
||||
value: q.name,
|
||||
}));
|
||||
}
|
||||
return currentQuery?.promql?.map((q) => ({ label: q.name, value: q.name }));
|
||||
return (currentQuery?.promql ?? []).map((q) => ({
|
||||
label: q.name,
|
||||
value: q.name,
|
||||
}));
|
||||
}, [currentQuery]);
|
||||
|
||||
@@ -68,7 +68,6 @@ export const DashboardContext = createContext<IDashboardContext>({
|
||||
APIError
|
||||
>,
|
||||
selectedDashboard: {} as Dashboard,
|
||||
dashboardId: '',
|
||||
layouts: [],
|
||||
panelMap: {},
|
||||
setPanelMap: () => {},
|
||||
@@ -81,8 +80,6 @@ export const DashboardContext = createContext<IDashboardContext>({
|
||||
updateLocalStorageDashboardVariables: () => {},
|
||||
dashboardQueryRangeCalled: false,
|
||||
setDashboardQueryRangeCalled: () => {},
|
||||
selectedRowWidgetId: '',
|
||||
setSelectedRowWidgetId: () => {},
|
||||
isDashboardFetching: false,
|
||||
columnWidths: {},
|
||||
setColumnWidths: () => {},
|
||||
@@ -102,10 +99,6 @@ export function DashboardProvider({
|
||||
|
||||
const [isDashboardLocked, setIsDashboardLocked] = useState<boolean>(false);
|
||||
|
||||
const [selectedRowWidgetId, setSelectedRowWidgetId] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const [
|
||||
dashboardQueryRangeCalled,
|
||||
setDashboardQueryRangeCalled,
|
||||
@@ -468,8 +461,6 @@ export function DashboardProvider({
|
||||
updateLocalStorageDashboardVariables,
|
||||
dashboardQueryRangeCalled,
|
||||
setDashboardQueryRangeCalled,
|
||||
selectedRowWidgetId,
|
||||
setSelectedRowWidgetId,
|
||||
isDashboardFetching,
|
||||
columnWidths,
|
||||
setColumnWidths,
|
||||
@@ -488,8 +479,6 @@ export function DashboardProvider({
|
||||
currentDashboard,
|
||||
dashboardQueryRangeCalled,
|
||||
setDashboardQueryRangeCalled,
|
||||
selectedRowWidgetId,
|
||||
setSelectedRowWidgetId,
|
||||
isDashboardFetching,
|
||||
columnWidths,
|
||||
setColumnWidths,
|
||||
|
||||
@@ -58,8 +58,9 @@ jest.mock('react-redux', () => ({
|
||||
jest.mock('uuid', () => ({ v4: jest.fn(() => 'mock-uuid') }));
|
||||
|
||||
function TestComponent(): JSX.Element {
|
||||
const { dashboardResponse, dashboardId, selectedDashboard } = useDashboard();
|
||||
const { dashboardResponse, selectedDashboard } = useDashboard();
|
||||
const { dashboardVariables } = useDashboardVariables();
|
||||
const dashboardId = selectedDashboard?.id;
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
const PREFIX = 'dashboard_row_widget_';
|
||||
|
||||
function getKey(dashboardId: string): string {
|
||||
return `${PREFIX}${dashboardId}`;
|
||||
}
|
||||
|
||||
export function setSelectedRowWidgetId(
|
||||
dashboardId: string,
|
||||
widgetId: string,
|
||||
): void {
|
||||
const key = getKey(dashboardId);
|
||||
|
||||
// remove all other selected widget ids for the dashboard before setting the new one
|
||||
// to ensure only one widget is selected at a time. Helps out in weird navigate and refresh scenarios
|
||||
Object.keys(sessionStorage)
|
||||
.filter((k) => k.startsWith(PREFIX) && k !== key)
|
||||
.forEach((k) => sessionStorage.removeItem(k));
|
||||
|
||||
sessionStorage.setItem(key, widgetId);
|
||||
}
|
||||
|
||||
export function getSelectedRowWidgetId(dashboardId: string): string | null {
|
||||
return sessionStorage.getItem(getKey(dashboardId));
|
||||
}
|
||||
|
||||
export function clearSelectedRowWidgetId(dashboardId: string): void {
|
||||
sessionStorage.removeItem(getKey(dashboardId));
|
||||
}
|
||||
@@ -15,7 +15,6 @@ export interface IDashboardContext {
|
||||
handleDashboardLockToggle: (value: boolean) => void;
|
||||
dashboardResponse: UseQueryResult<SuccessResponseV2<Dashboard>, unknown>;
|
||||
selectedDashboard: Dashboard | undefined;
|
||||
dashboardId: string;
|
||||
layouts: Layout[];
|
||||
panelMap: Record<string, { widgets: Layout[]; collapsed: boolean }>;
|
||||
setPanelMap: React.Dispatch<React.SetStateAction<Record<string, any>>>;
|
||||
@@ -40,8 +39,6 @@ export interface IDashboardContext {
|
||||
) => void;
|
||||
dashboardQueryRangeCalled: boolean;
|
||||
setDashboardQueryRangeCalled: (value: boolean) => void;
|
||||
selectedRowWidgetId: string | null;
|
||||
setSelectedRowWidgetId: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
isDashboardFetching: boolean;
|
||||
columnWidths: WidgetColumnWidths;
|
||||
setColumnWidths: React.Dispatch<React.SetStateAction<WidgetColumnWidths>>;
|
||||
|
||||
@@ -75,6 +75,8 @@ def clickhouse(
|
||||
</cluster>
|
||||
</remote_servers>
|
||||
|
||||
<user_defined_executable_functions_config>*function.xml</user_defined_executable_functions_config>
|
||||
|
||||
<distributed_ddl>
|
||||
<path>/clickhouse/task_queue/ddl</path>
|
||||
<profile>default</profile>
|
||||
@@ -117,17 +119,74 @@ def clickhouse(
|
||||
</clickhouse>
|
||||
"""
|
||||
|
||||
custom_function_config = """
|
||||
<functions>
|
||||
<function>
|
||||
<type>executable</type>
|
||||
<name>histogramQuantile</name>
|
||||
<return_type>Float64</return_type>
|
||||
<argument>
|
||||
<type>Array(Float64)</type>
|
||||
<name>buckets</name>
|
||||
</argument>
|
||||
<argument>
|
||||
<type>Array(Float64)</type>
|
||||
<name>counts</name>
|
||||
</argument>
|
||||
<argument>
|
||||
<type>Float64</type>
|
||||
<name>quantile</name>
|
||||
</argument>
|
||||
<format>CSV</format>
|
||||
<command>./histogramQuantile</command>
|
||||
</function>
|
||||
</functions>
|
||||
"""
|
||||
|
||||
tmp_dir = tmpfs("clickhouse")
|
||||
cluster_config_file_path = os.path.join(tmp_dir, "cluster.xml")
|
||||
with open(cluster_config_file_path, "w", encoding="utf-8") as f:
|
||||
f.write(cluster_config)
|
||||
|
||||
custom_function_file_path = os.path.join(tmp_dir, "custom-function.xml")
|
||||
with open(custom_function_file_path, "w", encoding="utf-8") as f:
|
||||
f.write(custom_function_config)
|
||||
|
||||
container.with_volume_mapping(
|
||||
cluster_config_file_path, "/etc/clickhouse-server/config.d/cluster.xml"
|
||||
)
|
||||
container.with_volume_mapping(
|
||||
custom_function_file_path,
|
||||
"/etc/clickhouse-server/custom-function.xml",
|
||||
)
|
||||
container.with_network(network)
|
||||
container.start()
|
||||
|
||||
# Download and install the histogramQuantile binary
|
||||
wrapped = container.get_wrapped_container()
|
||||
exit_code, output = wrapped.exec_run(
|
||||
[
|
||||
"bash",
|
||||
"-c",
|
||||
(
|
||||
'version="v0.0.1" && '
|
||||
'node_os=$(uname -s | tr "[:upper:]" "[:lower:]") && '
|
||||
'node_arch=$(uname -m | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) && '
|
||||
"cd /tmp && "
|
||||
'wget -O histogram-quantile.tar.gz "https://github.com/SigNoz/signoz/releases/download/histogram-quantile%2F${version}/histogram-quantile_${node_os}_${node_arch}.tar.gz" && '
|
||||
"tar -xzf histogram-quantile.tar.gz && "
|
||||
"mkdir -p /var/lib/clickhouse/user_scripts && "
|
||||
"mv histogram-quantile /var/lib/clickhouse/user_scripts/histogramQuantile && "
|
||||
"chmod +x /var/lib/clickhouse/user_scripts/histogramQuantile"
|
||||
),
|
||||
],
|
||||
)
|
||||
if exit_code != 0:
|
||||
logger.warning(
|
||||
"Failed to install histogramQuantile binary: %s",
|
||||
output.decode(),
|
||||
)
|
||||
|
||||
connection = clickhouse_connect.get_client(
|
||||
user=container.username,
|
||||
password=container.password,
|
||||
|
||||
@@ -372,3 +372,153 @@ def test_histogram_count_no_param(
|
||||
values[1]["value"] == first_values[le]
|
||||
) ## to keep parallel to the cumulative test cases, first_value refers to the value at 10:02
|
||||
assert values[-1]["value"] == last_values[le]
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"space_agg, zeroth_value, first_value, last_value",
|
||||
[
|
||||
("p50", 500, 818.182, 550.725),
|
||||
("p75", 750, 3000, 826.087),
|
||||
("p90", 900, 6400, 991.304),
|
||||
("p95", 950, 8000, 4200),
|
||||
("p99", 990, 8000, 8000),
|
||||
],
|
||||
)
|
||||
def test_histogram_percentile_for_all_services(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_metrics: Callable[[List[Metrics]], None],
|
||||
space_agg: str,
|
||||
zeroth_value: float,
|
||||
first_value: float,
|
||||
last_value: float,
|
||||
) -> None:
|
||||
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
|
||||
start_ms = int((now - timedelta(minutes=65)).timestamp() * 1000)
|
||||
end_ms = int(now.timestamp() * 1000)
|
||||
metric_name = f"test_{space_agg}_bucket"
|
||||
|
||||
metrics = Metrics.load_from_file(
|
||||
FILE,
|
||||
base_time=now - timedelta(minutes=60),
|
||||
metric_name_override=metric_name,
|
||||
)
|
||||
insert_metrics(metrics)
|
||||
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
query = build_builder_query(
|
||||
"A",
|
||||
metric_name,
|
||||
"doesnotreallymatter",
|
||||
space_agg,
|
||||
)
|
||||
|
||||
response = make_query_request(signoz, token, start_ms, end_ms, [query])
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
|
||||
data = response.json()
|
||||
result_values = sorted(get_series_values(data, "A"), key=lambda x: x["timestamp"])
|
||||
assert len(result_values) == 60
|
||||
assert result_values[0]["value"] == zeroth_value
|
||||
assert result_values[1]["value"] == first_value
|
||||
assert result_values[-1]["value"] == last_value
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"space_agg, first_value, last_value",
|
||||
[
|
||||
("p50", 818.182, 550.725),
|
||||
("p75", 3000, 826.087),
|
||||
("p90", 6400, 991.304),
|
||||
("p95", 8000, 4200),
|
||||
("p99", 8000, 8000),
|
||||
],
|
||||
)
|
||||
def test_histogram_percentile_for_cumulative_service(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_metrics: Callable[[List[Metrics]], None],
|
||||
space_agg: str,
|
||||
first_value: float,
|
||||
last_value: float,
|
||||
) -> None:
|
||||
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
|
||||
start_ms = int((now - timedelta(minutes=65)).timestamp() * 1000)
|
||||
end_ms = int(now.timestamp() * 1000)
|
||||
metric_name = f"test_{space_agg}_cumulative_bucket"
|
||||
|
||||
metrics = Metrics.load_from_file(
|
||||
FILE,
|
||||
base_time=now - timedelta(minutes=60),
|
||||
metric_name_override=metric_name,
|
||||
)
|
||||
insert_metrics(metrics)
|
||||
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
query = build_builder_query(
|
||||
"A",
|
||||
metric_name,
|
||||
"doesnotreallymatter",
|
||||
space_agg,
|
||||
filter_expression='service = "api"',
|
||||
)
|
||||
|
||||
response = make_query_request(signoz, token, start_ms, end_ms, [query])
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
|
||||
data = response.json()
|
||||
result_values = sorted(get_series_values(data, "A"), key=lambda x: x["timestamp"])
|
||||
assert len(result_values) == 59
|
||||
assert result_values[0]["value"] == first_value
|
||||
assert result_values[-1]["value"] == last_value
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"space_agg, zeroth_value, first_value, last_value",
|
||||
[
|
||||
("p50", 500, 818.182, 550.725),
|
||||
("p75", 750, 3000, 826.087),
|
||||
("p90", 900, 6400, 991.304),
|
||||
("p95", 950, 8000, 4200),
|
||||
("p99", 990, 8000, 8000),
|
||||
],
|
||||
)
|
||||
def test_histogram_percentile_for_delta_service(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_metrics: Callable[[List[Metrics]], None],
|
||||
space_agg: str,
|
||||
zeroth_value: float,
|
||||
first_value: float,
|
||||
last_value: float,
|
||||
) -> None:
|
||||
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
|
||||
start_ms = int((now - timedelta(minutes=65)).timestamp() * 1000)
|
||||
end_ms = int(now.timestamp() * 1000)
|
||||
metric_name = f"test_{space_agg}_bucket"
|
||||
|
||||
metrics = Metrics.load_from_file(
|
||||
FILE,
|
||||
base_time=now - timedelta(minutes=60),
|
||||
metric_name_override=metric_name,
|
||||
)
|
||||
insert_metrics(metrics)
|
||||
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
query = build_builder_query(
|
||||
"A",
|
||||
metric_name,
|
||||
"doesnotreallymatter",
|
||||
space_agg,
|
||||
filter_expression='service = "web"',
|
||||
)
|
||||
|
||||
response = make_query_request(signoz, token, start_ms, end_ms, [query])
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
|
||||
data = response.json()
|
||||
result_values = sorted(get_series_values(data, "A"), key=lambda x: x["timestamp"])
|
||||
assert len(result_values) == 60
|
||||
assert result_values[0]["value"] == zeroth_value
|
||||
assert result_values[1]["value"] == first_value
|
||||
assert result_values[-1]["value"] == last_value
|
||||
Reference in New Issue
Block a user