diff --git a/public/app/features/explore/ContentOutline/ContentOutlineItemButton.tsx b/public/app/features/explore/ContentOutline/ContentOutlineItemButton.tsx
index febd0e6d009..6d47db2766b 100644
--- a/public/app/features/explore/ContentOutline/ContentOutlineItemButton.tsx
+++ b/public/app/features/explore/ContentOutline/ContentOutlineItemButton.tsx
@@ -1,46 +1,90 @@
import { cx, css } from '@emotion/css';
-import React, { ButtonHTMLAttributes } from 'react';
+import React, { ButtonHTMLAttributes, useEffect, useRef, useState } from 'react';
import { IconName, isIconName, GrafanaTheme2 } from '@grafana/data';
import { Icon, useStyles2, Tooltip } from '@grafana/ui';
+import { TooltipPlacement } from '@grafana/ui/src/components/Tooltip';
type CommonProps = {
+ contentOutlineExpanded?: boolean;
title?: string;
- icon: string;
+ icon?: IconName | React.ReactNode;
tooltip?: string;
+ tooltipPlacement?: TooltipPlacement;
className?: string;
+ indentStyle?: string;
+ collapsible?: boolean;
+ collapsed?: boolean;
isActive?: boolean;
+ sectionId?: string;
+ toggleCollapsed?: () => void;
};
export type ContentOutlineItemButtonProps = CommonProps & ButtonHTMLAttributes
;
export function ContentOutlineItemButton({
+ contentOutlineExpanded,
title,
icon,
tooltip,
+ tooltipPlacement = 'bottom',
className,
+ indentStyle,
+ collapsible,
+ collapsed,
isActive,
+ sectionId,
+ toggleCollapsed,
...rest
}: ContentOutlineItemButtonProps) {
const styles = useStyles2(getStyles);
const buttonStyles = cx(styles.button, className);
+ const textRef = useRef(null);
+ const [isOverflowing, setIsOverflowing] = useState(false);
+
+ useEffect(() => {
+ if (textRef.current) {
+ setIsOverflowing(textRef.current?.scrollWidth > textRef.current?.clientWidth);
+ }
+ }, [title]);
+
const body = (
-
+
+ {collapsible && (
+
+ )}
+
+
);
- return tooltip ? (
-
+ // if there's a tooltip we want to show it if the text is overflowing
+ const showTooltip = tooltip && (!contentOutlineExpanded || isOverflowing);
+
+ return showTooltip ? (
+
{body}
) : (
@@ -48,13 +92,13 @@ export function ContentOutlineItemButton({
);
}
-function renderIcon(icon: IconName | React.ReactNode) {
+function OutlineIcon({ icon }: { icon: IconName | React.ReactNode }) {
if (!icon) {
return null;
}
if (isIconName(icon)) {
- return ;
+ return ;
}
return icon;
@@ -62,26 +106,48 @@ function renderIcon(icon: IconName | React.ReactNode) {
const getStyles = (theme: GrafanaTheme2) => {
return {
+ buttonContainer: css({
+ position: 'relative',
+ display: 'flex',
+ alignItems: 'center',
+ flexGrow: 1,
+ gap: theme.spacing(1),
+ overflow: 'hidden',
+ width: '100%',
+ }),
button: css({
label: 'content-outline-item-button',
display: 'flex',
- flexGrow: 1,
alignItems: 'center',
height: theme.spacing(theme.components.height.md),
padding: theme.spacing(0, 1),
gap: theme.spacing(1),
color: theme.colors.text.secondary,
+ width: '100%',
background: 'transparent',
border: 'none',
- textOverflow: 'ellipsis',
- overflow: 'hidden',
- whiteSpace: 'nowrap',
+ }),
+ collapseButton: css({
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ width: theme.spacing(3),
+ height: theme.spacing(4),
+ borderRadius: theme.shape.radius.default,
+ color: theme.colors.text.secondary,
+ background: 'transparent',
+ border: 'none',
+
'&:hover': {
color: theme.colors.text.primary,
- background: theme.colors.background.secondary,
- textDecoration: 'underline',
+ background: theme.colors.secondary.shade,
},
}),
+ textContainer: css({
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ }),
active: css({
backgroundColor: theme.colors.background.secondary,
borderTopRightRadius: theme.shape.radius.default,
diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx
index 4d27d12d865..da1c8540c82 100644
--- a/public/app/features/explore/Explore.tsx
+++ b/public/app/features/explore/Explore.tsx
@@ -93,9 +93,6 @@ const getStyles = (theme: GrafanaTheme2) => {
paddingRight: theme.spacing(2),
marginBottom: theme.spacing(2),
}),
- left: css({
- marginBottom: theme.spacing(2),
- }),
wrapper: css({
position: 'absolute',
top: 0,
@@ -370,7 +367,7 @@ export class Explore extends React.PureComponent {
return Object.entries(groupedByPlugin).map(([pluginId, frames], index) => {
return (
-
+
{
const { graphResult, absoluteRange, timeZone, queryResponse, showFlameGraph } = this.props;
return (
-
+
{
renderTablePanel(width: number) {
const { exploreId, timeZone } = this.props;
return (
-
+
{
renderRawPrometheus(width: number) {
const { exploreId, datasourceInstance, timeZone } = this.props;
return (
-
+
{
gap: theme.spacing(1),
});
return (
-
+
{
const { logsSample, timeZone, setSupplementaryQueryEnabled, exploreId, datasourceInstance, queries } = this.props;
return (
-
+
{
const datasourceType = datasourceInstance ? datasourceInstance?.type : 'unknown';
return (
-
+
{
renderFlameGraphPanel() {
const { queryResponse } = this.props;
return (
-
+
);
@@ -526,7 +523,7 @@ export class Explore extends React.PureComponent {
return (
// If there is no data (like 404) we show a separate error so no need to show anything here
dataFrames.length && (
-
+
{
}
return (
-
+
{
>
{contentOutlineVisible && (
-
-
-
+
)}
{
{datasourceInstance ? (
<>
-
+
{correlationsBox}
diff --git a/public/app/features/explore/QueryRows.tsx b/public/app/features/explore/QueryRows.tsx
index e90a09bb270..6911ca3a49b 100644
--- a/public/app/features/explore/QueryRows.tsx
+++ b/public/app/features/explore/QueryRows.tsx
@@ -10,6 +10,7 @@ import { useDispatch, useSelector } from 'app/types';
import { getDatasourceSrv } from '../plugins/datasource_srv';
import { QueryEditorRows } from '../query/components/QueryEditorRows';
+import { ContentOutlineItem } from './ContentOutline/ContentOutlineItem';
import { changeQueries, runQueries } from './state/query';
import { getExploreItemSelector } from './state/selectors';
@@ -88,6 +89,18 @@ export const QueryRows = ({ exploreId }: Props) => {
app={CoreApp.Explore}
history={history}
eventBus={eventBridge}
+ queryRowWrapper={(children, refId) => (
+
+ {children}
+
+ )}
/>
);
};
diff --git a/public/app/features/query/components/QueryEditorRows.tsx b/public/app/features/query/components/QueryEditorRows.tsx
index 17297eef12a..9399178d42a 100644
--- a/public/app/features/query/components/QueryEditorRows.tsx
+++ b/public/app/features/query/components/QueryEditorRows.tsx
@@ -1,4 +1,4 @@
-import React, { PureComponent } from 'react';
+import React, { PureComponent, ReactNode } from 'react';
import { DragDropContext, DragStart, Droppable, DropResult } from 'react-beautiful-dnd';
import {
@@ -34,6 +34,7 @@ export interface Props {
onQueryCopied?: () => void;
onQueryRemoved?: () => void;
onQueryToggled?: (queryStatus?: boolean | undefined) => void;
+ queryRowWrapper?: (children: ReactNode, refId: string) => ReactNode;
}
export class QueryEditorRows extends PureComponent {
@@ -144,6 +145,7 @@ export class QueryEditorRows extends PureComponent {
onQueryCopied,
onQueryRemoved,
onQueryToggled,
+ queryRowWrapper,
} = this.props;
return (
@@ -158,7 +160,7 @@ export class QueryEditorRows extends PureComponent {
? (settings: DataSourceInstanceSettings) => this.onDataSourceChange(settings, index)
: undefined;
- return (
+ const queryEditorRow = (
{
eventBus={eventBus}
/>
);
+
+ return queryRowWrapper ? queryRowWrapper(queryEditorRow, query.refId) : queryEditorRow;
})}
{provided.placeholder}