grafana/public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx

233 lines
6.9 KiB
TypeScript
Raw Normal View History

import React from 'react';
import { t } from '@grafana/i18n';
import {
sceneGraph,
SceneObject,
SceneObjectBase,
SceneObjectState,
VariableDependencyConfig,
VizPanel,
} from '@grafana/scenes';
Add `v2beta1` api version: Consolidate schema breaking changes (#108172) * Revert "Revert: Future-proofing query and data source model in Dashboard Sche… (#107985)" This reverts commit 13a89d4ae3f1f4ccdb98edc9b42f3c35522d2dfa. * Revert "Revert "Schema V2: Simplify annotations v1<->v2 conversions" (#107984)" This reverts commit 2b8c5bea1a6e5a54d761ce3e7bc7f5a1c529f60e. * make gen apps * e2e update * Use v2alpha2 by default (#108177) * Use v2alpha2 by default * Apply only DS changes to alpha2 * Use v2alpha2 by default except to query * Create a v2 index in @grafana/schema * Update path and apply lint * Update tests * Update imports to v2 status * Fix failing openapi test * Schemav2 breaking changes: conversion implementation (#108224) * provision v2alpha1 dashboard * Run conversions for DS refactor * Run snapshot testing on conversions * Normalize output name * Update snapshots to include all panel and variable cases * fix lint * fix lint * fix test and go lint * more go lint --------- Co-authored-by: Ivan Ortega <ivanortegaalba@gmail.com> Co-authored-by: Haris Rozajac <haris.rozajac12@gmail.com> * Schema v2: Introduce group/datasource convention to GroupBy and AdHoc variable (#108237) * Schema v2: Introduce group/datasource convention to GroupBy and AdHoc variables * add conversion * App Installer: Authorizer support (#108419) * Chore: use `satisfies` and remove a load of `any`s (#108397) use satisfies and remove a load of anys * improve logging and fail unified-storage migration with more than 0 errors (#108471) improve logging and fail unified-storage migration with more than 0 errors * fix conversion test * Secrets: Create more granular fixed roles for SecureValues (#108382) * Provisioning: Fix bug in job progress recording (#108440) Fix bug in job progress recording * Provisioning: Fix ImportAllPanelsFromLocalRepository test (#108441) * Provisioning: Skip flaky test * Fix flaky provisioning test * Fix lint --------- Co-authored-by: Roberto Jimenez Sanchez <roberto.jimenez@grafana.com> * BulkDeleteProvisionedResource: Move progress bar into a second step (#108417) * Move progress bar into a second step --------- Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * [Dashboard Schema Codegen] Move dashboard CUE codegen block back up into kind body (#108476) [Dashboard Schema Codegen] Move dashboard CUE codegen block back up into kind body to make sure new versions have the same settings. --------- Co-authored-by: Haris Rozajac <haris.rozajac12@gmail.com> Co-authored-by: Todd Treece <360020+toddtreece@users.noreply.github.com> Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com> Co-authored-by: Will Assis <35489495+gassiss@users.noreply.github.com> Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com> Co-authored-by: Roberto Jiménez Sánchez <jszroberto@gmail.com> Co-authored-by: Roberto Jimenez Sanchez <roberto.jimenez@grafana.com> Co-authored-by: Yunwen Zheng <yunwen.zheng@grafana.com> Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> Co-authored-by: Austin Pond <IfSentient@users.noreply.github.com> Co-authored-by: Ivan Ortega <ivanortegaalba@gmail.com> * Dashboard Schema V2: Refactor VizConfigKind to follow DataQueryKind convention (#108148) * Dashboards API: Register v2alpha2 API * Prepare conversion functions * Fix test * Refactor VizConfigKind to follow DataQueryKind convention * fix tests * use new dataquerykind convention alpha 2 * add conversion * fix tests * fix tests * fix another test * Fix merge --------- Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com> * fix k8s codegen * Update e2e-playwright/dashboards/TestV2Dashboard.json * Update e2e/dashboards/TestV2Dashboard.json * revert app generation for non-related apps * try again * another try * also revert folder and secret app generation * v2alpha1 provisioned dashboard * Fix kind * Fix conversion snapshots * Update API discovery registry * Rename to v2beta1 * Rename migrations * Update apps/dashboard/pkg/apis/dashboard/v2beta1/doc.go Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com> * Ensure conditional rendering and other non changed properties --------- Co-authored-by: Ivan Ortega <ivanortegaalba@gmail.com> Co-authored-by: Haris Rozajac <haris.rozajac12@gmail.com> Co-authored-by: Todd Treece <360020+toddtreece@users.noreply.github.com> Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com> Co-authored-by: Will Assis <35489495+gassiss@users.noreply.github.com> Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com> Co-authored-by: Roberto Jiménez Sánchez <jszroberto@gmail.com> Co-authored-by: Roberto Jimenez Sanchez <roberto.jimenez@grafana.com> Co-authored-by: Yunwen Zheng <yunwen.zheng@grafana.com> Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> Co-authored-by: Austin Pond <IfSentient@users.noreply.github.com> Co-authored-by: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com>
2025-07-30 21:01:27 +08:00
import { RowsLayoutRowKind } from '@grafana/schema/dist/esm/schema/dashboard/v2';
import appEvents from 'app/core/app_events';
import { LS_ROW_COPY_KEY } from 'app/core/constants';
import store from 'app/core/store';
import kbn from 'app/core/utils/kbn';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
import { ShowConfirmModalEvent } from 'app/types/events';
import { ConditionalRendering } from '../../conditional-rendering/ConditionalRendering';
import { serializeRow } from '../../serialization/layoutSerializers/RowsLayoutSerializer';
import { getElements } from '../../serialization/layoutSerializers/utils';
import { getDashboardSceneFor } from '../../utils/utils';
import { AutoGridLayoutManager } from '../layout-auto-grid/AutoGridLayoutManager';
import { clearClipboard } from '../layouts-shared/paste';
import { scrollCanvasElementIntoView } from '../layouts-shared/scrollCanvasElementIntoView';
import { BulkActionElement } from '../types/BulkActionElement';
import { DashboardDropTarget } from '../types/DashboardDropTarget';
import { DashboardLayoutManager } from '../types/DashboardLayoutManager';
import { EditableDashboardElement, EditableDashboardElementInfo } from '../types/EditableDashboardElement';
import { LayoutParent } from '../types/LayoutParent';
import { useEditOptions } from './RowItemEditor';
import { RowItemRenderer } from './RowItemRenderer';
import { RowItems } from './RowItems';
import { RowsLayoutManager } from './RowsLayoutManager';
export interface RowItemState extends SceneObjectState {
layout: DashboardLayoutManager;
title?: string;
collapse?: boolean;
hideHeader?: boolean;
fillScreen?: boolean;
isDropTarget?: boolean;
conditionalRendering?: ConditionalRendering;
repeatByVariable?: string;
repeatedRows?: RowItem[];
}
export class RowItem
extends SceneObjectBase<RowItemState>
implements LayoutParent, BulkActionElement, EditableDashboardElement, DashboardDropTarget
{
public static Component = RowItemRenderer;
protected _variableDependency = new VariableDependencyConfig(this, {
statePaths: ['title'],
});
public readonly isEditableDashboardElement = true;
public readonly isDashboardDropTarget = true;
public containerRef: React.MutableRefObject<HTMLDivElement | null> = React.createRef<HTMLDivElement>();
public constructor(state?: Partial<RowItemState>) {
super({
...state,
title: state?.title ?? t('dashboard.rows-layout.row.new', 'New row'),
layout: state?.layout ?? AutoGridLayoutManager.createEmpty(),
conditionalRendering: state?.conditionalRendering ?? ConditionalRendering.createEmpty(),
});
this.addActivationHandler(() => this._activationHandler());
}
private _activationHandler() {
const deactivate = this.state.conditionalRendering?.activate();
return () => {
if (deactivate) {
deactivate();
}
};
}
public getEditableElementInfo(): EditableDashboardElementInfo {
return {
typeName: t('dashboard.edit-pane.elements.row', 'Row'),
instanceName: sceneGraph.interpolate(this, this.state.title, undefined, 'text'),
icon: 'list-ul',
};
}
public getOutlineChildren(): SceneObject[] {
return this.state.layout.getOutlineChildren();
}
public getLayout(): DashboardLayoutManager {
return this.state.layout;
}
public getSlug(): string {
return kbn.slugifyForUrl(sceneGraph.interpolate(this, this.state.title ?? 'Row'));
}
public switchLayout(layout: DashboardLayoutManager) {
this.setState({ layout });
}
public useEditPaneOptions(isNewElement: boolean): OptionsPaneCategoryDescriptor[] {
return useEditOptions(this, isNewElement);
}
public onDelete() {
this.getParentLayout().removeRow(this);
}
public onConfirmDelete() {
if (this.getLayout().getVizPanels().length === 0) {
this.onDelete();
return;
}
if (this.getParentLayout().shouldUngroup()) {
this.onDelete();
return;
}
appEvents.publish(
new ShowConfirmModalEvent({
title: t('dashboard.rows-layout.delete-row-title', 'Delete row?'),
text: t(
'dashboard.rows-layout.delete-row-text',
'Deleting this row will also remove all panels. Are you sure you want to continue?'
),
yesText: t('dashboard.rows-layout.delete-row-yes', 'Delete'),
onConfirm: () => {
this.onDelete();
},
})
);
}
public createMultiSelectedElement(items: SceneObject[]): RowItems {
return new RowItems(items.filter((item) => item instanceof RowItem));
}
public onDuplicate() {
this.getParentLayout().duplicateRow(this);
}
public duplicate(): RowItem {
return this.clone({ key: undefined, layout: this.getLayout().duplicate() });
}
public serialize(): RowsLayoutRowKind {
return serializeRow(this);
}
public onCopy() {
const elements = getElements(this.getLayout(), getDashboardSceneFor(this));
clearClipboard();
store.set(LS_ROW_COPY_KEY, JSON.stringify({ elements, row: this.serialize() }));
}
public setIsDropTarget(isDropTarget: boolean) {
if (!!this.state.isDropTarget !== isDropTarget) {
this.setState({ isDropTarget });
}
}
public draggedPanelOutside(panel: VizPanel) {
this.getLayout().removePanel?.(panel);
this.setIsDropTarget(false);
}
public draggedPanelInside(panel: VizPanel) {
panel.clearParent();
this.getLayout().addPanel(panel);
this.setIsDropTarget(false);
}
public onChangeTitle(title: string) {
this.setState({ title });
}
public onChangeName(name: string) {
this.onChangeTitle(name);
}
public onHeaderHiddenToggle(hideHeader = !this.state.hideHeader) {
this.setState({ hideHeader });
}
public onChangeFillScreen(fillScreen: boolean) {
this.setState({ fillScreen });
}
public onChangeRepeat(repeat: string | undefined) {
if (repeat) {
this.setState({ repeatByVariable: repeat });
} else {
this.setState({ repeatedRows: undefined, $variables: undefined, repeatByVariable: undefined });
}
}
public onCollapseToggle() {
this.setState({ collapse: !this.state.collapse });
}
public getParentLayout(): RowsLayoutManager {
return sceneGraph.getAncestor(this, RowsLayoutManager);
}
public scrollIntoView() {
scrollCanvasElementIntoView(this, this.containerRef);
}
public getCollapsedState(): boolean {
return this.state.collapse ?? false;
}
public setCollapsedState(collapse: boolean) {
this.setState({ collapse });
}
public hasUniqueTitle(): boolean {
const parentLayout = this.getParentLayout();
const duplicateTitles = parentLayout.duplicateTitles();
return !duplicateTitles.has(this.state.title);
}
}