rework some stuff to make selecting the correct transform easier
This commit is contained in:
@@ -59,14 +59,14 @@ export const DetailView = memo(({ selectedItem, panel, tabs }: DetailViewProps)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const transformsTab = tabs.find((t) => t.tabId === TabId.Transformations);
|
||||
if (transformsTab instanceof PanelDataTransformationsTab && 'id' in selectedItem.data) {
|
||||
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} />
|
||||
<PanelDataTransformationsTabRendered model={transformsTab} selectedIdx={selectedItem.index} />
|
||||
</Container>
|
||||
</ScrollContainer>
|
||||
</>
|
||||
|
||||
@@ -32,6 +32,7 @@ import { PanelDataTransformationsTab } from './PanelDataTransformationsTab';
|
||||
import { QueryTransformList, QueryTransformItem } from './QueryTransformList';
|
||||
import { TransformationsDrawer } from './TransformationsDrawer';
|
||||
import { PanelDataPaneTab, TabId } from './types';
|
||||
import { isDataTransformerConfig, queryItemId, transformItemId } from './utils';
|
||||
|
||||
export interface PanelDataPaneState extends SceneObjectState {
|
||||
tabs: PanelDataPaneTab[];
|
||||
@@ -39,10 +40,6 @@ export interface PanelDataPaneState extends SceneObjectState {
|
||||
panelRef: SceneObjectRef<VizPanel>;
|
||||
}
|
||||
|
||||
const querySelectedId = (refId: string) => `query-${refId}`;
|
||||
const transformSelectedId = (index: number) => `transform-${index}`;
|
||||
const expressionSelectedId = (refId: string) => `expression-${refId}`;
|
||||
|
||||
export class PanelDataPane extends SceneObjectBase<PanelDataPaneState> {
|
||||
static Component = PanelDataPaneRendered;
|
||||
protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['tab'] });
|
||||
@@ -131,11 +128,7 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
}
|
||||
|
||||
if (updatedQuery) {
|
||||
setSelectedId(
|
||||
updatedQuery.datasource?.type === '__expr__'
|
||||
? expressionSelectedId(updatedQuery.refId)
|
||||
: querySelectedId(updatedQuery.refId)
|
||||
);
|
||||
setSelectedId(queryItemId(updatedQuery));
|
||||
}
|
||||
});
|
||||
}, [queryRunner]);
|
||||
@@ -147,45 +140,29 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
// Build combined items list
|
||||
const items: QueryTransformItem[] = useMemo(() => {
|
||||
const result: QueryTransformItem[] = [];
|
||||
|
||||
// Add queries and expressions (using actual array index)
|
||||
const queries = queryRunnerState?.queries || [];
|
||||
queries.forEach((query, actualIndex) => {
|
||||
if ('refId' in query) {
|
||||
if (isExpressionQuery(query)) {
|
||||
result.push({
|
||||
id: expressionSelectedId(query.refId),
|
||||
type: 'expression',
|
||||
data: query,
|
||||
index: actualIndex, // Store actual index in queries array
|
||||
});
|
||||
} else {
|
||||
result.push({
|
||||
id: querySelectedId(query.refId),
|
||||
type: 'query',
|
||||
data: query,
|
||||
index: actualIndex, // Store actual index in queries array
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add transformations
|
||||
const rawTransformations = transformerState?.transformations || [];
|
||||
const transformations = Array.isArray(rawTransformations)
|
||||
? rawTransformations.filter(
|
||||
(t): t is DataTransformerConfig =>
|
||||
t !== null && typeof t === 'object' && 'id' in t && typeof t.id === 'string'
|
||||
)
|
||||
: [];
|
||||
transformations.forEach((transform, index) => {
|
||||
const queries = queryRunnerState?.queries;
|
||||
for (let i = 0; i < (queries?.length ?? 0); i++) {
|
||||
const query = queries![i];
|
||||
result.push({
|
||||
id: `transform-${index}`,
|
||||
type: 'transform',
|
||||
data: transform,
|
||||
index,
|
||||
id: queryItemId(query),
|
||||
type: isExpressionQuery(query) ? 'expression' : 'query',
|
||||
data: query,
|
||||
index: i, // Store actual index in queries array
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const transformations = transformerState?.transformations;
|
||||
for (let i = 0; i < (transformations?.length ?? 0); i++) {
|
||||
const transform = transformations![i];
|
||||
if (isDataTransformerConfig(transform)) {
|
||||
result.push({
|
||||
id: transformItemId(i),
|
||||
type: 'transform',
|
||||
data: transform,
|
||||
index: i,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [queryRunnerState?.queries, transformerState?.transformations]);
|
||||
@@ -214,7 +191,7 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
setTimeout(() => {
|
||||
const newQueries = getQueryRunnerFor(panel)?.state.queries || [];
|
||||
if (newQueries.length > 0) {
|
||||
setSelectedId(querySelectedId(newQueries[newQueries.length - 1].refId));
|
||||
setSelectedId(queryItemId(newQueries[newQueries.length - 1]));
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
@@ -254,7 +231,7 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
queryRunner.runQueries();
|
||||
|
||||
// Select the new expression
|
||||
setSelectedId(expressionSelectedId(nextRefId));
|
||||
setSelectedId(queryItemId(newExpression));
|
||||
}
|
||||
},
|
||||
[queryRunner]
|
||||
@@ -268,15 +245,9 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
|
||||
const transformsTab = tabs.find((t) => t.tabId === TabId.Transformations);
|
||||
if (transformsTab instanceof PanelDataTransformationsTab) {
|
||||
const transformer = transformsTab.getDataTransformer();
|
||||
const rawTransformations = transformer.state.transformations || [];
|
||||
const transformations = Array.isArray(rawTransformations)
|
||||
? rawTransformations.filter(
|
||||
(t): t is DataTransformerConfig =>
|
||||
t !== null && typeof t === 'object' && 'id' in t && typeof t.id === 'string'
|
||||
)
|
||||
: [];
|
||||
|
||||
const transformations = (transformsTab.getDataTransformer().state.transformations || []).filter(
|
||||
isDataTransformerConfig
|
||||
);
|
||||
const newTransformation: DataTransformerConfig = {
|
||||
id: selected.value,
|
||||
options: {},
|
||||
@@ -287,7 +258,7 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
|
||||
// Select the newly added transformation
|
||||
setTimeout(() => {
|
||||
setSelectedId(transformSelectedId(transformations.length));
|
||||
setSelectedId(transformItemId(transformations.length));
|
||||
}, 100);
|
||||
}
|
||||
},
|
||||
@@ -319,7 +290,7 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
queryRunner.runQueries();
|
||||
|
||||
// Select the new query
|
||||
setSelectedId(querySelectedId(newRefId));
|
||||
setSelectedId(queryItemId(duplicatedQuery));
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -335,7 +306,7 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
queryRunner.runQueries();
|
||||
|
||||
// Clear selection if removing the selected query
|
||||
if (selectedId === querySelectedId(queries[index]?.refId)) {
|
||||
if (selectedId === queryItemId(queries[index])) {
|
||||
setSelectedId(null);
|
||||
}
|
||||
}
|
||||
@@ -380,7 +351,7 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
queryRunner.setState({ queries: newQueries });
|
||||
queryRunner.runQueries();
|
||||
|
||||
setSelectedId(expressionSelectedId(newRefId));
|
||||
setSelectedId(queryItemId(duplicatedExpression));
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -396,7 +367,7 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
queryRunner.runQueries();
|
||||
|
||||
const expressionToRemove = queries[index];
|
||||
if (expressionToRemove && selectedId === expressionSelectedId(expressionToRemove.refId)) {
|
||||
if (expressionToRemove && selectedId === queryItemId(expressionToRemove)) {
|
||||
setSelectedId(null);
|
||||
}
|
||||
}
|
||||
@@ -418,21 +389,16 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
|
||||
const handleRemoveTransform = useCallback(
|
||||
(index: number) => {
|
||||
const transformsTab = tabs.find((t) => t.tabId === TabId.Transformations);
|
||||
if (transformsTab instanceof PanelDataTransformationsTab) {
|
||||
const transformer = transformsTab.getDataTransformer();
|
||||
const rawTransformations = transformer.state.transformations || [];
|
||||
const transformations = Array.isArray(rawTransformations)
|
||||
? rawTransformations.filter(
|
||||
(t): t is DataTransformerConfig =>
|
||||
t !== null && typeof t === 'object' && 'id' in t && typeof t.id === 'string'
|
||||
)
|
||||
: [];
|
||||
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.filter((_, i) => i !== index);
|
||||
transformsTab.onChangeTransformations(newTransformations);
|
||||
|
||||
// Clear selection if removing the selected transformation
|
||||
if (selectedId === transformSelectedId(index)) {
|
||||
if (selectedId === transformItemId(index)) {
|
||||
setSelectedId(null);
|
||||
}
|
||||
}
|
||||
@@ -464,6 +430,7 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
onDuplicateQuery={handleDuplicateQuery}
|
||||
onRemoveQuery={handleRemoveQuery}
|
||||
onToggleQueryVisibility={handleToggleQueryVisibility}
|
||||
// TODO: can all the expression stuff just be handled with the query handlers since expressions are queries?
|
||||
onDuplicateExpression={handleDuplicateExpression}
|
||||
onRemoveExpression={handleRemoveExpression}
|
||||
onToggleExpressionVisibility={handleToggleExpressionVisibility}
|
||||
|
||||
+17
-12
@@ -24,7 +24,7 @@ import { PanelDataPane } from './PanelDataPane';
|
||||
import { PanelDataQueriesTab } from './PanelDataQueriesTab';
|
||||
import { TransformationsDrawer } from './TransformationsDrawer';
|
||||
import { PanelDataPaneTab, TabId, PanelDataTabHeaderProps } from './types';
|
||||
import { findSqlExpression, scrollToQueryRow } from './utils';
|
||||
import { findSqlExpression, isDataTransformerConfig, scrollToQueryRow } from './utils';
|
||||
|
||||
const SET_TIMEOUT = 750;
|
||||
|
||||
@@ -67,19 +67,17 @@ export class PanelDataTransformationsTab
|
||||
}
|
||||
}
|
||||
|
||||
export function PanelDataTransformationsTabRendered({ model }: SceneComponentProps<PanelDataTransformationsTab>) {
|
||||
export function PanelDataTransformationsTabRendered({
|
||||
model,
|
||||
selectedIdx,
|
||||
}: SceneComponentProps<PanelDataTransformationsTab> & { selectedIdx?: number }) {
|
||||
const sourceData = model.getQueryRunner().useState();
|
||||
const { data, transformations: transformsWrongType } = model.getDataTransformer().useState();
|
||||
const { data, transformations: rawTransformations } = model.getDataTransformer().useState();
|
||||
|
||||
// Type guard to ensure transformations are DataTransformerConfig[]
|
||||
const transformations = useMemo<DataTransformerConfig[]>(() => {
|
||||
return Array.isArray(transformsWrongType)
|
||||
? transformsWrongType.filter(
|
||||
(t): t is DataTransformerConfig =>
|
||||
t !== null && typeof t === 'object' && 'id' in t && typeof t.id === 'string'
|
||||
)
|
||||
: [];
|
||||
}, [transformsWrongType]);
|
||||
return Array.isArray(rawTransformations) ? rawTransformations.filter(isDataTransformerConfig) : [];
|
||||
}, [rawTransformations]);
|
||||
|
||||
const [drawerOpen, setDrawerOpen] = useState<boolean>(false);
|
||||
|
||||
@@ -159,7 +157,12 @@ export function PanelDataTransformationsTabRendered({ model }: SceneComponentPro
|
||||
|
||||
return (
|
||||
<>
|
||||
<TransformationsEditor data={sourceData.data} transformations={transformations} model={model} />
|
||||
<TransformationsEditor
|
||||
data={sourceData.data}
|
||||
transformations={transformations}
|
||||
model={model}
|
||||
selectedIdx={selectedIdx}
|
||||
/>
|
||||
{transformationsDrawer}
|
||||
</>
|
||||
);
|
||||
@@ -169,9 +172,10 @@ interface TransformationEditorProps {
|
||||
transformations: DataTransformerConfig[];
|
||||
model: PanelDataTransformationsTab;
|
||||
data: PanelData;
|
||||
selectedIdx?: number;
|
||||
}
|
||||
|
||||
function TransformationsEditor({ transformations, model, data }: TransformationEditorProps) {
|
||||
function TransformationsEditor({ transformations, model, data, selectedIdx }: TransformationEditorProps) {
|
||||
const transformationEditorRows = transformations.map((t, i) => ({ id: `${i} - ${t.id}`, transformation: t }));
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
@@ -211,6 +215,7 @@ function TransformationsEditor({ transformations, model, data }: TransformationE
|
||||
}}
|
||||
configs={transformationEditorRows}
|
||||
data={data}
|
||||
selectedIdx={selectedIdx}
|
||||
></TransformationOperationRows>
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,8 @@ import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { SceneDataQuery } from '@grafana/scenes';
|
||||
import { Icon, IconButton, Stack, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { queryItemId, transformItemId } from './utils';
|
||||
|
||||
interface QueryTransformCardProps {
|
||||
item: SceneDataQuery | DataTransformerConfig;
|
||||
type: 'query' | 'transform' | 'expression';
|
||||
@@ -69,7 +71,7 @@ export const QueryTransformCard = memo(
|
||||
onClick={onClick}
|
||||
onKeyDown={handleKeyDown}
|
||||
data-testid={`${type}-card-${index}`}
|
||||
data-card-id={'refId' in item ? item.refId : `transform-${index}`}
|
||||
data-card-id={'refId' in item ? queryItemId(item) : transformItemId(index)}
|
||||
>
|
||||
{/* Header with type and action icons */}
|
||||
<div
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { DataQuery } from '@grafana/schema';
|
||||
import { CustomTransformerDefinition, SceneDataQuery } from '@grafana/scenes';
|
||||
import { DataQuery, DataTransformerConfig } from '@grafana/schema';
|
||||
import { isExpressionQuery } from 'app/features/expressions/guards';
|
||||
import { ExpressionQueryType } from 'app/features/expressions/types';
|
||||
|
||||
export function findSqlExpression(queries: DataQuery[]) {
|
||||
@@ -22,3 +24,11 @@ export function scrollToQueryRow(refId: string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const queryItemId = (query: SceneDataQuery) =>
|
||||
isExpressionQuery(query) ? `expression-${query.refId}` : `query-${query.refId}`;
|
||||
export const transformItemId = (index: number) => `transform-${index}`;
|
||||
|
||||
export const isDataTransformerConfig = (
|
||||
t: DataTransformerConfig | CustomTransformerDefinition
|
||||
): t is DataTransformerConfig => t !== null && typeof t === 'object' && 'id' in t && typeof t.id === 'string';
|
||||
|
||||
+51
-29
@@ -1,3 +1,5 @@
|
||||
import { memo } from 'react';
|
||||
|
||||
import { DataTransformerConfig, standardTransformersRegistry } from '@grafana/data';
|
||||
|
||||
import { TransformationOperationRow } from './TransformationOperationRow';
|
||||
@@ -9,36 +11,56 @@ interface TransformationOperationRowsProps {
|
||||
configs: TransformationsEditorTransformation[];
|
||||
onRemove: (index: number) => void;
|
||||
onChange: (index: number, config: DataTransformerConfig) => void;
|
||||
selectedIdx?: number;
|
||||
}
|
||||
|
||||
export const TransformationOperationRows = ({
|
||||
data,
|
||||
onChange,
|
||||
onRemove,
|
||||
configs,
|
||||
}: TransformationOperationRowsProps) => {
|
||||
return (
|
||||
<>
|
||||
{configs.map((t, i) => {
|
||||
const uiConfig = standardTransformersRegistry.getIfExists(t.transformation.id);
|
||||
export const TransformationOperationRows = memo(
|
||||
({ data, onChange, onRemove, configs, selectedIdx }: TransformationOperationRowsProps) => {
|
||||
if (selectedIdx != null) {
|
||||
const t = configs[selectedIdx];
|
||||
if (!t) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!uiConfig) {
|
||||
return null;
|
||||
}
|
||||
const uiConfig = standardTransformersRegistry.getIfExists(t.transformation.id);
|
||||
if (!uiConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TransformationOperationRow
|
||||
index={i}
|
||||
id={`${t.id}`}
|
||||
key={`${t.id}`}
|
||||
data={data}
|
||||
configs={configs}
|
||||
uiConfig={uiConfig}
|
||||
onRemove={onRemove}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<TransformationOperationRow
|
||||
index={selectedIdx}
|
||||
id={`${t.id}`}
|
||||
key={`${t.id}`}
|
||||
data={data}
|
||||
configs={configs}
|
||||
uiConfig={uiConfig}
|
||||
onRemove={onRemove}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return configs.map((t, i) => {
|
||||
const uiConfig = standardTransformersRegistry.getIfExists(t.transformation.id);
|
||||
if (!uiConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TransformationOperationRow
|
||||
index={i}
|
||||
id={`${t.id}`}
|
||||
key={`${t.id}`}
|
||||
data={data}
|
||||
configs={configs}
|
||||
uiConfig={uiConfig}
|
||||
onRemove={onRemove}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
TransformationOperationRows.displayName = 'TransformationOperationRows';
|
||||
|
||||
Reference in New Issue
Block a user