diff --git a/dashboards/src/components/GridLayout/GridItemContent.tsx b/dashboards/src/components/GridLayout/GridItemContent.tsx index 86d5a5d..b6c62aa 100644 --- a/dashboards/src/components/GridLayout/GridItemContent.tsx +++ b/dashboards/src/components/GridLayout/GridItemContent.tsx @@ -89,6 +89,7 @@ export function GridItemContent(props: GridItemContentProps): ReactElement { return { kind: query.spec.plugin.kind, spec: query.spec.plugin.spec, + hidden: query.spec.hidden, }; }); diff --git a/dashboards/src/components/Panel/PanelContent.tsx b/dashboards/src/components/Panel/PanelContent.tsx index 4ac622d..36a10a1 100644 --- a/dashboards/src/components/Panel/PanelContent.tsx +++ b/dashboards/src/components/Panel/PanelContent.tsx @@ -13,7 +13,7 @@ import { usePlugin, PanelProps, QueryData, PanelPlugin } from '@perses-dev/plugin-system'; import { UnknownSpec, PanelDefinition, QueryDataType } from '@perses-dev/core'; -import { ReactElement } from 'react'; +import { ReactElement, useMemo } from 'react'; import { LoadingOverlay } from '@perses-dev/components'; import { Skeleton } from '@mui/material'; import { PanelPluginLoader } from './PanelPluginLoader'; @@ -32,6 +32,19 @@ export function PanelContent(props: PanelContentProps): ReactElement { const { panelPluginKind, definition, queryResults, spec, contentDimensions } = props; const { data: plugin, isLoading: isPanelLoading } = usePlugin('Panel', panelPluginKind, { useErrorBoundary: true }); + const queryResultsWithData = useMemo( + () => + queryResults.flatMap((q) => + q.data && !q.definition?.spec.hidden ? [{ data: q.data, definition: q.definition }] : [] + ), + [queryResults] + ); + + const areAllQueriesHidden = useMemo( + () => queryResults.every((q) => q.definition?.spec.hidden ?? false), + [queryResults] + ); + // Show fullsize skeleton if the panel plugin is loading. if (isPanelLoading) { return ( @@ -46,10 +59,7 @@ export function PanelContent(props: PanelContentProps): ReactElement { // Render the panel if any query has data, or the panel doesn't have a query attached (for example MarkdownPanel). // Loading indicator or errors of other queries are shown in the panel header. - const queryResultsWithData = queryResults.flatMap((q) => - q.data ? [{ data: q.data, definition: q.definition }] : [] - ); - if (queryResultsWithData.length > 0 || queryResults.length === 0) { + if (queryResultsWithData.length > 0 || queryResults.length === 0 || areAllQueriesHidden) { return ( { + const didChangeVisibility = queries.some( + (query) => query.spec.hidden !== previewDefinition.find((p) => p.kind === query.spec.plugin.kind)?.spec.hidden + ); + + if (didChangeVisibility) { + setPreviewDefinition( + queries.map((query) => { + return { + kind: query.spec.plugin.kind, + spec: query.spec.plugin.spec, + hidden: query.spec.hidden, + }; + }) + ); + } + + onQueriesChange(queries); + }, + [onQueriesChange, previewDefinition] + ); + return ( @@ -88,7 +113,7 @@ export function PanelQueriesSharedControls({ control={control} panelDefinition={panelDefinition} onJSONChange={onJSONChange} - onQueriesChange={onQueriesChange} + onQueriesChange={handleQueriesChange} onQueryRun={handleRunQuery} onPluginSpecChange={onPluginSpecChange} /> diff --git a/plugin-system/src/components/MultiQueryEditor/MultiQueryEditor.tsx b/plugin-system/src/components/MultiQueryEditor/MultiQueryEditor.tsx index d2ea8e9..fbec56d 100644 --- a/plugin-system/src/components/MultiQueryEditor/MultiQueryEditor.tsx +++ b/plugin-system/src/components/MultiQueryEditor/MultiQueryEditor.tsx @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { forwardRef, ReactElement, useState } from 'react'; +import { forwardRef, ReactElement, useCallback, useState } from 'react'; import { produce } from 'immer'; import { Button, Stack } from '@mui/material'; import AddIcon from 'mdi-material-ui/Plus'; @@ -138,6 +138,20 @@ export const MultiQueryEditor = forwardRef { + onChange( + produce(queries, (draft) => { + const entry = draft?.[index]; + if (entry) { + entry.spec.hidden = !isHidden; + } + }) + ); + }, + [onChange, queries] + ); + // show one query input if queries is empty const queryDefinitions: QueryDefinition[] = queries.length ? queries @@ -162,6 +176,8 @@ export const MultiQueryEditor = forwardRef 1 ? handleQueryDelete : undefined} onCollapseExpand={handleQueryCollapseExpand} + onVisibilityToggle={handleVisibilityToggle} + isHidden={query.spec.hidden} /> ))} diff --git a/plugin-system/src/components/MultiQueryEditor/QueryEditorContainer.tsx b/plugin-system/src/components/MultiQueryEditor/QueryEditorContainer.tsx index 013d875..56cdf89 100644 --- a/plugin-system/src/components/MultiQueryEditor/QueryEditorContainer.tsx +++ b/plugin-system/src/components/MultiQueryEditor/QueryEditorContainer.tsx @@ -13,10 +13,12 @@ import { produce } from 'immer'; import { QueryDefinition, QueryPluginType } from '@perses-dev/core'; -import { Stack, IconButton, Typography, BoxProps, Box, CircularProgress } from '@mui/material'; +import { Stack, IconButton, Typography, BoxProps, Box, CircularProgress, Tooltip } 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 EyeIcon from 'mdi-material-ui/Eye'; +import EyeOffIcon from 'mdi-material-ui/EyeOff'; import { forwardRef, ReactElement } from 'react'; import AlertIcon from 'mdi-material-ui/Alert'; import { InfoTooltip } from '@perses-dev/components'; @@ -36,7 +38,9 @@ interface QueryEditorContainerProps { onQueryRun: (index: number, query: QueryDefinition) => void; onCollapseExpand: (index: number) => void; isCollapsed?: boolean; + isHidden?: boolean; onDelete?: (index: number) => void; + onVisibilityToggle?: (index: number, isHidden: boolean) => void; } /** @@ -46,9 +50,11 @@ interface QueryEditorContainerProps { * @param index the index of the query in the list * @param query the query definition * @param isCollapsed whether the query editor is collapsed or not + * @param isHidden whether the query is hidden or not * @param onDelete callback when the query is deleted * @param onChange callback when the query is changed * @param onCollapseExpand callback when the query is collapsed or expanded + * @param onVisibilityToggle callback when the query is hidden or shown * @constructor */ @@ -61,10 +67,12 @@ export const QueryEditorContainer = forwardRef @@ -79,9 +87,16 @@ export const QueryEditorContainer = forwardRef onCollapseExpand(index)}> {isCollapsed ? : } - - Query #{index + 1} - + + + Query #{index + 1} + + {isHidden && ( + + Disabled + + )} + {queryResult?.isFetching && } @@ -117,6 +132,18 @@ export const QueryEditorContainer = forwardRef )} + {onVisibilityToggle && ( + + onVisibilityToggle?.(index, isHidden ?? false)} + sx={{ color: isHidden ? 'text.disabled' : 'text.secondary' }} + > + {isHidden ? : } + + + )} {onDelete && ( onDelete && onDelete(index)}> diff --git a/plugin-system/src/runtime/DataQueriesProvider/DataQueriesProvider.tsx b/plugin-system/src/runtime/DataQueriesProvider/DataQueriesProvider.tsx index 97a5ed2..9838a15 100644 --- a/plugin-system/src/runtime/DataQueriesProvider/DataQueriesProvider.tsx +++ b/plugin-system/src/runtime/DataQueriesProvider/DataQueriesProvider.tsx @@ -73,6 +73,7 @@ export function DataQueriesProvider(props: DataQueriesProviderProps): ReactEleme kind: type, spec: { plugin: definition, + hidden: definition.hidden, }, }; }); diff --git a/plugin-system/src/runtime/DataQueriesProvider/model.ts b/plugin-system/src/runtime/DataQueriesProvider/model.ts index 8e94759..bc7d971 100644 --- a/plugin-system/src/runtime/DataQueriesProvider/model.ts +++ b/plugin-system/src/runtime/DataQueriesProvider/model.ts @@ -18,7 +18,7 @@ import { useListPluginMetadata } from '../plugin-registry'; export type QueryOptions = Record; export interface DataQueriesProviderProps { - definitions: Array>; + definitions: Array & { hidden?: boolean }>; children?: ReactNode; options?: QueryOptions; queryOptions?: Omit;