chore: alex - fix transformation enabled/disabled, sections!
This commit is contained in:
@@ -137,13 +137,16 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
const dataTransformer = panel.state.$data instanceof SceneDataTransformer ? panel.state.$data : null;
|
||||
const transformerState = dataTransformer?.useState();
|
||||
|
||||
// Build combined items list
|
||||
const items: QueryTransformItem[] = useMemo(() => {
|
||||
const result: QueryTransformItem[] = [];
|
||||
// Build separate lists for queries/expressions and transformations
|
||||
const { dataSourceItems, transformItems, allItems } = useMemo(() => {
|
||||
const dataSourceItems: QueryTransformItem[] = [];
|
||||
const transformItems: QueryTransformItem[] = [];
|
||||
|
||||
// Add queries and expressions
|
||||
const queries = queryRunnerState?.queries;
|
||||
for (let i = 0; i < (queries?.length ?? 0); i++) {
|
||||
const query = queries![i];
|
||||
result.push({
|
||||
dataSourceItems.push({
|
||||
id: queryItemId(query),
|
||||
type: isExpressionQuery(query) ? 'expression' : 'query',
|
||||
data: query,
|
||||
@@ -151,11 +154,12 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
});
|
||||
}
|
||||
|
||||
// Add transformations
|
||||
const transformations = transformerState?.transformations;
|
||||
for (let i = 0; i < (transformations?.length ?? 0); i++) {
|
||||
const transform = transformations![i];
|
||||
if (isDataTransformerConfig(transform)) {
|
||||
result.push({
|
||||
transformItems.push({
|
||||
id: transformItemId(i),
|
||||
type: 'transform',
|
||||
data: transform,
|
||||
@@ -164,20 +168,24 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return {
|
||||
dataSourceItems,
|
||||
transformItems,
|
||||
allItems: [...dataSourceItems, ...transformItems],
|
||||
};
|
||||
}, [queryRunnerState?.queries, transformerState?.transformations]);
|
||||
|
||||
// Auto-select first item if nothing is selected
|
||||
const effectiveSelectedId = useMemo(() => {
|
||||
if (selectedId === null && items.length > 0) {
|
||||
return items[0].id;
|
||||
if (selectedId === null && allItems.length > 0) {
|
||||
return allItems[0].id;
|
||||
}
|
||||
return selectedId;
|
||||
}, [selectedId, items]);
|
||||
}, [selectedId, allItems]);
|
||||
|
||||
const selectedItem = useMemo(() => {
|
||||
return items.find((item) => item.id === effectiveSelectedId);
|
||||
}, [items, effectiveSelectedId]);
|
||||
return allItems.find((item) => item.id === effectiveSelectedId);
|
||||
}, [allItems, effectiveSelectedId]);
|
||||
|
||||
const handleSelect = useCallback((id: string) => {
|
||||
setSelectedId(id);
|
||||
@@ -406,6 +414,22 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
[tabs, selectedId]
|
||||
);
|
||||
|
||||
const handleToggleTransformVisibility = useCallback(
|
||||
(index: number) => {
|
||||
const transformsTab = tabs.find((t): t is PanelDataTransformationsTab => t.tabId === TabId.Transformations);
|
||||
if (transformsTab) {
|
||||
const transformations = (transformsTab.getDataTransformer().state.transformations || []).filter(
|
||||
isDataTransformerConfig
|
||||
);
|
||||
const newTransformations = transformations.map((t, i) =>
|
||||
i === index ? { ...t, disabled: t.disabled ? undefined : true } : t
|
||||
);
|
||||
transformsTab.onChangeTransformations(newTransformations);
|
||||
}
|
||||
},
|
||||
[tabs]
|
||||
);
|
||||
|
||||
// Get data for transformations drawer
|
||||
const sourceData = queryRunner?.useState();
|
||||
const series = sourceData?.data?.series || [];
|
||||
@@ -421,7 +445,9 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
<div {...containerProps} className={cx(containerProps.className, styles.unifiedLayout)}>
|
||||
<div {...primaryProps} className={cx(primaryProps.className, styles.leftPane)}>
|
||||
<QueryTransformList
|
||||
items={items}
|
||||
allItems={allItems}
|
||||
dataSourceItems={dataSourceItems}
|
||||
transformItems={transformItems}
|
||||
selectedId={effectiveSelectedId}
|
||||
onSelect={handleSelect}
|
||||
onAddQuery={handleAddQuery}
|
||||
@@ -435,6 +461,7 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
onRemoveExpression={handleRemoveExpression}
|
||||
onToggleExpressionVisibility={handleToggleExpressionVisibility}
|
||||
onRemoveTransform={handleRemoveTransform}
|
||||
onToggleTransformVisibility={handleToggleTransformVisibility}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
@@ -470,9 +497,9 @@ export function shouldShowAlertingTab(pluginId: string) {
|
||||
return isGraph || isTimeseries;
|
||||
}
|
||||
|
||||
// Left pane sizing: card width + padding (16px left + 48px right = 64px)
|
||||
const LEFT_PANE_MIN = 180 + 64; // 244px (180px card + padding)
|
||||
const LEFT_PANE_MAX = 240 + 64; // 304px (240px card + padding)
|
||||
// Left pane sizing: cards grow from 180px-300px, plus content padding (32px + 48px = 80px)
|
||||
const LEFT_PANE_MIN = 180 + 80; // 260px (180px card min + 80px padding)
|
||||
const LEFT_PANE_MAX = 300 + 80; // 380px (300px card max + 80px padding)
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
@@ -496,9 +523,9 @@ function getStyles(theme: GrafanaTheme2) {
|
||||
cursor: 'col-resize',
|
||||
}),
|
||||
leftPane: css({
|
||||
// !important to override useSplitter's inline styles
|
||||
// !important on minWidth to override useSplitter's inline minWidth: 'min-content'
|
||||
minWidth: `${LEFT_PANE_MIN}px !important`,
|
||||
maxWidth: `${LEFT_PANE_MAX}px !important`,
|
||||
maxWidth: `${LEFT_PANE_MAX}px`,
|
||||
overflow: 'hidden',
|
||||
}),
|
||||
};
|
||||
|
||||
+11
-5
@@ -48,7 +48,9 @@ export const QueryTransformCard = memo(
|
||||
return undefined;
|
||||
}, [type, item]);
|
||||
|
||||
const isHidden = (type === 'query' || type === 'expression') && 'hide' in item && item.hide;
|
||||
const isHidden =
|
||||
((type === 'query' || type === 'expression') && 'hide' in item && item.hide) ||
|
||||
(type === 'transform' && 'disabled' in item && item.disabled);
|
||||
const icon = type === 'query' ? 'database' : type === 'expression' ? 'code' : 'pivot';
|
||||
const typeLabel = type === 'query' ? 'Query' : type === 'expression' ? 'Expression' : 'Transformation';
|
||||
const name = getName();
|
||||
@@ -90,15 +92,19 @@ export const QueryTransformCard = memo(
|
||||
<span className={styles.typeLabel}>{typeLabel}</span>
|
||||
</div>
|
||||
<Stack gap={0.25}>
|
||||
{(type === 'query' || type === 'expression') && onToggleVisibility && (
|
||||
{onToggleVisibility && (
|
||||
<IconButton
|
||||
name={isHidden ? 'eye-slash' : 'eye'}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
tooltip={
|
||||
isHidden
|
||||
? t('dashboard-scene.query-transform-card.show-response', 'Show response')
|
||||
: t('dashboard-scene.query-transform-card.hide-response', 'Hide response')
|
||||
? type === 'transform'
|
||||
? t('dashboard-scene.query-transform-card.enable-transform', 'Enable transformation')
|
||||
: t('dashboard-scene.query-transform-card.show-response', 'Show response')
|
||||
: type === 'transform'
|
||||
? t('dashboard-scene.query-transform-card.disable-transform', 'Disable transformation')
|
||||
: t('dashboard-scene.query-transform-card.hide-response', 'Hide response')
|
||||
}
|
||||
onClick={(e) => handleAction(e, onToggleVisibility)}
|
||||
className={styles.actionButton}
|
||||
@@ -161,7 +167,7 @@ const getStyles = (theme: GrafanaTheme2, colors: ReturnType<typeof usePanelDataP
|
||||
background: theme.colors.background.secondary,
|
||||
width: '100%',
|
||||
minWidth: 180,
|
||||
maxWidth: 240,
|
||||
maxWidth: 300,
|
||||
'&:hover': {
|
||||
borderColor: theme.colors.border.strong,
|
||||
},
|
||||
|
||||
+79
-27
@@ -19,7 +19,9 @@ export interface QueryTransformItem {
|
||||
}
|
||||
|
||||
interface QueryTransformListProps {
|
||||
items: QueryTransformItem[];
|
||||
allItems: QueryTransformItem[];
|
||||
dataSourceItems: QueryTransformItem[];
|
||||
transformItems: QueryTransformItem[];
|
||||
selectedId: string | null;
|
||||
onSelect: (id: string) => void;
|
||||
onAddQuery: () => void;
|
||||
@@ -32,11 +34,14 @@ interface QueryTransformListProps {
|
||||
onRemoveExpression?: (index: number) => void;
|
||||
onToggleExpressionVisibility?: (index: number) => void;
|
||||
onRemoveTransform?: (index: number) => void;
|
||||
onToggleTransformVisibility?: (index: number) => void;
|
||||
}
|
||||
|
||||
export const QueryTransformList = memo(
|
||||
({
|
||||
items,
|
||||
dataSourceItems,
|
||||
transformItems,
|
||||
allItems,
|
||||
selectedId,
|
||||
onSelect,
|
||||
onAddQuery,
|
||||
@@ -49,13 +54,14 @@ export const QueryTransformList = memo(
|
||||
onRemoveExpression,
|
||||
onToggleExpressionVisibility,
|
||||
onRemoveTransform,
|
||||
onToggleTransformVisibility,
|
||||
}: QueryTransformListProps) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const allConnections = useMemo(() => {
|
||||
const conns: Array<{ from: string; to: string }> = [];
|
||||
|
||||
items.forEach((item) => {
|
||||
allItems.forEach((item) => {
|
||||
if (item.type === 'expression' && 'expression' in item.data && 'refId' in item.data) {
|
||||
const expr = item.data;
|
||||
|
||||
@@ -78,7 +84,7 @@ export const QueryTransformList = memo(
|
||||
});
|
||||
|
||||
return conns;
|
||||
}, [items]);
|
||||
}, [allItems]);
|
||||
|
||||
// Filter connections to only show for selected card
|
||||
const visibleConnections = useMemo(() => {
|
||||
@@ -87,7 +93,7 @@ export const QueryTransformList = memo(
|
||||
}
|
||||
|
||||
// Find the item to get its refId
|
||||
const activeItem = items.find((item) => item.id === selectedId);
|
||||
const activeItem = allItems.find((item) => item.id === selectedId);
|
||||
if (!activeItem || !('refId' in activeItem.data)) {
|
||||
return [];
|
||||
}
|
||||
@@ -96,7 +102,7 @@ export const QueryTransformList = memo(
|
||||
|
||||
// Show connections where this card is involved (either as source or destination)
|
||||
return allConnections.filter((conn) => conn.from === activeRefId || conn.to === activeRefId);
|
||||
}, [allConnections, selectedId, items]);
|
||||
}, [allConnections, selectedId, allItems]);
|
||||
|
||||
const getHandlers = (item: QueryTransformItem) => {
|
||||
switch (item.type) {
|
||||
@@ -118,23 +124,24 @@ export const QueryTransformList = memo(
|
||||
return {
|
||||
onDuplicate: undefined,
|
||||
onRemove: onRemoveTransform ? () => onRemoveTransform(item.index) : undefined,
|
||||
onToggleVisibility: undefined,
|
||||
onToggleVisibility: onToggleTransformVisibility ? () => onToggleTransformVisibility(item.index) : undefined,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const stats = useMemo(() => {
|
||||
const totalCards = items.length;
|
||||
const queries = items.filter((item) => item.type === 'query' || item.type === 'expression');
|
||||
const hiddenQueries = queries.filter((item) => 'hide' in item.data && item.data.hide);
|
||||
const visibleQueries = queries.length - hiddenQueries.length;
|
||||
const totalCards = allItems.length;
|
||||
const hiddenQueries = dataSourceItems.filter((item) => 'hide' in item.data && item.data.hide);
|
||||
const disabledTransforms = transformItems.filter((item) => 'disabled' in item.data && item.data.disabled);
|
||||
const hiddenTotal = hiddenQueries.length + disabledTransforms.length;
|
||||
const visibleTotal = totalCards - hiddenTotal;
|
||||
|
||||
return {
|
||||
totalCards,
|
||||
visibleQueries,
|
||||
hiddenQueries: hiddenQueries.length,
|
||||
visibleQueries: visibleTotal,
|
||||
hiddenQueries: hiddenTotal,
|
||||
};
|
||||
}, [items]);
|
||||
}, [allItems, dataSourceItems, transformItems]);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
@@ -149,18 +156,46 @@ export const QueryTransformList = memo(
|
||||
<div className={styles.scrollWrapper}>
|
||||
<ScrollContainer data-scrollcontainer>
|
||||
<div className={styles.content}>
|
||||
<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)}
|
||||
/>
|
||||
))}
|
||||
<Stack direction="column" gap={3}>
|
||||
{/* Data Sources Section (Queries + Expressions) */}
|
||||
{dataSourceItems.length > 0 && (
|
||||
<Stack direction="column" gap={2}>
|
||||
<div className={styles.sectionLabel}>
|
||||
{t('dashboard-scene.query-transform-list.queries-expressions', 'Queries & Expressions')}
|
||||
</div>
|
||||
{dataSourceItems.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)}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{/* Transformations Section */}
|
||||
{transformItems.length > 0 && (
|
||||
<Stack direction="column" gap={2}>
|
||||
<div className={styles.sectionLabel}>
|
||||
{t('dashboard-scene.query-transform-list.transformations', 'Transformations')}
|
||||
</div>
|
||||
{transformItems.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)}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<AddDataItemMenu
|
||||
onAddQuery={onAddQuery}
|
||||
@@ -226,13 +261,30 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
textTransform: 'uppercase',
|
||||
color: theme.colors.text.primary,
|
||||
}),
|
||||
sectionLabel: css({
|
||||
fontFamily: "'CommitMono', monospace",
|
||||
fontSize: theme.typography.bodySmall.fontSize,
|
||||
color: theme.colors.text.maxContrast,
|
||||
textTransform: 'uppercase',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(1),
|
||||
marginLeft: theme.spacing(-2),
|
||||
marginRight: theme.spacing(-2),
|
||||
'&::before, &::after': {
|
||||
content: '""',
|
||||
flex: 1,
|
||||
height: '1px',
|
||||
background: theme.colors.border.weak,
|
||||
},
|
||||
}),
|
||||
scrollWrapper: css({
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
position: 'relative',
|
||||
}),
|
||||
content: css({
|
||||
padding: theme.spacing(2),
|
||||
padding: theme.spacing(4),
|
||||
paddingRight: theme.spacing(6),
|
||||
}),
|
||||
footer: css({
|
||||
|
||||
Reference in New Issue
Block a user