Transformations: Add applicability indicators to empty state
This commit is contained in:
+13
-1
@@ -1,6 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { DataTransformerID, standardTransformersRegistry, TransformerRegistryItem } from '@grafana/data';
|
||||
import { DataFrame, DataTransformerID, standardTransformersRegistry, TransformerRegistryItem } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { t, Trans } from '@grafana/i18n';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
@@ -16,6 +16,8 @@ interface EmptyTransformationsProps {
|
||||
onShowPicker: () => void;
|
||||
onGoToQueries?: () => void;
|
||||
onAddTransformation?: (transformationId: string) => void;
|
||||
data?: DataFrame[];
|
||||
isSqlApplicable?: boolean;
|
||||
}
|
||||
|
||||
const TRANSFORMATION_IDS = [
|
||||
@@ -60,6 +62,7 @@ export function LegacyEmptyTransformationsMessage({ onShowPicker }: { onShowPick
|
||||
export function NewEmptyTransformationsMessage(props: EmptyTransformationsProps) {
|
||||
const hasGoToQueries = props.onGoToQueries != null;
|
||||
const hasAddTransformation = props.onAddTransformation != null;
|
||||
const isSqlApplicable = props.isSqlApplicable !== false; // Default to true if not provided
|
||||
|
||||
// Get transformations from registry
|
||||
const transformations = useMemo(() => {
|
||||
@@ -69,6 +72,9 @@ export function NewEmptyTransformationsMessage(props: EmptyTransformationsProps)
|
||||
}, []);
|
||||
|
||||
const handleSqlTransformationClick = () => {
|
||||
if (!isSqlApplicable) {
|
||||
return;
|
||||
}
|
||||
reportInteraction('dashboards_expression_interaction', {
|
||||
action: 'add_expression',
|
||||
expression_type: 'sql',
|
||||
@@ -110,6 +116,11 @@ export function NewEmptyTransformationsMessage(props: EmptyTransformationsProps)
|
||||
imageUrl={config.theme2.isDark ? sqlDarkImage : sqlLightImage}
|
||||
onClick={handleSqlTransformationClick}
|
||||
testId="go-to-queries-button"
|
||||
isDisabled={!isSqlApplicable}
|
||||
disabledTooltip={t(
|
||||
'dashboard-scene.empty-transformations-message.sql-not-applicable',
|
||||
'SQL expressions require backend data sources'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{hasAddTransformation &&
|
||||
@@ -121,6 +132,7 @@ export function NewEmptyTransformationsMessage(props: EmptyTransformationsProps)
|
||||
showIllustrations={true}
|
||||
showPluginState={false}
|
||||
showTags={false}
|
||||
data={props.data}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
+33
@@ -5,6 +5,7 @@ import { useCallback, useMemo, useState } from 'react';
|
||||
import { DataTransformerConfig, GrafanaTheme2, PanelData } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import {
|
||||
SceneObjectBase,
|
||||
SceneComponentProps,
|
||||
@@ -18,6 +19,7 @@ import { Button, ButtonGroup, ConfirmModal, Tab, useStyles2 } from '@grafana/ui'
|
||||
import { TransformationOperationRows } from 'app/features/dashboard/components/TransformationsEditor/TransformationOperationRows';
|
||||
import { ExpressionQueryType } from 'app/features/expressions/types';
|
||||
|
||||
import { ExpressionDatasourceUID } from '../../../expressions/types';
|
||||
import { getQueryRunnerFor } from '../../utils/utils';
|
||||
|
||||
import { EmptyTransformationsMessage } from './EmptyTransformationsMessage';
|
||||
@@ -83,6 +85,35 @@ export function PanelDataTransformationsTabRendered({ model }: SceneComponentPro
|
||||
: [];
|
||||
}, [transformsWrongType]);
|
||||
|
||||
// Check if SQL expressions are applicable (all datasources must be backend datasources)
|
||||
const isSqlApplicable = useMemo(() => {
|
||||
const queryRunner = model.getQueryRunner();
|
||||
const queries = queryRunner.state.queries;
|
||||
|
||||
if (!queries || queries.length === 0) {
|
||||
return true; // If no queries, SQL is theoretically applicable
|
||||
}
|
||||
|
||||
// Check each query's datasource
|
||||
for (const query of queries) {
|
||||
const datasourceRef = query.datasource;
|
||||
|
||||
// Skip expression queries
|
||||
if (datasourceRef && 'uid' in datasourceRef && datasourceRef.uid === ExpressionDatasourceUID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const dsSettings = getDataSourceSrv().getInstanceSettings(datasourceRef);
|
||||
|
||||
// If we can't get datasource settings or it's not a backend datasource, SQL is not applicable
|
||||
if (!dsSettings || (!dsSettings.meta.backend && !dsSettings.meta.isBackend)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}, [model]);
|
||||
|
||||
const [drawerOpen, setDrawerOpen] = useState<boolean>(false);
|
||||
const [confirmModalOpen, setConfirmModalOpen] = useState<boolean>(false);
|
||||
|
||||
@@ -154,6 +185,8 @@ export function PanelDataTransformationsTabRendered({ model }: SceneComponentPro
|
||||
onShowPicker={openDrawer}
|
||||
onGoToQueries={onGoToQueries}
|
||||
onAddTransformation={onAddTransformation}
|
||||
data={sourceData.data.series}
|
||||
isSqlApplicable={isSqlApplicable}
|
||||
/>
|
||||
{transformationsDrawer}
|
||||
</>
|
||||
|
||||
+30
-4
@@ -1,7 +1,7 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { css, cx } from '@emotion/css';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Card, useStyles2 } from '@grafana/ui';
|
||||
import { Card, IconButton, useStyles2 } from '@grafana/ui';
|
||||
|
||||
export interface SqlExpressionCardProps {
|
||||
name: string;
|
||||
@@ -9,13 +9,24 @@ export interface SqlExpressionCardProps {
|
||||
imageUrl?: string;
|
||||
onClick: () => void;
|
||||
testId?: string;
|
||||
isDisabled?: boolean;
|
||||
disabledTooltip?: string;
|
||||
}
|
||||
|
||||
export function SqlExpressionCard({ name, description, imageUrl, onClick, testId }: SqlExpressionCardProps) {
|
||||
export function SqlExpressionCard({
|
||||
name,
|
||||
description,
|
||||
imageUrl,
|
||||
onClick,
|
||||
testId,
|
||||
isDisabled,
|
||||
disabledTooltip,
|
||||
}: SqlExpressionCardProps) {
|
||||
const styles = useStyles2(getSqlExpressionCardStyles);
|
||||
const cardClasses = isDisabled ? cx(styles.card, styles.cardDisabled) : styles.card;
|
||||
|
||||
return (
|
||||
<Card className={styles.card} data-testid={testId} onClick={onClick} noMargin>
|
||||
<Card className={cardClasses} data-testid={testId} onClick={onClick} noMargin>
|
||||
<Card.Heading className={styles.heading}>
|
||||
<div className={styles.titleRow}>
|
||||
<span>{name}</span>
|
||||
@@ -28,6 +39,9 @@ export function SqlExpressionCard({ name, description, imageUrl, onClick, testId
|
||||
<img className={styles.image} src={imageUrl} alt={name} />
|
||||
</span>
|
||||
)}
|
||||
{isDisabled && disabledTooltip && (
|
||||
<IconButton className={styles.cardApplicableInfo} name="info-circle" tooltip={disabledTooltip} />
|
||||
)}
|
||||
</Card.Description>
|
||||
</Card>
|
||||
);
|
||||
@@ -67,5 +81,17 @@ function getSqlExpressionCardStyles(theme: GrafanaTheme2) {
|
||||
maxWidth: '100%',
|
||||
marginTop: theme.spacing(2),
|
||||
}),
|
||||
cardDisabled: css({
|
||||
backgroundColor: theme.colors.action.disabledBackground,
|
||||
img: {
|
||||
filter: 'grayscale(100%)',
|
||||
opacity: 0.33,
|
||||
},
|
||||
}),
|
||||
cardApplicableInfo: css({
|
||||
position: 'absolute',
|
||||
bottom: theme.spacing(1),
|
||||
right: theme.spacing(1),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5942,6 +5942,7 @@
|
||||
"add-transformation": "Add transformation",
|
||||
"show-more": "Show more",
|
||||
"sql-name": "Transform with SQL",
|
||||
"sql-not-applicable": "SQL expressions require backend data sources",
|
||||
"sql-transformation-description": "Manipulate your data using MySQL-like syntax"
|
||||
},
|
||||
"general-settings-edit-view": {
|
||||
|
||||
Reference in New Issue
Block a user