mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Prometheus: Highlight operations added in the query builder (#47961)
* Highlight newly added operations * Better diff for the operations change * Changed the highlight style
This commit is contained in:
parent
5c3be630f2
commit
ff5aef194c
@ -1,8 +1,8 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { DataSourceApi, GrafanaTheme2 } from '@grafana/data';
|
||||
import { Stack } from '@grafana/experimental';
|
||||
import { Button, Icon, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
import {
|
||||
VisualQueryModeller,
|
||||
@ -24,6 +24,7 @@ export interface Props {
|
||||
onChange: (index: number, update: QueryBuilderOperation) => void;
|
||||
onRemove: (index: number) => void;
|
||||
onRunQuery: () => void;
|
||||
highlight?: boolean;
|
||||
}
|
||||
|
||||
export function OperationEditor({
|
||||
@ -35,9 +36,12 @@ export function OperationEditor({
|
||||
queryModeller,
|
||||
query,
|
||||
datasource,
|
||||
highlight,
|
||||
}: Props) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const def = queryModeller.getOperationDef(operation.id);
|
||||
const shouldHighlight = useHighlight(highlight);
|
||||
|
||||
if (!def) {
|
||||
return <span>Operation {operation.id} not found</span>;
|
||||
}
|
||||
@ -122,7 +126,7 @@ export function OperationEditor({
|
||||
<Draggable draggableId={`operation-${index}`} index={index}>
|
||||
{(provided) => (
|
||||
<div
|
||||
className={styles.card}
|
||||
className={cx(styles.card, shouldHighlight && styles.cardHighlight)}
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
data-testid={`operations.${index}.wrapper`}
|
||||
@ -150,6 +154,29 @@ export function OperationEditor({
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* When highlight is switched on makes sure it is switched of right away, so we just flash the highlight and then fade
|
||||
* out.
|
||||
* @param highlight
|
||||
*/
|
||||
function useHighlight(highlight?: boolean) {
|
||||
const [keepHighlight, setKeepHighlight] = useState(true);
|
||||
useEffect(() => {
|
||||
let t: any;
|
||||
if (highlight) {
|
||||
t = setTimeout(() => {
|
||||
setKeepHighlight(false);
|
||||
}, 1);
|
||||
} else {
|
||||
setKeepHighlight(true);
|
||||
}
|
||||
|
||||
return () => clearTimeout(t);
|
||||
}, [highlight]);
|
||||
|
||||
return keepHighlight && highlight;
|
||||
}
|
||||
|
||||
function renderAddRestParamButton(
|
||||
paramDef: QueryBuilderOperationParamDef,
|
||||
onAddRestParam: () => void,
|
||||
@ -198,6 +225,11 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
borderRadius: theme.shape.borderRadius(1),
|
||||
marginBottom: theme.spacing(1),
|
||||
position: 'relative',
|
||||
transition: 'all 1s ease-in 0s',
|
||||
}),
|
||||
cardHighlight: css({
|
||||
boxShadow: `0px 0px 4px 0px ${theme.colors.primary.border}`,
|
||||
border: `1px solid ${theme.colors.primary.border}`,
|
||||
}),
|
||||
infoIcon: css({
|
||||
marginLeft: theme.spacing(0.5),
|
||||
|
@ -4,6 +4,7 @@ import { Stack } from '@grafana/experimental';
|
||||
import { Button, Cascader, CascaderOption, useStyles2 } from '@grafana/ui';
|
||||
import React, { useState } from 'react';
|
||||
import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd';
|
||||
import { useMountedState, usePrevious } from 'react-use';
|
||||
import { QueryBuilderOperation, QueryWithOperations, VisualQueryModeller } from '../shared/types';
|
||||
import { OperationEditor } from './OperationEditor';
|
||||
|
||||
@ -26,6 +27,8 @@ export function OperationList<T extends QueryWithOperations>({
|
||||
const styles = useStyles2(getStyles);
|
||||
const { operations } = query;
|
||||
|
||||
const opsToHighlight = useOperationsHighlight(operations);
|
||||
|
||||
const [cascaderOpen, setCascaderOpen] = useState(false);
|
||||
|
||||
const onOperationChange = (index: number, update: QueryBuilderOperation) => {
|
||||
@ -86,7 +89,7 @@ export function OperationList<T extends QueryWithOperations>({
|
||||
<div className={styles.operationList} ref={provided.innerRef} {...provided.droppableProps}>
|
||||
{operations.map((op, index) => (
|
||||
<OperationEditor
|
||||
key={index}
|
||||
key={op.id + index}
|
||||
queryModeller={queryModeller}
|
||||
index={index}
|
||||
operation={op}
|
||||
@ -95,6 +98,7 @@ export function OperationList<T extends QueryWithOperations>({
|
||||
onChange={onOperationChange}
|
||||
onRemove={onRemove}
|
||||
onRunQuery={onRunQuery}
|
||||
highlight={opsToHighlight[index]}
|
||||
/>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
@ -125,6 +129,49 @@ export function OperationList<T extends QueryWithOperations>({
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns indexes of operations that should be highlighted. We check the diff of operations added but at the same time
|
||||
* we want to highlight operations only after the initial render, so we check for mounted state and calculate the diff
|
||||
* only after.
|
||||
* @param operations
|
||||
*/
|
||||
function useOperationsHighlight(operations: QueryBuilderOperation[]) {
|
||||
const isMounted = useMountedState();
|
||||
const prevOperations = usePrevious(operations);
|
||||
|
||||
if (!isMounted()) {
|
||||
return operations.map(() => false);
|
||||
}
|
||||
|
||||
if (!prevOperations) {
|
||||
return operations.map(() => true);
|
||||
}
|
||||
|
||||
let newOps: boolean[] = [];
|
||||
|
||||
if (prevOperations.length - 1 === operations.length && operations.every((op) => prevOperations.includes(op))) {
|
||||
// In case we remove one op and does not change any ops then don't highlight anything.
|
||||
return operations.map(() => false);
|
||||
}
|
||||
if (prevOperations.length + 1 === operations.length && prevOperations.every((op) => operations.includes(op))) {
|
||||
// If we add a single op just find it and highlight just that.
|
||||
const newOp = operations.find((op) => !prevOperations.includes(op));
|
||||
newOps = operations.map((op) => {
|
||||
return op === newOp;
|
||||
});
|
||||
} else {
|
||||
// Default diff of all ops.
|
||||
newOps = operations.map((op, index) => {
|
||||
return !isSameOp(op.id, prevOperations[index]?.id);
|
||||
});
|
||||
}
|
||||
return newOps;
|
||||
}
|
||||
|
||||
function isSameOp(op1?: string, op2?: string) {
|
||||
return op1 === op2 || `__${op1}_by` === op2 || op1 === `__${op2}_by`;
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
heading: css({
|
||||
|
Loading…
Reference in New Issue
Block a user