diff --git a/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationEditor.tsx b/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationEditor.tsx
index ecf4683714d..f464cf9786c 100644
--- a/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationEditor.tsx
+++ b/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationEditor.tsx
@@ -1,6 +1,6 @@
import { css } from '@emotion/css';
import { DataSourceApi, GrafanaTheme2 } from '@grafana/data';
-import { FlexItem, Stack } from '@grafana/experimental';
+import { Stack } from '@grafana/experimental';
import { Button, useStyles2 } from '@grafana/ui';
import React from 'react';
import { Draggable } from 'react-beautiful-dnd';
@@ -11,8 +11,7 @@ import {
QueryBuilderOperationDef,
QueryBuilderOperationParamDef,
} from '../shared/types';
-import { OperationInfoButton } from './OperationInfoButton';
-import { OperationName } from './OperationName';
+import { OperationHeader } from './OperationHeader';
import { getOperationParamEditor } from './OperationParamEditor';
import { getOperationParamId } from './operationUtils';
@@ -121,27 +120,15 @@ export function OperationEditor({
{...provided.draggableProps}
data-testid={`operations.${index}.wrapper`}
>
-
-
-
-
-
-
-
+
{operationElements}
{restParam}
{index < query.operations.length - 1 && (
@@ -205,16 +192,6 @@ const getStyles = (theme: GrafanaTheme2) => {
marginBottom: theme.spacing(1),
position: 'relative',
}),
- header: css({
- borderBottom: `1px solid ${theme.colors.border.medium}`,
- padding: theme.spacing(0.5, 0.5, 0.5, 1),
- gap: theme.spacing(1),
- display: 'flex',
- alignItems: 'center',
- '&:hover .operation-header-show-on-hover': css({
- opacity: 1,
- }),
- }),
infoIcon: css({
color: theme.colors.text.secondary,
}),
@@ -234,12 +211,6 @@ const getStyles = (theme: GrafanaTheme2) => {
verticalAlign: 'middle',
height: '32px',
}),
- operationHeaderButtons: css({
- opacity: 0,
- transition: theme.transitions.create(['opacity'], {
- duration: theme.transitions.duration.short,
- }),
- }),
paramValue: css({
display: 'table-cell',
paddingBottom: theme.spacing(0.5),
diff --git a/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationHeader.tsx b/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationHeader.tsx
new file mode 100644
index 00000000000..0f5e723c76f
--- /dev/null
+++ b/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationHeader.tsx
@@ -0,0 +1,115 @@
+import { css } from '@emotion/css';
+import { GrafanaTheme2, SelectableValue } from '@grafana/data';
+import { FlexItem } from '@grafana/experimental';
+import { Button, Select, useStyles2 } from '@grafana/ui';
+import React, { useState } from 'react';
+import { OperationInfoButton } from './OperationInfoButton';
+import { VisualQueryModeller, QueryBuilderOperation, QueryBuilderOperationDef } from './types';
+
+export interface Props {
+ operation: QueryBuilderOperation;
+ def: QueryBuilderOperationDef;
+ index: number;
+ queryModeller: VisualQueryModeller;
+ dragHandleProps: any;
+ onChange: (index: number, update: QueryBuilderOperation) => void;
+ onRemove: (index: number) => void;
+}
+
+interface State {
+ isOpen?: boolean;
+ alternatives?: Array>;
+}
+
+export const OperationHeader = React.memo(
+ ({ operation, def, index, onChange, onRemove, queryModeller, dragHandleProps }) => {
+ const styles = useStyles2(getStyles);
+ const [state, setState] = useState({});
+
+ const onToggleSwitcher = () => {
+ if (state.isOpen) {
+ setState({ ...state, isOpen: false });
+ } else {
+ const alternatives = queryModeller
+ .getAlternativeOperations(def.alternativesKey!)
+ .map((alt) => ({ label: alt.name, value: alt }));
+ setState({ isOpen: true, alternatives });
+ }
+ };
+
+ return (
+
+ {!state.isOpen && (
+ <>
+
{def.name ?? def.id}
+
+
+
+
+
+ >
+ )}
+ {state.isOpen && (
+
+
+ )}
+
+ );
+ }
+);
+
+OperationHeader.displayName = 'OperationHeader';
+
+const getStyles = (theme: GrafanaTheme2) => {
+ return {
+ header: css({
+ borderBottom: `1px solid ${theme.colors.border.medium}`,
+ padding: theme.spacing(0.5, 0.5, 0.5, 1),
+ display: 'flex',
+ alignItems: 'center',
+ '&:hover .operation-header-show-on-hover': css({
+ opacity: 1,
+ }),
+ }),
+ operationHeaderButtons: css({
+ opacity: 0,
+ transition: theme.transitions.create(['opacity'], {
+ duration: theme.transitions.duration.short,
+ }),
+ }),
+ selectWrapper: css({
+ paddingRight: theme.spacing(2),
+ }),
+ };
+};
diff --git a/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationName.tsx b/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationName.tsx
deleted file mode 100644
index 0b190cfc01e..00000000000
--- a/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationName.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import { css } from '@emotion/css';
-import { GrafanaTheme2, SelectableValue } from '@grafana/data';
-import { Icon, Select, useStyles2 } from '@grafana/ui';
-import React, { useState } from 'react';
-import { VisualQueryModeller, QueryBuilderOperation, QueryBuilderOperationDef } from './types';
-
-export interface Props {
- operation: QueryBuilderOperation;
- def: QueryBuilderOperationDef;
- index: number;
- queryModeller: VisualQueryModeller;
- onChange: (index: number, update: QueryBuilderOperation) => void;
-}
-
-interface State {
- isOpen?: boolean;
- alternatives?: Array>;
-}
-
-export const OperationName = React.memo(({ operation, def, index, onChange, queryModeller }) => {
- const styles = useStyles2(getStyles);
- const [state, setState] = useState({});
-
- const onToggleSwitcher = () => {
- if (state.isOpen) {
- setState({ ...state, isOpen: false });
- } else {
- const alternatives = queryModeller
- .getAlternativeOperations(def.alternativesKey!)
- .map((alt) => ({ label: alt.name, value: alt }));
- setState({ isOpen: true, alternatives });
- }
- };
-
- const nameElement = {def.name ?? def.id};
-
- if (!def.alternativesKey) {
- return nameElement;
- }
-
- return (
- <>
- {!state.isOpen && (
-
- )}
- {state.isOpen && (
-