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
12 changes: 2 additions & 10 deletions dashboards/src/components/GridLayout/GridItemContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,6 @@ export function GridItemContent(props: GridItemContentProps): ReactElement {

const { data: plugin } = usePlugin('Panel', panelDefinition.spec.plugin.kind);

const queryDefinitions = queries ?? [];
const definitions = queryDefinitions.map((query) => {
return {
kind: query.spec.plugin.kind,
spec: query.spec.plugin.spec,
};
});

const pluginQueryOptions =
typeof plugin?.queryOptions === 'function'
? plugin?.queryOptions(panelDefinition.spec.plugin.spec)
Expand All @@ -125,7 +117,7 @@ export function GridItemContent(props: GridItemContentProps): ReactElement {
}}
>
<DataQueriesProvider
definitions={definitions}
definitions={queries ?? []}
options={{ suggestedStepMs, ...pluginQueryOptions }}
queryOptions={{ enabled: shouldQuery }}
>
Expand All @@ -142,7 +134,7 @@ export function GridItemContent(props: GridItemContentProps): ReactElement {
</DataQueriesProvider>
<QueryViewerDialog
open={openQueryViewer}
queryDefinitions={queryDefinitions}
queryDefinitions={queries ?? []}
onClose={() => setOpenQueryViewer(false)}
/>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,30 +51,15 @@ export function PanelQueriesSharedControls({
[panelDefinition.spec.plugin.spec, pluginPreview]
);

const [previewDefinition, setPreviewDefinition] = useState(
() =>
panelDefinition.spec.queries?.map((query) => {
return {
kind: query.spec.plugin.kind,
spec: query.spec.plugin.spec,
};
}) ?? []
);
const [previewDefinition, setPreviewDefinition] = useState<QueryDefinition[]>(panelDefinition.spec.queries ?? []);

const handleOnQueriesChange = useCallback(
(queries: QueryDefinition[]) => {
onQueriesChange(queries);

// If the number of queries has changed, force preview definition update to remove results of deleted queries.
if (queries.length !== previewDefinition.length) {
setPreviewDefinition(
queries.map((query) => {
return {
kind: query.spec.plugin.kind,
spec: query.spec.plugin.spec,
};
})
);
setPreviewDefinition(queries);
}
},
[onQueriesChange, previewDefinition.length]
Expand All @@ -83,10 +68,7 @@ export function PanelQueriesSharedControls({
const handleRunQuery = useCallback((index: number, newDef: QueryDefinition) => {
setPreviewDefinition((prev) => {
const newDefinitions = [...prev];
newDefinitions[index] = {
kind: newDef.spec.plugin.kind,
spec: newDef.spec.plugin.spec,
};
newDefinitions[index] = newDef;
return newDefinitions;
});
}, []);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,28 @@

import { produce } from 'immer';
import { QueryDefinition, QueryPluginType } from '@perses-dev/spec';
import { Stack, IconButton, Typography, BoxProps, Box, CircularProgress } from '@mui/material';
import {
Stack,
IconButton,
Typography,
BoxProps,
Box,
CircularProgress,
TextField,
Button,
InputAdornment,
} from '@mui/material';
import DeleteIcon from 'mdi-material-ui/DeleteOutline';
import ChevronDown from 'mdi-material-ui/ChevronDown';
import ChevronRight from 'mdi-material-ui/ChevronRight';
import { forwardRef, ReactElement } from 'react';
import { forwardRef, ReactElement, useState } from 'react';
import AlertIcon from 'mdi-material-ui/Alert';
import { InfoTooltip } from '@perses-dev/components';
import PencilIcon from 'mdi-material-ui/Pencil';
import CheckIcon from 'mdi-material-ui/Check';
import { QueryData } from '../../runtime';
import { PluginEditor, PluginEditorProps, PluginEditorRef } from '../PluginEditor';
import { defaultQueryName } from './utils';

/**
* Properties for {@link QueryEditorContainer}
Expand Down Expand Up @@ -66,6 +79,20 @@ export const QueryEditorContainer = forwardRef<PluginEditorRef, QueryEditorConta
onQueryRun,
onCollapseExpand,
} = props;

const [isEditingName, setIsEditingName] = useState(false);
const [name, setName] = useState(query.spec.name ?? defaultQueryName(index));

function handleNameChange(): void {
setIsEditingName(false);
onChange(
index,
produce(query, (draft) => {
draft.spec.name = name;
})
);
}

return (
<Stack key={index} spacing={1}>
<Stack
Expand All @@ -75,13 +102,57 @@ export const QueryEditorContainer = forwardRef<PluginEditorRef, QueryEditorConta
borderBottom={1}
borderColor={(theme) => theme.palette.divider}
>
<Stack direction="row">
<IconButton size="small" onClick={() => onCollapseExpand(index)}>
<Stack direction="row" gap={1}>
<IconButton
size="small"
sx={{ width: 'fit-content', height: 'fit-content' }}
onClick={() => onCollapseExpand(index)}
>
{isCollapsed ? <ChevronRight /> : <ChevronDown />}
</IconButton>
<Typography variant="overline" component="h4">
Query #{index + 1}
</Typography>
<Stack
direction="row"
gap={1}
alignItems="center"
alignContent="center"
sx={{
'&:hover button': {
visibility: 'visible',
},
}}
>
{isEditingName ? (
<TextField
size="small"
variant="outlined"
value={name}
onChange={(e) => setName(e.target.value)}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton size="small" aria-label="save query name" onClick={handleNameChange} edge="end">
<CheckIcon />
</IconButton>
</InputAdornment>
),
}}
/>
) : (
<>
<Typography variant="overline" component="h4">
{name}
</Typography>
<Button
variant="contained"
size="small"
onClick={() => setIsEditingName(true)}
sx={{ visibility: isCollapsed ? 'hidden' : 'visible' }}
>
<PencilIcon fontSize="small" />
</Button>
</>
)}
</Stack>
</Stack>
<Stack direction="row" alignItems="center">
{queryResult?.isFetching && <CircularProgress aria-label="loading" size="1.125rem" />}
Expand Down
1 change: 1 addition & 0 deletions plugin-system/src/components/MultiQueryEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
// limitations under the License.

export * from './MultiQueryEditor';
export * from './utils';
30 changes: 30 additions & 0 deletions plugin-system/src/components/MultiQueryEditor/utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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 { QueryDefinition } from '@perses-dev/spec';

export function defaultQueryName(index: number): string {
return `Query #${index + 1}`;
}

export function getQueryName(definitions: QueryDefinition[], query: QueryDefinition): string {
if (query.spec.name) {
return query.spec.name;
}
const index = definitions.findIndex((definition) => definition === query);
return defaultQueryName(index);
}

export function generateQueryNames(definitions: QueryDefinition[]): string[] {
return definitions.map((queryDef: QueryDefinition, index: number) => queryDef.spec.name ?? defaultQueryName(index));
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ describe('PanelSpecEditor', () => {
const renderComponent = (props: Omit<PanelSpecEditorProps, 'control'>): void => {
const DataQueriesProviderMock = (childProps: { children: ReactElement }): ReactElement => {
const ctx = {
queryDefinitions: [],
queryResults: [],
refetchAll: (): void => {},
isFetching: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { ErrorAlert, JSONEditor, LinksEditor } from '@perses-dev/components';
import { PanelDefinition, PanelEditorValues, QueryDefinition, UnknownSpec } from '@perses-dev/spec';
import { Control, Controller } from 'react-hook-form';
import { forwardRef, ReactElement } from 'react';
import { QueryCountProvider, useDataQueriesContext, usePlugin } from '../../runtime';
import { useDataQueriesContext, usePlugin } from '../../runtime';
import { PanelPlugin } from '../../model';
import { OptionsEditorTabsProps, OptionsEditorTabs } from '../OptionsEditorTabs';
import { MultiQueryEditor } from '../MultiQueryEditor';
Expand Down Expand Up @@ -131,11 +131,7 @@ export const PanelSpecEditor = forwardRef<PluginEditorRef, PanelSpecEditorProps>
),
});

return (
<QueryCountProvider queryCount={(panelDefinition.spec.queries ?? []).length}>
<OptionsEditorTabs key={tabs.length} tabs={tabs} />
</QueryCountProvider>
);
return <OptionsEditorTabs key={tabs.length} tabs={tabs} />;
});

PanelSpecEditor.displayName = 'PanelSpecEditor';
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import React, { ReactElement } from 'react';
import { renderHook } from '@testing-library/react';
import { QueryDefinition } from '@perses-dev/spec';
import {
MOCK_TIME_SERIES_DATA,
MOCK_TRACE_DATA,
Expand All @@ -21,9 +22,7 @@ import {
MOCK_ALERTS_DATA,
MOCK_SILENCES_DATA,
} from '../../test';
import { useListPluginMetadata } from '../plugin-registry';
import { DataQueriesProvider, useDataQueries } from './DataQueriesProvider';
import { useQueryType } from './model';

jest.mock('../time-series-queries', () => ({
useTimeSeriesQueries: jest.fn().mockImplementation(() => [{ data: MOCK_TIME_SERIES_DATA }]),
Expand Down Expand Up @@ -95,11 +94,16 @@ jest.mock('../plugin-registry', () => ({

describe('useDataQueries', (): void => {
it('should return the correct data for TimeSeriesQuery', () => {
const definitions = [
const definitions: QueryDefinition[] = [
{
kind: 'PrometheusTimeSeriesQuery',
kind: 'TimeSeriesQuery',
spec: {
query: 'up',
plugin: {
kind: 'PrometheusTimeSeriesQuery',
spec: {
query: 'up',
},
},
},
},
];
Expand All @@ -115,11 +119,16 @@ describe('useDataQueries', (): void => {
});

it('should return the correct data for TraceQuery', () => {
const definitions = [
const definitions: QueryDefinition[] = [
{
kind: 'TempoTraceQuery',
kind: 'TraceQuery',
spec: {
query: '{ duration > 1000ms }',
plugin: {
kind: 'TempoTraceQuery',
spec: {
query: '{ duration > 1000ms }',
},
},
},
},
];
Expand All @@ -135,10 +144,15 @@ describe('useDataQueries', (): void => {
});

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

Expand All @@ -153,10 +167,15 @@ describe('useDataQueries', (): void => {
});

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

Expand All @@ -170,32 +189,3 @@ describe('useDataQueries', (): void => {
expect(result.current.queryResults[0]?.data).toEqual(MOCK_SILENCES_DATA);
});
});

describe('useQueryType', () => {
it('should return the correct query type for a given plugin kind', () => {
const { result } = renderHook(() => 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 ', () => {
const { result } = renderHook(() => useQueryType());

const getQueryType = result.current;
expect(() => getQueryType('UnknownQuery')).toThrow(`Unable to determine the query type: UnknownQuery`);
});

it('should return undefined if useListPluginMetadata is still loading', () => {
(useListPluginMetadata as jest.Mock).mockReturnValue({ isLoading: true });
const { result } = renderHook(() => useQueryType());

const getQueryType = result.current;
expect(getQueryType('PrometheusTimeSeriesQuery')).toBeUndefined();
expect(getQueryType('TempoTraceQuery')).toBeUndefined();
expect(getQueryType('UnknownQuery')).toBeUndefined();
});
});
Loading
Loading