This commit is contained in:
Kristina Durivage 2025-09-19 18:21:45 -05:00
parent f166968357
commit 141885acae
19 changed files with 184 additions and 7 deletions

View File

@ -127,6 +127,8 @@ DataTopic: "series" | "annotations" | "alertStates" @cog(kind="enum",memberNames
DataTransformerConfig: {
// Unique identifier of transformer
id: string
// Unique identifier of the instance of the transformer
refId?: string
// Disabled transformations are skipped
disabled?: bool
// Optional frame matcher. When missing it will be applied to all results

View File

@ -76,7 +76,7 @@ func (c *DashboardClient) Patch(ctx context.Context, identifier resource.Identif
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *DashboardClient) UpdateStatus(ctx context.Context, newStatus DashboardStatus, opts resource.UpdateOptions) (*Dashboard, error) {
func (c *DashboardClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus DashboardStatus, opts resource.UpdateOptions) (*Dashboard, error) {
return c.client.Update(ctx, &Dashboard{
TypeMeta: metav1.TypeMeta{
Kind: DashboardKind().Kind(),
@ -84,6 +84,8 @@ func (c *DashboardClient) UpdateStatus(ctx context.Context, newStatus DashboardS
},
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: opts.ResourceVersion,
Namespace: identifier.Namespace,
Name: identifier.Name,
},
Status: newStatus,
}, resource.UpdateOptions{

View File

@ -516,6 +516,8 @@ lineage: schemas: [{
#DataTransformerConfig: {
// Unique identifier of transformer
id: string
// Unique identifier of the instance of the transformer
refId?: string
// Disabled transformations are skipped
disabled?: bool
// Optional frame matcher. When missing it will be applied to all results

View File

@ -76,7 +76,7 @@ func (c *DashboardClient) Patch(ctx context.Context, identifier resource.Identif
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *DashboardClient) UpdateStatus(ctx context.Context, newStatus DashboardStatus, opts resource.UpdateOptions) (*Dashboard, error) {
func (c *DashboardClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus DashboardStatus, opts resource.UpdateOptions) (*Dashboard, error) {
return c.client.Update(ctx, &Dashboard{
TypeMeta: metav1.TypeMeta{
Kind: DashboardKind().Kind(),
@ -84,6 +84,8 @@ func (c *DashboardClient) UpdateStatus(ctx context.Context, newStatus DashboardS
},
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: opts.ResourceVersion,
Namespace: identifier.Namespace,
Name: identifier.Name,
},
Status: newStatus,
}, resource.UpdateOptions{

View File

@ -516,6 +516,8 @@ lineage: schemas: [{
#DataTransformerConfig: {
// Unique identifier of transformer
id: string
// Unique identifier of the instance of the transformer
refId?: string
// Disabled transformations are skipped
disabled?: bool
// Optional frame matcher. When missing it will be applied to all results

View File

@ -76,7 +76,7 @@ func (c *DashboardClient) Patch(ctx context.Context, identifier resource.Identif
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *DashboardClient) UpdateStatus(ctx context.Context, newStatus DashboardStatus, opts resource.UpdateOptions) (*Dashboard, error) {
func (c *DashboardClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus DashboardStatus, opts resource.UpdateOptions) (*Dashboard, error) {
return c.client.Update(ctx, &Dashboard{
TypeMeta: metav1.TypeMeta{
Kind: DashboardKind().Kind(),
@ -84,6 +84,8 @@ func (c *DashboardClient) UpdateStatus(ctx context.Context, newStatus DashboardS
},
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: opts.ResourceVersion,
Namespace: identifier.Namespace,
Name: identifier.Name,
},
Status: newStatus,
}, resource.UpdateOptions{

View File

@ -76,7 +76,7 @@ func (c *DashboardClient) Patch(ctx context.Context, identifier resource.Identif
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *DashboardClient) UpdateStatus(ctx context.Context, newStatus DashboardStatus, opts resource.UpdateOptions) (*Dashboard, error) {
func (c *DashboardClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus DashboardStatus, opts resource.UpdateOptions) (*Dashboard, error) {
return c.client.Update(ctx, &Dashboard{
TypeMeta: metav1.TypeMeta{
Kind: DashboardKind().Kind(),
@ -84,6 +84,8 @@ func (c *DashboardClient) UpdateStatus(ctx context.Context, newStatus DashboardS
},
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: opts.ResourceVersion,
Namespace: identifier.Namespace,
Name: identifier.Name,
},
Status: newStatus,
}, resource.UpdateOptions{

View File

@ -131,6 +131,8 @@ DataTopic: "series" | "annotations" | "alertStates" @cog(kind="enum",memberNames
DataTransformerConfig: {
// Unique identifier of transformer
id: string
// Unique identifier of the instance of the transformer
refId?: string
// Disabled transformations are skipped
disabled?: bool
// Optional frame matcher. When missing it will be applied to all results

View File

@ -225,6 +225,8 @@ func NewDashboardTransformationKind() *DashboardTransformationKind {
type DashboardDataTransformerConfig struct {
// Unique identifier of transformer
Id string `json:"id"`
// Unique identifier of the instance of the transformer
RefId *string `json:"refId,omitempty"`
// Disabled transformations are skipped
Disabled *bool `json:"disabled,omitempty"`
// Optional frame matcher. When missing it will be applied to all results

View File

@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/resource"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kube-openapi/pkg/spec3"
v0alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
v1beta1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
@ -19,6 +20,8 @@ import (
v2beta1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1"
)
var ()
var appManifestData = app.ManifestData{
AppName: "dashboard",
Group: "dashboard.grafana.app",
@ -34,6 +37,10 @@ var appManifestData = app.ManifestData{
Conversion: false,
},
},
Routes: app.ManifestVersionRoutes{
Namespaced: map[string]spec3.PathProps{},
Cluster: map[string]spec3.PathProps{},
},
},
{
@ -47,6 +54,10 @@ var appManifestData = app.ManifestData{
Conversion: false,
},
},
Routes: app.ManifestVersionRoutes{
Namespaced: map[string]spec3.PathProps{},
Cluster: map[string]spec3.PathProps{},
},
},
{
@ -60,6 +71,10 @@ var appManifestData = app.ManifestData{
Conversion: false,
},
},
Routes: app.ManifestVersionRoutes{
Namespaced: map[string]spec3.PathProps{},
Cluster: map[string]spec3.PathProps{},
},
},
{
@ -73,6 +88,10 @@ var appManifestData = app.ManifestData{
Conversion: false,
},
},
Routes: app.ManifestVersionRoutes{
Namespaced: map[string]spec3.PathProps{},
Cluster: map[string]spec3.PathProps{},
},
},
},
}
@ -104,6 +123,7 @@ var customRouteToGoResponseType = map[string]any{}
// ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists.
// kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths.
// If there is no association for the provided kind, version, custom route path, and method, exists will return false.
// Resource routes (those without a kind) should prefix their route with "<namespace>/" if the route is namespaced (otherwise the route is assumed to be cluster-scope)
func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (goType any, exists bool) {
if len(path) > 0 && path[0] == '/' {
path = path[1:]
@ -122,8 +142,22 @@ func ManifestCustomRouteQueryAssociator(kind, version, path, verb string) (goTyp
return goType, exists
}
var customRouteToGoRequestBodyType = map[string]any{}
func ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb string) (goType any, exists bool) {
if len(path) > 0 && path[0] == '/' {
path = path[1:]
}
goType, exists = customRouteToGoRequestBodyType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))]
return goType, exists
}
type GoTypeAssociator struct{}
func NewGoTypeAssociator() *GoTypeAssociator {
return &GoTypeAssociator{}
}
func (g *GoTypeAssociator) KindToGoType(kind, version string) (goType resource.Kind, exists bool) {
return ManifestGoTypeAssociator(kind, version)
}
@ -133,3 +167,6 @@ func (g *GoTypeAssociator) CustomRouteReturnGoType(kind, version, path, verb str
func (g *GoTypeAssociator) CustomRouteQueryGoType(kind, version, path, verb string) (goType runtime.Object, exists bool) {
return ManifestCustomRouteQueryAssociator(kind, version, path, verb)
}
func (g *GoTypeAssociator) CustomRouteRequestBodyGoType(kind, version, path, verb string) (goType any, exists bool) {
return ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb)
}

View File

@ -512,6 +512,8 @@ lineage: schemas: [{
#DataTransformerConfig: {
// Unique identifier of transformer
id: string
// Unique identifier of the instance of the transformer
refId?: string
// Disabled transformations are skipped
disabled?: bool
// Optional frame matcher. When missing it will be applied to all results

View File

@ -746,6 +746,10 @@ export interface DataTransformerConfig {
* Valid options depend on the transformer id
*/
options: unknown;
/**
* Unique identifier of the instance of the transformer
*/
refId?: string;
/**
* Where to pull DataFrames from as input to transformation
*/

View File

@ -176,6 +176,8 @@ export const defaultTransformationKind = (): TransformationKind => ({
export interface DataTransformerConfig {
// Unique identifier of transformer
id: string;
// Unique identifier of the instance of the transformer
refId?: string;
// Disabled transformations are skipped
disabled?: boolean;
// Optional frame matcher. When missing it will be applied to all results

View File

@ -312,6 +312,8 @@ const DashboardLinkPlacement = "inControlsMenu"
type DataTransformerConfig struct {
// Unique identifier of transformer
Id string `json:"id"`
// Unique identifier of the instance of the transformer
RefId *string `json:"refId,omitempty"`
// Disabled transformations are skipped
Disabled *bool `json:"disabled,omitempty"`
// Optional frame matcher. When missing it will be applied to all results

View File

@ -72,7 +72,7 @@ export const QueryOperationRowHeader = ({
// this is just to provide a better experience for mouse users
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<div className={styles.titleWrapper} onClick={onRowToggle}>
<div className={cx(styles.title, disabled && styles.disabled)}>{title}</div>
<div className={cx(styles.title, disabled && styles.disabled)}>{title} test</div>
</div>
)}
{headerElement}

View File

@ -1,3 +1,4 @@
/* eslint-disable no-restricted-syntax */
import { css } from '@emotion/css';
import { useState } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';

View File

@ -1,3 +1,4 @@
import { css, cx } from '@emotion/css';
import { useCallback, useEffect, useState } from 'react';
import { useToggle } from 'react-use';
import { mergeMap } from 'rxjs';
@ -10,11 +11,12 @@ import {
getFrameMatchers,
transformDataFrame,
DataFrame,
GrafanaTheme2,
} from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { t } from '@grafana/i18n';
import { t, Trans } from '@grafana/i18n';
import { getTemplateSrv, reportInteraction } from '@grafana/runtime';
import { ConfirmModal } from '@grafana/ui';
import { ConfirmModal, FieldValidationMessage, Icon, Input, useStyles2 } from '@grafana/ui';
import {
QueryOperationAction,
QueryOperationToggleAction,
@ -48,9 +50,11 @@ export const TransformationOperationRow = ({
uiConfig,
onChange,
}: TransformationOperationRowProps) => {
const styles = useStyles2(getStyles);
const [showDeleteModal, setShowDeleteModal] = useToggle(false);
const [showDebug, toggleShowDebug] = useToggle(false);
const [showHelp, toggleShowHelp] = useToggle(false);
const [isRefIdEditing, toggleIsRefIdEditing] = useToggle(false);
const disabled = !!configs[index].transformation.disabled;
const topic = configs[index].transformation.topic;
const showFilterEditor = configs[index].transformation.filter != null || topic != null;
@ -157,6 +161,45 @@ export const TransformationOperationRow = ({
};
}, [index, data, configs]);
const renderHeader = () => {
///** <!--{validationError && <FieldValidationMessage horizontal>{validationError}</FieldValidationMessage>}-->
return (
<div className={styles.wrapper}>
{!isRefIdEditing && (
<button
className={styles.queryNameWrapper}
title={t('query.query-editor-row-header.query-name-div-title-edit-query-name', 'Edit transformation name')}
onClick={() => toggleIsRefIdEditing()}
data-testid="query-name-div"
type="button"
>
<span className={styles.queryName}>{configs[index].refId}</span>
<Icon name="pen" className={styles.queryEditIcon} size="sm" />
</button>
)}
{isRefIdEditing && (
<>
<Input
type="text"
defaultValue={configs[index].refId}
//onBlur={onEditQueryBlur}
autoFocus
//onKeyDown={onKeyDown}
//onFocus={onFocus}
//invalid={validationError !== null}
onChange={(input) => {
onChange(index, { ...configs[index].transformation, refId: input.currentTarget.value });
}}
className={styles.queryNameInput}
data-testid="query-name-input"
/>
</>
)}
</div>
);
};
const renderActions = () => {
return (
<>
@ -232,6 +275,7 @@ export const TransformationOperationRow = ({
title={`${index + 1} - ${uiConfig.name}`}
draggable
actions={renderActions}
headerElement={renderHeader}
disabled={disabled}
expanderMessages={{
close: 'Collapse transformation row',
@ -263,3 +307,59 @@ export const TransformationOperationRow = ({
</>
);
};
const getStyles = (theme: GrafanaTheme2) => {
return {
wrapper: css({
label: 'Wrapper',
display: 'flex',
alignItems: 'center',
marginLeft: theme.spacing(0.5),
overflow: 'hidden',
}),
queryNameWrapper: css({
display: 'flex',
cursor: 'pointer',
border: '1px solid transparent',
borderRadius: theme.shape.radius.default,
alignItems: 'center',
padding: theme.spacing(0, 0, 0, 0.5),
margin: 0,
background: 'transparent',
overflow: 'hidden',
'&:hover': {
background: theme.colors.action.hover,
border: `1px dashed ${theme.colors.border.strong}`,
},
'&:focus': {
border: `2px solid ${theme.colors.primary.border}`,
},
'&:hover, &:focus': {
'.query-name-edit-icon': {
visibility: 'visible',
},
},
}),
queryName: css({
fontWeight: theme.typography.fontWeightMedium,
color: theme.colors.primary.text,
cursor: 'pointer',
overflow: 'hidden',
marginLeft: theme.spacing(0.5),
}),
queryEditIcon: cx(
css({
marginLeft: theme.spacing(2),
visibility: 'hidden',
}),
'query-name-edit-icon'
),
queryNameInput: css({
maxWidth: '300px',
margin: '-4px 0',
}),
};
};

View File

@ -276,6 +276,16 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
renderTransformationEditors = () => {
const { data, transformations } = this.state;
const transformationNoRefIdIdxs = transformations
.map((t, i) => {
return t.refId === undefined ? i : undefined;
})
.filter((idx) => idx !== undefined);
transformationNoRefIdIdxs.forEach((tIdx, i) => {
transformations[tIdx].refId = `T-${i}`;
});
return (
<DragDropContext onDragEnd={this.onDragEnd}>
<Droppable droppableId="transformations-list" direction="vertical">

View File

@ -2,5 +2,6 @@ import { DataTransformerConfig } from '@grafana/data';
export interface TransformationsEditorTransformation {
transformation: DataTransformerConfig;
refId?: string;
id: string;
}