Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions plugin-system/src/model/alerts-queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright The Perses Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { Query, QueryKey } from '@tanstack/react-query';
import { AlertsData, UnknownSpec } from '@perses-dev/spec';
import { DatasourceStore, VariableStateMap } from '../runtime';
import { Plugin } from './plugin-base';

/**
* An object containing all the dependencies of an AlertsQuery.
*/
type AlertsQueryPluginDependencies = {
/**
* Returns a list of variables name this alerts query depends on.
*/
variables?: string[];
};

/**
* A plugin for running alerts queries.
* Alerts represent current state, not historical data, so the context
* does NOT include absoluteTimeRange.
*/
export interface AlertsQueryPlugin<Spec = UnknownSpec> extends Plugin<Spec> {
getAlertsData: (spec: Spec, ctx: AlertsQueryContext, abortSignal?: AbortSignal) => Promise<AlertsData>;
dependsOn?: (spec: Spec, ctx: AlertsQueryContext) => AlertsQueryPluginDependencies;
}

/**
* Context available to AlertsQuery plugins at runtime.
* Note: No absoluteTimeRange since alerts represent current state.
*/
export interface AlertsQueryContext {
datasourceStore: DatasourceStore;
variableState: VariableStateMap;
}

export type AlertsDataQuery = Query<AlertsData, unknown, AlertsData, QueryKey>;
2 changes: 2 additions & 0 deletions plugin-system/src/model/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ export * from './trace-queries';
export * from './profile-queries';
export * from './variables';
export * from './calculations';
export * from './alerts-queries';
export * from './silences-queries';
4 changes: 4 additions & 0 deletions plugin-system/src/model/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { ProfileQueryPlugin } from './profile-queries';
import { VariablePlugin } from './variables';
import { ExplorePlugin } from './explore';
import { LogQueryPlugin } from './log-queries';
import { AlertsQueryPlugin } from './alerts-queries';
import { SilencesQueryPlugin } from './silences-queries';

export interface PluginModuleSpec {
plugins: PluginMetadata[];
Expand Down Expand Up @@ -81,6 +83,8 @@ export interface SupportedPlugins {
TraceQuery: TraceQueryPlugin;
ProfileQuery: ProfileQueryPlugin;
LogQuery: LogQueryPlugin;
AlertsQuery: AlertsQueryPlugin;
SilencesQuery: SilencesQueryPlugin;
Datasource: DatasourcePlugin;
Explore: ExplorePlugin;
}
Expand Down
48 changes: 48 additions & 0 deletions plugin-system/src/model/silences-queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright The Perses Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { Query, QueryKey } from '@tanstack/react-query';
import { SilencesData, UnknownSpec } from '@perses-dev/spec';
import { DatasourceStore, VariableStateMap } from '../runtime';
import { Plugin } from './plugin-base';

/**
* An object containing all the dependencies of a SilencesQuery.
*/
type SilencesQueryPluginDependencies = {
/**
* Returns a list of variables name this silences query depends on.
*/
variables?: string[];
};

/**
* A plugin for running silences queries.
* Silences represent current state, not historical data, so the context
* does NOT include absoluteTimeRange.
*/
export interface SilencesQueryPlugin<Spec = UnknownSpec> extends Plugin<Spec> {
getSilencesData: (spec: Spec, ctx: SilencesQueryContext, abortSignal?: AbortSignal) => Promise<SilencesData>;
dependsOn?: (spec: Spec, ctx: SilencesQueryContext) => SilencesQueryPluginDependencies;
}

/**
* Context available to SilencesQuery plugins at runtime.
* Note: No absoluteTimeRange since silences represent current state.
*/
export interface SilencesQueryContext {
datasourceStore: DatasourceStore;
variableState: VariableStateMap;
}

export type SilencesDataQuery = Query<SilencesData, unknown, SilencesData, QueryKey>;
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@

import React, { ReactElement } from 'react';
import { renderHook } from '@testing-library/react';
import { MOCK_TIME_SERIES_DATA, MOCK_TRACE_DATA, MOCK_PROFILE_DATA, MOCK_LOG_DATA } from '../../test';
import {
MOCK_TIME_SERIES_DATA,
MOCK_TRACE_DATA,
MOCK_PROFILE_DATA,
MOCK_LOG_DATA,
MOCK_ALERTS_DATA,
MOCK_SILENCES_DATA,
} from '../../test';
import { useListPluginMetadata } from '../plugin-registry';
import { DataQueriesProvider, useDataQueries } from './DataQueriesProvider';
import { useQueryType } from './model';
Expand All @@ -34,6 +41,14 @@ jest.mock('../log-queries', () => ({
useLogQueries: jest.fn().mockImplementation(() => [{ data: MOCK_LOG_DATA }]),
}));

jest.mock('../alerts-queries', () => ({
useAlertsQueries: jest.fn().mockImplementation(() => [{ data: MOCK_ALERTS_DATA }]),
}));

jest.mock('../silences-queries', () => ({
useSilencesQueries: jest.fn().mockImplementation(() => [{ data: MOCK_SILENCES_DATA }]),
}));

jest.mock('../plugin-registry', () => ({
useListPluginMetadata: jest.fn().mockImplementation(() => ({
data: [
Expand All @@ -55,6 +70,24 @@ jest.mock('../plugin-registry', () => ({
},
kind: 'TraceQuery',
},
{
spec: {
display: {
name: 'Alertmanager Alerts Query',
},
name: 'AlertmanagerAlertsQuery',
},
kind: 'AlertsQuery',
},
{
spec: {
display: {
name: 'Alertmanager Silences Query',
},
name: 'AlertmanagerSilencesQuery',
},
kind: 'SilencesQuery',
},
],
isLoading: false,
})),
Expand Down Expand Up @@ -100,6 +133,42 @@ describe('useDataQueries', (): void => {
});
expect(traceResult.current.queryResults[0]?.data).toEqual(MOCK_TRACE_DATA);
});

it('should return the correct data for AlertsQuery', () => {
const definitions = [
{
kind: 'AlertmanagerAlertsQuery',
spec: {},
},
];

const wrapper = ({ children }: React.PropsWithChildren): ReactElement => {
return <DataQueriesProvider definitions={definitions}>{children}</DataQueriesProvider>;
};

const { result } = renderHook(() => useDataQueries('AlertsQuery'), {
wrapper,
});
expect(result.current.queryResults[0]?.data).toEqual(MOCK_ALERTS_DATA);
});

it('should return the correct data for SilencesQuery', () => {
const definitions = [
{
kind: 'AlertmanagerSilencesQuery',
spec: {},
},
];

const wrapper = ({ children }: React.PropsWithChildren): ReactElement => {
return <DataQueriesProvider definitions={definitions}>{children}</DataQueriesProvider>;
};

const { result } = renderHook(() => useDataQueries('SilencesQuery'), {
wrapper,
});
expect(result.current.queryResults[0]?.data).toEqual(MOCK_SILENCES_DATA);
});
});

describe('useQueryType', () => {
Expand All @@ -109,6 +178,8 @@ describe('useQueryType', () => {
const getQueryType = result.current;
expect(getQueryType('PrometheusTimeSeriesQuery')).toBe('TimeSeriesQuery');
expect(getQueryType('TempoTraceQuery')).toBe('TraceQuery');
expect(getQueryType('AlertmanagerAlertsQuery')).toBe('AlertsQuery');
expect(getQueryType('AlertmanagerSilencesQuery')).toBe('SilencesQuery');
});

it('should throw an error if query type is not found ', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { QueryType, TimeSeriesQueryDefinition } from '@perses-dev/spec';
import { useTimeSeriesQueries } from '../time-series-queries';
import { useTraceQueries, TraceQueryDefinition } from '../trace-queries';
import { useProfileQueries, ProfileQueryDefinition } from '../profile-queries';
import { useAlertsQueries, AlertsQueryDefinition } from '../alerts-queries';
import { useSilencesQueries, SilencesQueryDefinition } from '../silences-queries';

import { useUsageMetrics } from '../UsageMetricsProvider';
import { LogQueryDefinition, useLogQueries } from '../log-queries';
Expand Down Expand Up @@ -98,19 +100,33 @@ export function DataQueriesProvider(props: DataQueriesProviderProps): ReactEleme
const logQueries = queryDefinitions.filter((definition) => definition.kind === 'LogQuery') as LogQueryDefinition[];
const logResults = useLogQueries(logQueries);

const alertsQueries = queryDefinitions.filter(
(definition) => definition.kind === 'AlertsQuery'
) as AlertsQueryDefinition[];
const alertsResults = useAlertsQueries(alertsQueries);

const silencesQueries = queryDefinitions.filter(
(definition) => definition.kind === 'SilencesQuery'
) as SilencesQueryDefinition[];
const silencesResults = useSilencesQueries(silencesQueries);

const refetchAll = useCallback(() => {
timeSeriesResults.forEach((result) => result.refetch());
traceResults.forEach((result) => result.refetch());
profileResults.forEach((result) => result.refetch());
logResults.forEach((result) => result.refetch());
}, [timeSeriesResults, traceResults, profileResults, logResults]);
alertsResults.forEach((result) => result.refetch());
silencesResults.forEach((result) => result.refetch());
}, [timeSeriesResults, traceResults, profileResults, logResults, alertsResults, silencesResults]);

const ctx = useMemo(() => {
const mergedQueryResults = [
...transformQueryResults(timeSeriesResults, timeSeriesQueries),
...transformQueryResults(traceResults, traceQueries),
...transformQueryResults(profileResults, profileQueries),
...transformQueryResults(logResults, logQueries),
...transformQueryResults(alertsResults, alertsQueries),
...transformQueryResults(silencesResults, silencesQueries),
];

if (queryOptions?.enabled) {
Expand Down Expand Up @@ -141,6 +157,10 @@ export function DataQueriesProvider(props: DataQueriesProviderProps): ReactEleme
profileResults,
logQueries,
logResults,
alertsQueries,
alertsResults,
silencesQueries,
silencesResults,
refetchAll,
queryOptions?.enabled,
usageMetrics,
Expand Down
37 changes: 35 additions & 2 deletions plugin-system/src/runtime/DataQueriesProvider/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ export function useQueryType(): (pluginKind: string) => string | undefined {
const { data: traceQueryPlugins, isLoading: isTraceQueryPluginLoading } = useListPluginMetadata(['TraceQuery']);
const { data: profileQueryPlugins, isLoading: isProfileQueryPluginLoading } = useListPluginMetadata(['ProfileQuery']);
const { data: logQueries, isLoading: isLogQueryPluginLoading } = useListPluginMetadata(['LogQuery']);
const { data: alertsQueryPlugins, isLoading: isAlertsQueryPluginLoading } = useListPluginMetadata(['AlertsQuery']);
const { data: silencesQueryPlugins, isLoading: isSilencesQueryPluginLoading } = useListPluginMetadata([
'SilencesQuery',
]);

// For example, `map: {"TimeSeriesQuery":["PrometheusTimeSeriesQuery"],"TraceQuery":["TempoTraceQuery"]}`
const queryTypeMap = useMemo(() => {
Expand All @@ -73,6 +77,8 @@ export function useQueryType(): (pluginKind: string) => string | undefined {
TraceQuery: [],
ProfileQuery: [],
LogQuery: [],
AlertsQuery: [],
SilencesQuery: [],
};

if (timeSeriesQueryPlugins) {
Expand All @@ -99,8 +105,27 @@ export function useQueryType(): (pluginKind: string) => string | undefined {
});
}

if (alertsQueryPlugins) {
alertsQueryPlugins.forEach((plugin) => {
map[plugin.kind]?.push(plugin.spec.name);
});
}

if (silencesQueryPlugins) {
silencesQueryPlugins.forEach((plugin) => {
map[plugin.kind]?.push(plugin.spec.name);
});
}

return map;
}, [timeSeriesQueryPlugins, traceQueryPlugins, profileQueryPlugins, logQueries]);
}, [
timeSeriesQueryPlugins,
traceQueryPlugins,
profileQueryPlugins,
logQueries,
alertsQueryPlugins,
silencesQueryPlugins,
]);

const getQueryType = useCallback(
(pluginKind: string) => {
Expand All @@ -114,13 +139,19 @@ export function useQueryType(): (pluginKind: string) => string | undefined {
return isProfileQueryPluginLoading;
case 'LokiLogQuery':
return isLogQueryPluginLoading;
case 'AlertmanagerAlertsQuery':
return isAlertsQueryPluginLoading;
case 'AlertmanagerSilencesQuery':
return isSilencesQueryPluginLoading;
}

return (
isTraceQueryPluginLoading ||
isTimeSeriesQueryLoading ||
isProfileQueryPluginLoading ||
isLogQueryPluginLoading
isLogQueryPluginLoading ||
isAlertsQueryPluginLoading ||
isSilencesQueryPluginLoading
);
};

Expand All @@ -142,6 +173,8 @@ export function useQueryType(): (pluginKind: string) => string | undefined {
isTraceQueryPluginLoading,
isProfileQueryPluginLoading,
isLogQueryPluginLoading,
isAlertsQueryPluginLoading,
isSilencesQueryPluginLoading,
]
);

Expand Down
Loading
Loading