Skip to content
Merged
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
3 changes: 2 additions & 1 deletion components/src/Legend/TableLegend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const getCheckboxColor: TableProps<LegendItem>['getCheckboxColor'] = (data) => {

// This is a rough estimate of value that needs to be subtracted from the total width to avoid horizontal scrolling on initial render
const TABLE_PADDING = 45;
const EMPTY_COLUMNS: Array<TableColumnConfig<LegendItem>> = [];

export function TableLegend({
items,
Expand All @@ -62,7 +63,7 @@ export function TableLegend({
onItemMouseOut,
height,
width,
columns: additionalColumns = [],
columns: additionalColumns = EMPTY_COLUMNS,
sorting,
onSortingChange,
}: TableLegendProps): ReactElement {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import type { QueryDefinition } from '@perses-dev/spec';
import type { QueryDefinition, QueryType } from '@perses-dev/spec';
import { renderHook } from '@testing-library/react';
import type { ReactElement } from 'react';
import React from 'react';
import React, { useMemo } from 'react';

import {
MOCK_TIME_SERIES_DATA,
Expand All @@ -25,7 +25,8 @@ import {
MOCK_SILENCES_DATA,
MOCK_JSON_DATA,
} from '../../test';
import { DataQueriesProvider, useDataQueries } from './DataQueriesProvider';
import { DataQueriesContext, DataQueriesProvider, useDataQueries } from './DataQueriesProvider';
import type { DataQueriesContextType } from './model';

vi.mock('../time-series-queries', () => ({
useTimeSeriesQueries: vi.fn().mockImplementation(() => [{ data: MOCK_TIME_SERIES_DATA }]),
Expand Down Expand Up @@ -99,7 +100,85 @@ vi.mock('../plugin-registry', () => ({
})),
}));

function createQueryContext(): DataQueriesContextType {
const timeSeriesDefinition: QueryDefinition = {
kind: 'TimeSeriesQuery',
spec: { plugin: { kind: 'PrometheusTimeSeriesQuery', spec: {} } },
};
const traceDefinition: QueryDefinition = {
kind: 'TraceQuery',
spec: { plugin: { kind: 'TempoTraceQuery', spec: {} } },
};
const traceError = new Error('Trace failed');
return {
queryDefinitions: [timeSeriesDefinition, traceDefinition],
queryResults: [
{
definition: timeSeriesDefinition,
data: MOCK_TIME_SERIES_DATA,
isFetching: false,
isLoading: false,
error: new Error('Time series failed'),
},
{
definition: traceDefinition,
data: MOCK_TRACE_DATA,
isFetching: true,
isLoading: false,
error: traceError,
},
],
errors: [new Error('Time series failed'), traceError],
isFetching: true,
isLoading: false,
refetchAll: vi.fn(),
};
}

describe('useDataQueries', (): void => {
it('reuses chart transformations on local rerenders and updates them when query type or data changes', () => {
let context = createQueryContext();
const traceDefinition = context.queryDefinitions[1];
const traceError = context.errors[1];
const wrapper = ({ children }: React.PropsWithChildren): ReactElement => (
<DataQueriesContext.Provider value={context}>{children}</DataQueriesContext.Provider>
);
const transform = vi.fn((queries: DataQueriesContextType['queryResults']) => queries.map((query) => query.data));
const { result, rerender } = renderHook(
({ queryType }: { queryType: keyof QueryType }) => {
const queries = useDataQueries(queryType);
const data = useMemo(() => transform(queries.queryResults), [queries.queryResults]);
return { queries, data };
},
{ wrapper, initialProps: { queryType: 'TimeSeriesQuery' as keyof QueryType } },
);
const initial = result.current.queries;
rerender({ queryType: 'TimeSeriesQuery' });
expect(result.current.queries).toBe(initial);
expect(transform).toHaveBeenCalledTimes(1);
expect(result.current.data).toEqual([MOCK_TIME_SERIES_DATA]);
expect(result.current.queries.isFetching).toBe(false);

rerender({ queryType: 'TraceQuery' });
expect(transform).toHaveBeenCalledTimes(2);
expect(result.current.data).toEqual([MOCK_TRACE_DATA]);
expect(result.current.queries.queryDefinitions).toEqual([traceDefinition]);
expect(result.current.queries.queryResults[0]?.data).toBe(MOCK_TRACE_DATA);
expect(result.current.queries.errors).toEqual([traceError]);
expect(result.current.queries.isFetching).toBe(true);

context = {
...context,
queryResults: context.queryResults.map((query) => ({ ...query, isFetching: false })),
isFetching: false,
};
rerender({ queryType: 'TraceQuery' });
expect(transform).toHaveBeenCalledTimes(3);
expect(result.current.queries.isFetching).toBe(false);
result.current.queries.refetchAll();
expect(context.refetchAll).toHaveBeenCalledOnce();
});

it('should return the correct data for TimeSeriesQuery', () => {
const definitions: QueryDefinition[] = [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,26 +45,26 @@ export function useDataQueriesContext(): DataQueriesContextType {
export function useDataQueries<T extends keyof QueryType>(queryType: T): UseDataQueryResults<QueryType[T]> {
const ctx = useDataQueriesContext();

// Filter query definitions based on the specified query type
const filteredQueryDefinitions = ctx.queryDefinitions.filter((definition) => definition.kind === queryType);

// Filter the query results based on the specified query type
const filteredQueryResults = ctx.queryResults.filter(
(queryResult) => queryResult?.definition?.kind === queryType,
) as Array<QueryData<QueryType[T]>>;

// Filter the errors based on the specified query type
const filteredErrors = ctx.errors.filter((errors, index) => ctx.queryResults[index]?.definition?.kind === queryType);

// Create a new context object with the filtered results and errors
return {
queryDefinitions: filteredQueryDefinitions,
queryResults: filteredQueryResults,
isFetching: filteredQueryResults.some((result) => result.isFetching),
isLoading: filteredQueryResults.some((result) => result.isLoading),
refetchAll: ctx.refetchAll,
errors: filteredErrors,
};
// Keep result arrays stable during local panel interactions so downstream chart
// transformations can reuse their memoized results.
return useMemo(() => {
const filteredQueryDefinitions = ctx.queryDefinitions.filter((definition) => definition.kind === queryType);
const filteredQueryResults = ctx.queryResults.filter(
(queryResult) => queryResult?.definition?.kind === queryType,
) as Array<QueryData<QueryType[T]>>;
const filteredErrors = ctx.errors.filter(
(_error, index) => ctx.queryResults[index]?.definition?.kind === queryType,
);

return {
queryDefinitions: filteredQueryDefinitions,
queryResults: filteredQueryResults,
isFetching: filteredQueryResults.some((result) => result.isFetching),
isLoading: filteredQueryResults.some((result) => result.isLoading),
refetchAll: ctx.refetchAll,
errors: filteredErrors,
};
}, [ctx.queryDefinitions, ctx.queryResults, ctx.errors, ctx.refetchAll, queryType]);
}

export function DataQueriesProvider(props: DataQueriesProviderProps): ReactElement {
Expand Down
Loading