add transformation menu

This commit is contained in:
Develer
2025-12-03 15:09:06 +01:00
parent 2ef57dfa4a
commit b085f3ccfd
3 changed files with 123 additions and 50 deletions
@@ -18,67 +18,76 @@ interface DetailViewProps {
selectedItem: QueryTransformItem | undefined;
panel: VizPanel;
tabs: Array<{ tabId: TabId }>;
onRemoveTransform?: (index: number) => void;
onToggleTransformVisibility?: (index: number) => void;
}
export const DetailView = memo(({ selectedItem, panel, tabs }: DetailViewProps) => {
const styles = useStyles2(getStyles);
export const DetailView = memo(
({ selectedItem, panel, tabs, onRemoveTransform, onToggleTransformVisibility }: DetailViewProps) => {
const styles = useStyles2(getStyles);
const renderContent = useCallback(() => {
if (!selectedItem) {
return (
<div className={styles.emptyState}>
<p>
<Trans i18nKey="dashboard-scene.panel-data-pane.empty-state">
Select a query or transformation to edit
</Trans>
</p>
</div>
);
}
const renderContent = useCallback(() => {
if (!selectedItem) {
return (
<div className={styles.emptyState}>
<p>
<Trans i18nKey="dashboard-scene.panel-data-pane.empty-state">
Select a query or transformation to edit
</Trans>
</p>
</div>
);
}
if (selectedItem.type === 'query' && 'refId' in selectedItem.data) {
const query = selectedItem.data;
return (
<>
<DetailViewHeader selectedItem={selectedItem} panel={panel} />
<ScrollContainer>
<QueryDetailView panel={panel} query={query} queryIndex={selectedItem.index} />
</ScrollContainer>
</>
);
} else if (selectedItem.type === 'expression' && 'refId' in selectedItem.data) {
const data = selectedItem.data;
if (isExpressionQuery(data)) {
if (selectedItem.type === 'query' && 'refId' in selectedItem.data) {
const query = selectedItem.data;
return (
<>
<DetailViewHeader selectedItem={selectedItem} panel={panel} />
<ScrollContainer>
<ExpressionDetailView panel={panel} expression={data} expressionIndex={selectedItem.index} />
<QueryDetailView panel={panel} query={query} queryIndex={selectedItem.index} />
</ScrollContainer>
</>
);
} else if (selectedItem.type === 'expression' && 'refId' in selectedItem.data) {
const data = selectedItem.data;
if (isExpressionQuery(data)) {
return (
<>
<DetailViewHeader selectedItem={selectedItem} panel={panel} />
<ScrollContainer>
<ExpressionDetailView panel={panel} expression={data} expressionIndex={selectedItem.index} />
</ScrollContainer>
</>
);
}
} else {
const transformsTab = tabs.find((t): t is PanelDataTransformationsTab => t.tabId === TabId.Transformations);
if (transformsTab && 'id' in selectedItem.data) {
return (
<>
<DetailViewHeader
selectedItem={selectedItem}
panel={panel}
onRemoveTransform={onRemoveTransform}
onToggleTransformVisibility={onToggleTransformVisibility}
/>
<ScrollContainer>
<Container>
<PanelDataTransformationsTabRendered model={transformsTab} selectedIdx={selectedItem.index} />
</Container>
</ScrollContainer>
</>
);
}
}
} else {
const transformsTab = tabs.find((t): t is PanelDataTransformationsTab => t.tabId === TabId.Transformations);
if (transformsTab && 'id' in selectedItem.data) {
return (
<>
<DetailViewHeader selectedItem={selectedItem} panel={panel} />
<ScrollContainer>
<Container>
<PanelDataTransformationsTabRendered model={transformsTab} selectedIdx={selectedItem.index} />
</Container>
</ScrollContainer>
</>
);
}
}
return null;
}, [selectedItem, panel, tabs, styles.emptyState]);
return null;
}, [selectedItem, panel, tabs, styles.emptyState, onRemoveTransform, onToggleTransformVisibility]);
return <div className={styles.container}>{renderContent()}</div>;
});
return <div className={styles.container}>{renderContent()}</div>;
}
);
DetailView.displayName = 'DetailView';
@@ -32,6 +32,8 @@ import { QueryTransformItem } from './QueryTransformList';
interface DetailViewHeaderProps {
selectedItem: QueryTransformItem;
panel: VizPanel;
onRemoveTransform?: (index: number) => void;
onToggleTransformVisibility?: (index: number) => void;
}
const ITEM_CONFIG = (theme: GrafanaTheme2) => ({
@@ -49,7 +51,12 @@ const ITEM_CONFIG = (theme: GrafanaTheme2) => ({
},
});
export const DetailViewHeader = ({ selectedItem, panel }: DetailViewHeaderProps) => {
export const DetailViewHeader = ({
selectedItem,
panel,
onRemoveTransform,
onToggleTransformVisibility,
}: DetailViewHeaderProps) => {
const theme = useTheme2();
const config = useMemo(() => ITEM_CONFIG(theme)[selectedItem.type], [theme, selectedItem.type]);
const styles = useStyles2(getStyles, config);
@@ -243,12 +250,30 @@ export const DetailViewHeader = ({ selectedItem, panel }: DetailViewHeaderProps)
queryRunner?.runQueries();
}, [panel]);
// Transformation action handlers
const onRemoveTransformation = useCallback(() => {
if (selectedItem.type !== 'transform' || selectedItem.index === undefined) {
return;
}
onRemoveTransform?.(selectedItem.index);
}, [selectedItem, onRemoveTransform]);
const onToggleTransformationVisibility = useCallback(() => {
if (selectedItem.type !== 'transform' || selectedItem.index === undefined) {
return;
}
onToggleTransformVisibility?.(selectedItem.index);
}, [selectedItem, onToggleTransformVisibility]);
const refId = 'refId' in selectedItem.data ? selectedItem.data.refId : '';
const isHidden =
(selectedItem.type === 'query' || selectedItem.type === 'expression') &&
'hide' in selectedItem.data &&
selectedItem.data.hide;
const isTransformDisabled =
selectedItem.type === 'transform' && 'disabled' in selectedItem.data && selectedItem.data.disabled;
// Get transformation display name
const transformationName = useMemo(() => {
if (selectedItem.type === 'transform' && 'id' in selectedItem.data) {
@@ -314,7 +339,7 @@ export const DetailViewHeader = ({ selectedItem, panel }: DetailViewHeaderProps)
)}
</Stack>
{/* Right side: Run Query + Actions Menu */}
{/* Right side: Run Query + Actions Menu for queries/expressions */}
{(selectedItem.type === 'query' || selectedItem.type === 'expression') && (
<Stack gap={0.5} alignItems="center">
<Button
@@ -361,6 +386,39 @@ export const DetailViewHeader = ({ selectedItem, panel }: DetailViewHeaderProps)
</Dropdown>
</Stack>
)}
{/* Right side: Actions Menu for transformations */}
{selectedItem.type === 'transform' && (
<Stack gap={0.5} alignItems="center">
<Dropdown
overlay={
<Menu>
<Menu.Item
label={
isTransformDisabled
? t('dashboard-scene.detail-view-header.enable-transform', 'Enable')
: t('dashboard-scene.detail-view-header.disable-transform', 'Disable')
}
icon={isTransformDisabled ? 'eye' : 'eye-slash'}
onClick={onToggleTransformationVisibility}
/>
<Menu.Divider />
<Menu.Item
label={t('dashboard-scene.detail-view-header.remove-transform', 'Remove')}
icon="trash-alt"
onClick={onRemoveTransformation}
/>
</Menu>
}
>
<IconButton
name="ellipsis-v"
variant="secondary"
tooltip={t('dashboard-scene.detail-view-header.actions', 'Actions')}
/>
</Dropdown>
</Stack>
)}
</div>
</div>
);
@@ -501,7 +501,13 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
style={{ ...splitterProps.style, width: '16px' }}
/>
<div {...secondaryProps}>
<DetailView selectedItem={selectedItem} panel={panel} tabs={tabs} />
<DetailView
selectedItem={selectedItem}
panel={panel}
tabs={tabs}
onRemoveTransform={handleRemoveTransform}
onToggleTransformVisibility={handleToggleTransformVisibility}
/>
</div>
</div>
<TransformationsDrawer