diff --git a/apps/dashboard/kinds/v2beta1/dashboard_spec.cue b/apps/dashboard/kinds/v2beta1/dashboard_spec.cue index fe9d13de67f..f8e1ab4dd97 100644 --- a/apps/dashboard/kinds/v2beta1/dashboard_spec.cue +++ b/apps/dashboard/kinds/v2beta1/dashboard_spec.cue @@ -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 diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_client_gen.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_client_gen.go index f20c92ae314..08e772728d4 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_client_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_client_gen.go @@ -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{ diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_kind.cue b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_kind.cue index fb971b2cc3f..8cabdd16c3e 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_kind.cue +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_kind.cue @@ -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 diff --git a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_client_gen.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_client_gen.go index 5ab0ff632a2..41bd34c838f 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_client_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_client_gen.go @@ -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{ diff --git a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue index fb971b2cc3f..8cabdd16c3e 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue @@ -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 diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_client_gen.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_client_gen.go index 587ff5e586b..a18e1438711 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_client_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_client_gen.go @@ -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{ diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_client_gen.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_client_gen.go index 9fc3441b2ea..5e419ee2861 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_client_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_client_gen.go @@ -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{ diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue index 2c7d74674f2..afb513ce1b5 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue @@ -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 diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec_gen.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec_gen.go index d43c7765f16..0546cbf71aa 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec_gen.go @@ -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 diff --git a/apps/dashboard/pkg/apis/dashboard_manifest.go b/apps/dashboard/pkg/apis/dashboard_manifest.go index 048e7ca7d53..aac90f6e88d 100644 --- a/apps/dashboard/pkg/apis/dashboard_manifest.go +++ b/apps/dashboard/pkg/apis/dashboard_manifest.go @@ -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 "/" 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) +} diff --git a/kinds/dashboard/dashboard_kind.cue b/kinds/dashboard/dashboard_kind.cue index 20571c8211a..011dd3bca8d 100644 --- a/kinds/dashboard/dashboard_kind.cue +++ b/kinds/dashboard/dashboard_kind.cue @@ -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 diff --git a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts index d158d884aa0..968a182f062 100644 --- a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts +++ b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts @@ -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 */ diff --git a/packages/grafana-schema/src/schema/dashboard/v2beta1/types.spec.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2beta1/types.spec.gen.ts index 40c2d212d31..6818f92a413 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2beta1/types.spec.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2beta1/types.spec.gen.ts @@ -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 diff --git a/pkg/kinds/dashboard/dashboard_spec_gen.go b/pkg/kinds/dashboard/dashboard_spec_gen.go index fa72f781f75..f8d85b0b37d 100644 --- a/pkg/kinds/dashboard/dashboard_spec_gen.go +++ b/pkg/kinds/dashboard/dashboard_spec_gen.go @@ -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 diff --git a/public/app/core/components/QueryOperationRow/QueryOperationRowHeader.tsx b/public/app/core/components/QueryOperationRow/QueryOperationRowHeader.tsx index 18abd7e0505..7e712870ae2 100644 --- a/public/app/core/components/QueryOperationRow/QueryOperationRowHeader.tsx +++ b/public/app/core/components/QueryOperationRow/QueryOperationRowHeader.tsx @@ -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
-
{title}
+
{title} test
)} {headerElement} diff --git a/public/app/features/correlations/Forms/TransformationEditorRow.tsx b/public/app/features/correlations/Forms/TransformationEditorRow.tsx index 823a6cf33e2..edef16fbf5a 100644 --- a/public/app/features/correlations/Forms/TransformationEditorRow.tsx +++ b/public/app/features/correlations/Forms/TransformationEditorRow.tsx @@ -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'; diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx index bebae5de3e2..690d086f886 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx @@ -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 = () => { + ///** + return ( +
+ {!isRefIdEditing && ( + + )} + + {isRefIdEditing && ( + <> + { + onChange(index, { ...configs[index].transformation, refId: input.currentTarget.value }); + }} + className={styles.queryNameInput} + data-testid="query-name-input" + /> + + )} +
+ ); + }; + 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', + }), + }; +}; diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx index afdf6b782ea..9f1d001d0cd 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx @@ -276,6 +276,16 @@ class UnThemedTransformationsEditor extends React.PureComponent { 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 ( diff --git a/public/app/features/dashboard/components/TransformationsEditor/types.ts b/public/app/features/dashboard/components/TransformationsEditor/types.ts index 50ff63d2fc8..7b90cbbb2cc 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/types.ts +++ b/public/app/features/dashboard/components/TransformationsEditor/types.ts @@ -2,5 +2,6 @@ import { DataTransformerConfig } from '@grafana/data'; export interface TransformationsEditorTransformation { transformation: DataTransformerConfig; + refId?: string; id: string; }