chore: implement splitter + minor changes

This commit is contained in:
Alex Spencer
2025-12-01 15:47:54 -08:00
parent 21b799615f
commit f9e5e30dc2
6 changed files with 89 additions and 123 deletions
@@ -78,6 +78,7 @@ const getStyles = (theme: GrafanaTheme2) => {
display: 'flex',
flexDirection: 'column',
height: '100%',
width: '100%',
background: theme.colors.background.primary,
}),
emptyState: css({
@@ -128,6 +128,7 @@ const getStyles = (theme: GrafanaTheme2) => {
flexDirection: 'column',
gap: theme.spacing(2),
padding: theme.spacing(2),
width: '100%',
}),
expressionContent: css({
display: 'flex',
@@ -1,4 +1,4 @@
import { css } from '@emotion/css';
import { css, cx } from '@emotion/css';
import { useCallback, useMemo, useState } from 'react';
import { DataTransformerConfig, GrafanaTheme2, SelectableValue } from '@grafana/data';
@@ -13,7 +13,7 @@ import {
VizPanel,
SceneDataTransformer,
} from '@grafana/scenes';
import { useStyles2 } from '@grafana/ui';
import { useStyles2, useSplitter } from '@grafana/ui';
import { getConfig } from 'app/core/config';
import { contextSrv } from 'app/core/services/context_srv';
import { getRulesPermissions } from 'app/features/alerting/unified/utils/access-control';
@@ -398,30 +398,34 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
const sourceData = queryRunner?.useState();
const series = sourceData?.data?.series || [];
const { containerProps, primaryProps, secondaryProps, splitterProps } = useSplitter({
direction: 'row',
initialSize: 0.2,
});
return (
<div className={styles.dataPane} data-testid={selectors.components.PanelEditor.DataPane.content}>
<div className={styles.unifiedLayout}>
<div className={styles.splitLayout}>
<div className={styles.leftPanel}>
<QueryTransformList
items={items}
selectedId={effectiveSelectedId}
onSelect={handleSelect}
onAddQuery={handleAddQuery}
onAddTransform={handleAddTransform}
onAddExpression={handleAddExpression}
onDuplicateQuery={handleDuplicateQuery}
onRemoveQuery={handleRemoveQuery}
onToggleQueryVisibility={handleToggleQueryVisibility}
onDuplicateExpression={handleDuplicateExpression}
onRemoveExpression={handleRemoveExpression}
onToggleExpressionVisibility={handleToggleExpressionVisibility}
onRemoveTransform={handleRemoveTransform}
/>
</div>
<div className={styles.rightPanel}>
<DetailView selectedItem={selectedItem} panel={panel} tabs={tabs} />
</div>
<div {...containerProps} className={cx(containerProps.className, styles.unifiedLayout)}>
<div {...primaryProps}>
<QueryTransformList
items={items}
selectedId={effectiveSelectedId}
onSelect={handleSelect}
onAddQuery={handleAddQuery}
onAddTransform={handleAddTransform}
onAddExpression={handleAddExpression}
onDuplicateQuery={handleDuplicateQuery}
onRemoveQuery={handleRemoveQuery}
onToggleQueryVisibility={handleToggleQueryVisibility}
onDuplicateExpression={handleDuplicateExpression}
onRemoveExpression={handleRemoveExpression}
onToggleExpressionVisibility={handleToggleExpressionVisibility}
onRemoveTransform={handleRemoveTransform}
/>
</div>
<div {...splitterProps} className={cx(splitterProps.className, styles.splitter)} />
<div {...secondaryProps}>
<DetailView selectedItem={selectedItem} panel={panel} tabs={tabs} />
</div>
</div>
<TransformationsDrawer
@@ -468,22 +472,18 @@ function getStyles(theme: GrafanaTheme2) {
borderTopRightRadius: theme.shape.radius.default,
overflow: 'hidden',
}),
splitLayout: css({
display: 'flex',
height: '100%',
width: '100%',
}),
leftPanel: css({
width: '25%',
minWidth: '200px',
maxWidth: '400px',
flexShrink: 0,
borderRight: `1px solid ${theme.colors.border.weak}`,
}),
rightPanel: css({
flex: 1,
minWidth: 0,
overflow: 'hidden',
splitter: css({
position: 'relative',
'&::before': {
content: '""',
position: 'absolute',
left: '50%',
top: 0,
bottom: 0,
width: '1px',
background: theme.colors.border.weak,
transform: 'translateX(-50%)',
},
}),
};
}
@@ -207,6 +207,7 @@ const getStyles = (theme: GrafanaTheme2) => {
flexDirection: 'column',
gap: theme.spacing(2),
padding: theme.spacing(2),
width: '100%',
}),
datasourceSection: css({
paddingBottom: theme.spacing(2),
@@ -149,11 +149,11 @@ const getStyles = (theme: GrafanaTheme2) => {
return {
card: css({
cursor: 'pointer',
marginBottom: theme.spacing(1),
border: `1px solid ${theme.colors.border.weak}`,
borderRadius: theme.shape.radius.default,
overflow: 'hidden',
background: theme.colors.background.primary,
width: '100%',
[`&:hover .${actionsClass}`]: {
opacity: 1,
},
@@ -3,7 +3,7 @@ import { memo } from 'react';
import { DataTransformerConfig, GrafanaTheme2 } from '@grafana/data';
import { SceneDataQuery } from '@grafana/scenes';
import { ScrollContainer, useStyles2 } from '@grafana/ui';
import { ScrollContainer, Stack, useStyles2 } from '@grafana/ui';
import { ExpressionQueryType } from 'app/features/expressions/types';
import { AddDataItemMenu } from './AddDataItemMenu';
@@ -50,79 +50,54 @@ export const QueryTransformList = memo(
}: QueryTransformListProps) => {
const styles = useStyles2(getStyles);
const queries = items.filter((item) => item.type === 'query');
const expressions = items.filter((item) => item.type === 'expression');
const transforms = items.filter((item) => item.type === 'transform');
const getHandlers = (item: QueryTransformItem) => {
switch (item.type) {
case 'query':
return {
onDuplicate: onDuplicateQuery ? () => onDuplicateQuery(item.index) : undefined,
onRemove: onRemoveQuery ? () => onRemoveQuery(item.index) : undefined,
onToggleVisibility: onToggleQueryVisibility ? () => onToggleQueryVisibility(item.index) : undefined,
};
case 'expression':
return {
onDuplicate: onDuplicateExpression ? () => onDuplicateExpression(item.index) : undefined,
onRemove: onRemoveExpression ? () => onRemoveExpression(item.index) : undefined,
onToggleVisibility: onToggleExpressionVisibility
? () => onToggleExpressionVisibility(item.index)
: undefined,
};
case 'transform':
return {
onDuplicate: undefined,
onRemove: onRemoveTransform ? () => onRemoveTransform(item.index) : undefined,
onToggleVisibility: undefined,
};
}
};
return (
<div className={styles.container}>
<div className={styles.scrollContainer}>
<ScrollContainer>
<div className={styles.content}>
{queries.length > 0 && (
<div className={styles.section}>
{queries.map((item) => (
<QueryTransformCard
key={item.id}
item={item.data}
type="query"
index={item.index}
isSelected={selectedId === item.id}
onClick={() => onSelect(item.id)}
onDuplicate={onDuplicateQuery ? () => onDuplicateQuery(item.index) : undefined}
onRemove={onRemoveQuery ? () => onRemoveQuery(item.index) : undefined}
onToggleVisibility={
onToggleQueryVisibility ? () => onToggleQueryVisibility(item.index) : undefined
}
/>
))}
</div>
)}
{expressions.length > 0 && (
<div className={styles.section}>
{expressions.map((item) => (
<QueryTransformCard
key={item.id}
item={item.data}
type="expression"
index={item.index}
isSelected={selectedId === item.id}
onClick={() => onSelect(item.id)}
onDuplicate={onDuplicateExpression ? () => onDuplicateExpression(item.index) : undefined}
onRemove={onRemoveExpression ? () => onRemoveExpression(item.index) : undefined}
onToggleVisibility={
onToggleExpressionVisibility ? () => onToggleExpressionVisibility(item.index) : undefined
}
/>
))}
</div>
)}
{transforms.length > 0 && (
<div className={styles.section}>
{transforms.map((item) => (
<QueryTransformCard
key={item.id}
item={item.data}
type="transform"
index={item.index}
isSelected={selectedId === item.id}
onClick={() => onSelect(item.id)}
onRemove={onRemoveTransform ? () => onRemoveTransform(item.index) : undefined}
/>
))}
</div>
)}
<div className={styles.addButton}>
<AddDataItemMenu
onAddQuery={onAddQuery}
onAddTransform={onAddTransform}
onAddExpression={onAddExpression}
<Stack direction="column" gap={2}>
{items.map((item) => (
<QueryTransformCard
key={item.id}
item={item.data}
type={item.type}
index={item.index}
isSelected={selectedId === item.id}
onClick={() => onSelect(item.id)}
{...getHandlers(item)}
/>
</div>
</div>
))}
<AddDataItemMenu
onAddQuery={onAddQuery}
onAddTransform={onAddTransform}
onAddExpression={onAddExpression}
/>
</Stack>
</ScrollContainer>
</div>
</div>
@@ -138,26 +113,14 @@ const getStyles = (theme: GrafanaTheme2) => {
display: 'flex',
flexDirection: 'column',
height: '100%',
background: theme.colors.background.primary,
borderRight: `1px solid ${theme.colors.border.weak}`,
width: '100%',
overflow: 'hidden',
}),
scrollContainer: css({
padding: theme.spacing(2),
flex: 1,
minHeight: 0,
overflow: 'auto',
}),
content: css({
padding: theme.spacing(2),
}),
section: css({
'&:not(:first-child)': {
marginTop: theme.spacing(2),
},
}),
addButton: css({
marginTop: theme.spacing(2),
paddingTop: theme.spacing(2),
}),
};
};