Skip to content

Commit

Permalink
Transformations: Add frame source picker to allow transforming annota…
Browse files Browse the repository at this point in the history
…tions (#77842)

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
  • Loading branch information
leeoniya and ryantxu committed Jan 3, 2024
1 parent a5957ba commit fb79be4
Show file tree
Hide file tree
Showing 16 changed files with 165 additions and 56 deletions.
3 changes: 0 additions & 3 deletions .betterer.results
Original file line number Diff line number Diff line change
Expand Up @@ -2799,9 +2799,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Styles should be written using objects.", "9"],
[0, 0, 0, "Styles should be written using objects.", "10"]
],
"public/app/features/dashboard/components/TransformationsEditor/TransformationFilter.tsx:5381": [
[0, 0, 0, "Styles should be written using objects.", "0"]
],
"public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ use the output of one transformation as the input to another transformation, etc
| `options` | | **Yes** | | Options to be passed to the transformer<br/>Valid options depend on the transformer id |
| `disabled` | boolean | No | | Disabled transformations are skipped |
| `filter` | [MatcherConfig](#matcherconfig) | No | | Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation.<br/>It comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type. |
| `topic` | string | No | | Where to pull DataFrames from as input to transformation<br/>Possible values are: `series`, `annotations`, `alertStates`. |

### MatcherConfig

Expand Down
2 changes: 2 additions & 0 deletions kinds/dashboard/dashboard_kind.cue
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,8 @@ lineage: schemas: [{
disabled?: bool
// Optional frame matcher. When missing it will be applied to all results
filter?: #MatcherConfig
// Where to pull DataFrames from as input to transformation
topic?: "series" | "annotations" | "alertStates" // replaced with common.DataTopic
// Options to be passed to the transformer
// Valid options depend on the transformer id
options: _
Expand Down
13 changes: 7 additions & 6 deletions packages/grafana-data/src/types/query.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { DataQuery as SchemaDataQuery, DataSourceRef as SchemaDataSourceRef } from '@grafana/schema';
import {
DataQuery as SchemaDataQuery,
DataSourceRef as SchemaDataSourceRef,
DataTopic as SchemaDataTopic,
} from '@grafana/schema';

/**
* @deprecated use the type from @grafana/schema
Expand All @@ -13,12 +17,9 @@ export interface DataSourceRef extends SchemaDataSourceRef {}
/**
* Attached to query results (not persisted)
*
* @public
* @deprecated use the type from @grafana/schema
*/
export enum DataTopic {
Annotations = 'annotations',
AlertStates = 'alertStates',
}
export { SchemaDataTopic as DataTopic };

/**
* Abstract representation of any label-based query
Expand Down
10 changes: 10 additions & 0 deletions packages/grafana-schema/src/common/common.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@
// Run 'make gen-cue' from repository root to regenerate.


/**
* A topic is attached to DataFrame metadata in query results.
* This specifies where the data should be used.
*/
export enum DataTopic {
AlertStates = 'alertStates',
Annotations = 'annotations',
Series = 'series',
}

/**
* TODO docs
*/
Expand Down
5 changes: 5 additions & 0 deletions packages/grafana-schema/src/common/data.cue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package common

// A topic is attached to DataFrame metadata in query results.
// This specifies where the data should be used.
DataTopic: "series" | "annotations" | "alertStates" @cuetsy(kind="enum",memberNames="Series|Annotations|AlertStates")
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,10 @@ export interface DataTransformerConfig {
* Valid options depend on the transformer id
*/
options: unknown;
/**
* Where to pull DataFrames from as input to transformation
*/
topic?: ('series' | 'annotations' | 'alertStates'); // replaced with common.DataTopic
}

/**
Expand Down
3 changes: 2 additions & 1 deletion packages/grafana-schema/src/veneer/dashboard.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DataSourceRef as CommonDataSourceRef, DataSourceRef } from '../common/common.gen';
import { DataSourceRef as CommonDataSourceRef, DataSourceRef, DataTopic } from '../common/common.gen';
import * as raw from '../raw/dashboard/x/dashboard_types.gen';

import { DataQuery } from './common.types';
Expand Down Expand Up @@ -59,6 +59,7 @@ export interface MatcherConfig<TConfig = any> extends raw.MatcherConfig {

export interface DataTransformerConfig<TOptions = any> extends raw.DataTransformerConfig {
options: TOptions;
topic?: DataTopic;
}

export interface TimePickerConfig extends raw.TimePickerConfig {}
Expand Down
13 changes: 13 additions & 0 deletions pkg/kinds/dashboard/dashboard_spec_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,69 @@ import { css } from '@emotion/css';
import React, { useMemo } from 'react';

import {
DataFrame,
DataTransformerConfig,
GrafanaTheme2,
StandardEditorContext,
StandardEditorsRegistryItem,
} from '@grafana/data';
import { Field, useStyles2 } from '@grafana/ui';
import { DataTopic } from '@grafana/schema';
import { Field, Select, useStyles2 } from '@grafana/ui';
import { FrameSelectionEditor } from 'app/plugins/panel/geomap/editor/FrameSelectionEditor';

import { TransformationData } from './TransformationsEditor';

interface TransformationFilterProps {
index: number;
config: DataTransformerConfig;
data: DataFrame[];
data: TransformationData;
onChange: (index: number, config: DataTransformerConfig) => void;
}

export const TransformationFilter = ({ index, data, config, onChange }: TransformationFilterProps) => {
const styles = useStyles2(getStyles);
const context = useMemo(() => {
// eslint-disable-next-line
return { data } as StandardEditorContext<unknown>;
}, [data]);

const opts = useMemo(() => {
return {
// eslint-disable-next-line
context: { data: data.series } as StandardEditorContext<unknown>,
showTopic: true || data.annotations?.length || config.topic?.length,
showFilter: config.topic !== DataTopic.Annotations,
source: [
{ value: DataTopic.Series, label: `Query results` },
{ value: DataTopic.Annotations, label: `Annotation data` },
],
};
}, [data, config.topic]);

return (
<div className={styles.wrapper}>
<Field label="Apply transformation to">
<FrameSelectionEditor
value={config.filter!}
context={context}
// eslint-disable-next-line
item={{} as StandardEditorsRegistryItem}
onChange={(filter) => onChange(index, { ...config, filter })}
/>
<>
{opts.showTopic && (
<Select
isClearable={true}
options={opts.source}
value={opts.source.find((v) => v.value === config.topic)}
placeholder={opts.source[0].label}
className={styles.padded}
onChange={(option) => {
onChange(index, {
...config,
topic: option?.value,
});
}}
/>
)}
{opts.showFilter && (
<FrameSelectionEditor
value={config.filter!}
context={opts.context}
// eslint-disable-next-line
item={{} as StandardEditorsRegistryItem}
onChange={(filter) => onChange(index, { ...config, filter })}
/>
)}
</>
</Field>
</div>
);
Expand All @@ -44,13 +74,16 @@ const getStyles = (theme: GrafanaTheme2) => {
const borderRadius = theme.shape.radius.default;

return {
wrapper: css`
padding: ${theme.spacing(2)};
border: 2px solid ${theme.colors.background.secondary};
border-top: none;
border-radius: 0 0 ${borderRadius} ${borderRadius};
position: relative;
top: -4px;
`,
wrapper: css({
padding: theme.spacing(2),
border: `2px solid ${theme.colors.background.secondary}`,
borderTop: `none`,
borderRadius: `0 0 ${borderRadius} ${borderRadius}`,
position: `relative`,
top: `-4px`,
}),
padded: css({
marginBottom: theme.spacing(1),
}),
};
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useCallback } from 'react';
import { useToggle } from 'react-use';

import { DataFrame, DataTransformerConfig, TransformerRegistryItem, FrameMatcherID } from '@grafana/data';
import { DataTransformerConfig, TransformerRegistryItem, FrameMatcherID, DataTopic } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
import { ConfirmModal } from '@grafana/ui';
import {
Expand All @@ -15,12 +15,13 @@ import { PluginStateInfo } from 'app/features/plugins/components/PluginStateInfo
import { TransformationEditor } from './TransformationEditor';
import { TransformationEditorHelperModal } from './TransformationEditorHelperModal';
import { TransformationFilter } from './TransformationFilter';
import { TransformationData } from './TransformationsEditor';
import { TransformationsEditorTransformation } from './types';

interface TransformationOperationRowProps {
id: string;
index: number;
data: DataFrame[];
data: TransformationData;
uiConfig: TransformerRegistryItem<null>;
configs: TransformationsEditorTransformation[];
onRemove: (index: number) => void;
Expand All @@ -40,8 +41,9 @@ export const TransformationOperationRow = ({
const [showDebug, toggleShowDebug] = useToggle(false);
const [showHelp, toggleShowHelp] = useToggle(false);
const disabled = !!configs[index].transformation.disabled;
const filter = configs[index].transformation.filter != null;
const showFilter = filter || data.length > 1;
const topic = configs[index].transformation.topic;
const showFilterEditor = configs[index].transformation.filter != null || topic != null;
const showFilterToggle = showFilterEditor || data.series.length > 1 || (data.annotations?.length ?? 0) > 0;

const onDisableToggle = useCallback(
(index: number) => {
Expand Down Expand Up @@ -99,12 +101,12 @@ export const TransformationOperationRow = ({
onClick={instrumentToggleCallback(toggleShowHelp, 'help', showHelp)}
active={showHelp}
/>
{showFilter && (
{showFilterToggle && (
<QueryOperationToggleAction
title="Filter"
icon="filter"
onClick={instrumentToggleCallback(toggleFilter, 'filter', filter)}
active={filter}
onClick={instrumentToggleCallback(toggleFilter, 'filter', showFilterEditor)}
active={showFilterEditor}
/>
)}
<QueryOperationToggleAction
Expand Down Expand Up @@ -156,13 +158,14 @@ export const TransformationOperationRow = ({
open: 'Expand transformation row',
}}
>
{filter && (
{showFilterEditor && (
<TransformationFilter index={index} config={configs[index].transformation} data={data} onChange={onChange} />
)}

<TransformationEditor
debugMode={showDebug}
index={index}
data={data}
data={topic === DataTopic.Annotations ? data.annotations ?? [] : data.series}
configs={configs}
uiConfig={uiConfig}
onChange={onChange}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React from 'react';

import { DataFrame, DataTransformerConfig, standardTransformersRegistry } from '@grafana/data';
import { DataTransformerConfig, standardTransformersRegistry } from '@grafana/data';

import { TransformationOperationRow } from './TransformationOperationRow';
import { TransformationData } from './TransformationsEditor';
import { TransformationsEditorTransformation } from './types';

interface TransformationOperationRowsProps {
data: DataFrame[];
data: TransformationData;
configs: TransformationsEditorTransformation[];
onRemove: (index: number) => void;
onChange: (index: number, config: DataTransformerConfig) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,13 @@ const VIEW_ALL_VALUE = 'viewAll';
export type viewAllType = 'viewAll';
export type FilterCategory = TransformerCategory | viewAllType;

export interface TransformationData {
series: DataFrame[];
annotations?: DataFrame[];
}

interface State {
data: DataFrame[];
data: TransformationData;
transformations: TransformationsEditorTransformation[];
search: string;
showPicker?: boolean;
Expand All @@ -68,7 +73,9 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
transformation: t,
id: ids[i],
})),
data: [],
data: {
series: [],
},
search: '',
selectedFilter: VIEW_ALL_VALUE,
showIllustrations: true,
Expand Down Expand Up @@ -120,7 +127,7 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
.getQueryRunner()
.getData({ withTransforms: false, withFieldConfig: false })
.subscribe({
next: (panelData: PanelData) => this.setState({ data: panelData.series }),
next: (panelData: PanelData) => this.setState({ data: panelData }),
});
}

Expand Down Expand Up @@ -384,7 +391,7 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
onSearchChange={this.onSearchChange}
onSearchKeyDown={this.onSearchKeyDown}
onTransformationAdd={this.onTransformationAdd}
data={this.state.data}
data={this.state.data.series}
selectedFilter={this.state.selectedFilter}
showIllustrations={this.state.showIllustrations}
/>
Expand Down

0 comments on commit fb79be4

Please sign in to comment.