2025-03-27 23:48:05 +08:00
|
|
|
import React from 'react';
|
|
|
|
|
2025-06-12 17:03:52 +08:00
|
|
|
import { t } from '@grafana/i18n';
|
2025-03-27 21:52:26 +08:00
|
|
|
import {
|
|
|
|
sceneGraph,
|
|
|
|
SceneObject,
|
|
|
|
SceneObjectBase,
|
|
|
|
SceneObjectState,
|
|
|
|
VariableDependencyConfig,
|
|
|
|
VizPanel,
|
|
|
|
} from '@grafana/scenes';
|
2025-04-02 22:22:24 +08:00
|
|
|
import { RowsLayoutRowKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen';
|
2025-04-10 01:01:43 +08:00
|
|
|
import appEvents from 'app/core/app_events';
|
2025-04-02 20:35:51 +08:00
|
|
|
import { LS_ROW_COPY_KEY } from 'app/core/constants';
|
|
|
|
import store from 'app/core/store';
|
2025-03-24 20:50:37 +08:00
|
|
|
import kbn from 'app/core/utils/kbn';
|
2024-12-10 14:21:30 +08:00
|
|
|
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
2025-04-10 01:01:43 +08:00
|
|
|
import { ShowConfirmModalEvent } from 'app/types/events';
|
2024-12-10 14:21:30 +08:00
|
|
|
|
2025-03-13 15:25:55 +08:00
|
|
|
import { ConditionalRendering } from '../../conditional-rendering/ConditionalRendering';
|
2025-04-02 20:35:51 +08:00
|
|
|
import { serializeRow } from '../../serialization/layoutSerializers/RowsLayoutSerializer';
|
|
|
|
import { getElements } from '../../serialization/layoutSerializers/utils';
|
2025-05-12 20:16:25 +08:00
|
|
|
import { getDashboardSceneFor } from '../../utils/utils';
|
2025-04-10 15:47:33 +08:00
|
|
|
import { AutoGridLayoutManager } from '../layout-auto-grid/AutoGridLayoutManager';
|
2025-04-02 20:35:51 +08:00
|
|
|
import { clearClipboard } from '../layouts-shared/paste';
|
2025-03-27 23:48:05 +08:00
|
|
|
import { scrollCanvasElementIntoView } from '../layouts-shared/scrollCanvasElementIntoView';
|
2025-02-05 17:08:41 +08:00
|
|
|
import { BulkActionElement } from '../types/BulkActionElement';
|
2025-03-27 21:52:26 +08:00
|
|
|
import { DashboardDropTarget } from '../types/DashboardDropTarget';
|
2025-02-05 17:08:41 +08:00
|
|
|
import { DashboardLayoutManager } from '../types/DashboardLayoutManager';
|
2025-02-27 20:42:22 +08:00
|
|
|
import { EditableDashboardElement, EditableDashboardElementInfo } from '../types/EditableDashboardElement';
|
2025-02-05 17:08:41 +08:00
|
|
|
import { LayoutParent } from '../types/LayoutParent';
|
2024-12-10 14:21:30 +08:00
|
|
|
|
2025-04-10 19:28:07 +08:00
|
|
|
import { useEditOptions } from './RowItemEditor';
|
2025-02-07 18:57:54 +08:00
|
|
|
import { RowItemRenderer } from './RowItemRenderer';
|
|
|
|
import { RowItems } from './RowItems';
|
2024-12-10 14:21:30 +08:00
|
|
|
import { RowsLayoutManager } from './RowsLayoutManager';
|
|
|
|
|
|
|
|
export interface RowItemState extends SceneObjectState {
|
|
|
|
layout: DashboardLayoutManager;
|
|
|
|
title?: string;
|
2025-03-26 20:49:28 +08:00
|
|
|
collapse?: boolean;
|
|
|
|
hideHeader?: boolean;
|
2025-03-24 20:51:21 +08:00
|
|
|
fillScreen?: boolean;
|
2025-03-27 21:52:26 +08:00
|
|
|
isDropTarget?: boolean;
|
2025-03-13 15:25:55 +08:00
|
|
|
conditionalRendering?: ConditionalRendering;
|
2025-06-25 16:02:42 +08:00
|
|
|
repeatByVariable?: string;
|
|
|
|
repeatedRows?: RowItem[];
|
2024-12-10 14:21:30 +08:00
|
|
|
}
|
|
|
|
|
2025-02-03 23:21:38 +08:00
|
|
|
export class RowItem
|
|
|
|
extends SceneObjectBase<RowItemState>
|
2025-03-27 21:52:26 +08:00
|
|
|
implements LayoutParent, BulkActionElement, EditableDashboardElement, DashboardDropTarget
|
2025-02-03 23:21:38 +08:00
|
|
|
{
|
2025-02-07 18:57:54 +08:00
|
|
|
public static Component = RowItemRenderer;
|
|
|
|
|
2025-02-03 17:46:47 +08:00
|
|
|
protected _variableDependency = new VariableDependencyConfig(this, {
|
|
|
|
statePaths: ['title'],
|
|
|
|
});
|
|
|
|
|
2025-02-05 17:08:41 +08:00
|
|
|
public readonly isEditableDashboardElement = true;
|
2025-03-27 21:52:26 +08:00
|
|
|
public readonly isDashboardDropTarget = true;
|
2025-04-02 21:52:53 +08:00
|
|
|
public containerRef: React.MutableRefObject<HTMLDivElement | null> = React.createRef<HTMLDivElement>();
|
2024-12-10 14:21:30 +08:00
|
|
|
|
2025-02-07 18:57:54 +08:00
|
|
|
public constructor(state?: Partial<RowItemState>) {
|
|
|
|
super({
|
|
|
|
...state,
|
|
|
|
title: state?.title ?? t('dashboard.rows-layout.row.new', 'New row'),
|
2025-03-27 19:16:47 +08:00
|
|
|
layout: state?.layout ?? AutoGridLayoutManager.createEmpty(),
|
2025-03-13 15:25:55 +08:00
|
|
|
conditionalRendering: state?.conditionalRendering ?? ConditionalRendering.createEmpty(),
|
2025-02-07 18:57:54 +08:00
|
|
|
});
|
2025-03-13 15:25:55 +08:00
|
|
|
|
|
|
|
this.addActivationHandler(() => this._activationHandler());
|
|
|
|
}
|
|
|
|
|
|
|
|
private _activationHandler() {
|
|
|
|
const deactivate = this.state.conditionalRendering?.activate();
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
if (deactivate) {
|
|
|
|
deactivate();
|
|
|
|
}
|
|
|
|
};
|
2025-02-07 18:57:54 +08:00
|
|
|
}
|
2025-02-03 17:46:47 +08:00
|
|
|
|
2025-02-27 20:42:22 +08:00
|
|
|
public getEditableElementInfo(): EditableDashboardElementInfo {
|
2025-03-10 22:03:54 +08:00
|
|
|
return {
|
|
|
|
typeName: t('dashboard.edit-pane.elements.row', 'Row'),
|
|
|
|
instanceName: sceneGraph.interpolate(this, this.state.title, undefined, 'text'),
|
2025-03-26 19:26:55 +08:00
|
|
|
icon: 'list-ul',
|
2025-03-10 22:03:54 +08:00
|
|
|
};
|
2025-02-27 20:42:22 +08:00
|
|
|
}
|
|
|
|
|
2025-06-25 16:02:42 +08:00
|
|
|
public getOutlineChildren(): SceneObject[] {
|
|
|
|
return this.state.layout.getOutlineChildren();
|
|
|
|
}
|
|
|
|
|
2025-02-07 18:57:54 +08:00
|
|
|
public getLayout(): DashboardLayoutManager {
|
|
|
|
return this.state.layout;
|
|
|
|
}
|
2025-02-03 17:46:47 +08:00
|
|
|
|
2025-03-24 20:50:37 +08:00
|
|
|
public getSlug(): string {
|
|
|
|
return kbn.slugifyForUrl(sceneGraph.interpolate(this, this.state.title ?? 'Row'));
|
|
|
|
}
|
|
|
|
|
2025-02-07 18:57:54 +08:00
|
|
|
public switchLayout(layout: DashboardLayoutManager) {
|
2025-05-26 17:12:25 +08:00
|
|
|
this.setState({ layout });
|
2025-02-07 18:57:54 +08:00
|
|
|
}
|
2024-12-10 14:21:30 +08:00
|
|
|
|
2025-04-10 19:28:07 +08:00
|
|
|
public useEditPaneOptions(isNewElement: boolean): OptionsPaneCategoryDescriptor[] {
|
|
|
|
return useEditOptions(this, isNewElement);
|
2024-12-10 14:21:30 +08:00
|
|
|
}
|
|
|
|
|
2025-02-07 18:57:54 +08:00
|
|
|
public onDelete() {
|
2025-04-02 21:52:53 +08:00
|
|
|
this.getParentLayout().removeRow(this);
|
2024-12-10 14:21:30 +08:00
|
|
|
}
|
|
|
|
|
2025-04-10 01:01:43 +08:00
|
|
|
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();
|
|
|
|
},
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2025-02-07 18:57:54 +08:00
|
|
|
public createMultiSelectedElement(items: SceneObject[]): RowItems {
|
|
|
|
return new RowItems(items.filter((item) => item instanceof RowItem));
|
2024-12-10 14:21:30 +08:00
|
|
|
}
|
|
|
|
|
2025-03-20 19:39:48 +08:00
|
|
|
public onDuplicate() {
|
2025-04-02 21:52:53 +08:00
|
|
|
this.getParentLayout().duplicateRow(this);
|
2025-03-20 19:39:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
public duplicate(): RowItem {
|
|
|
|
return this.clone({ key: undefined, layout: this.getLayout().duplicate() });
|
|
|
|
}
|
|
|
|
|
2025-04-02 20:35:51 +08:00
|
|
|
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() }));
|
|
|
|
}
|
|
|
|
|
2025-03-27 21:52:26 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2025-02-07 18:57:54 +08:00
|
|
|
public onChangeTitle(title: string) {
|
2025-04-10 19:28:07 +08:00
|
|
|
this.setState({ title });
|
2025-02-07 18:57:54 +08:00
|
|
|
}
|
2024-12-10 14:21:30 +08:00
|
|
|
|
2025-04-11 01:23:36 +08:00
|
|
|
public onChangeName(name: string) {
|
|
|
|
this.onChangeTitle(name);
|
|
|
|
}
|
|
|
|
|
2025-03-26 20:49:28 +08:00
|
|
|
public onHeaderHiddenToggle(hideHeader = !this.state.hideHeader) {
|
|
|
|
this.setState({ hideHeader });
|
2025-02-07 18:57:54 +08:00
|
|
|
}
|
2024-12-10 14:21:30 +08:00
|
|
|
|
2025-03-24 20:51:21 +08:00
|
|
|
public onChangeFillScreen(fillScreen: boolean) {
|
|
|
|
this.setState({ fillScreen });
|
2025-02-07 18:57:54 +08:00
|
|
|
}
|
2024-12-10 14:21:30 +08:00
|
|
|
|
2025-02-07 18:57:54 +08:00
|
|
|
public onChangeRepeat(repeat: string | undefined) {
|
|
|
|
if (repeat) {
|
2025-06-25 16:02:42 +08:00
|
|
|
this.setState({ repeatByVariable: repeat });
|
2025-02-07 18:57:54 +08:00
|
|
|
} else {
|
2025-06-25 16:02:42 +08:00
|
|
|
this.setState({ repeatedRows: undefined, $variables: undefined, repeatByVariable: undefined });
|
2025-02-07 18:57:54 +08:00
|
|
|
}
|
|
|
|
}
|
2025-02-03 17:46:47 +08:00
|
|
|
|
2025-02-07 18:57:54 +08:00
|
|
|
public onCollapseToggle() {
|
2025-03-26 20:49:28 +08:00
|
|
|
this.setState({ collapse: !this.state.collapse });
|
2025-02-07 18:57:54 +08:00
|
|
|
}
|
2025-02-03 17:46:47 +08:00
|
|
|
|
2025-04-02 21:52:53 +08:00
|
|
|
public getParentLayout(): RowsLayoutManager {
|
2025-03-05 19:18:41 +08:00
|
|
|
return sceneGraph.getAncestor(this, RowsLayoutManager);
|
|
|
|
}
|
|
|
|
|
2025-03-27 23:48:05 +08:00
|
|
|
public scrollIntoView() {
|
|
|
|
scrollCanvasElementIntoView(this, this.containerRef);
|
|
|
|
}
|
2025-04-03 16:13:43 +08:00
|
|
|
|
|
|
|
public getCollapsedState(): boolean {
|
|
|
|
return this.state.collapse ?? false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public setCollapsedState(collapse: boolean) {
|
|
|
|
this.setState({ collapse });
|
|
|
|
}
|
2025-04-03 19:31:07 +08:00
|
|
|
|
|
|
|
public hasUniqueTitle(): boolean {
|
|
|
|
const parentLayout = this.getParentLayout();
|
|
|
|
const duplicateTitles = parentLayout.duplicateTitles();
|
|
|
|
return !duplicateTitles.has(this.state.title);
|
|
|
|
}
|
2025-02-03 17:46:47 +08:00
|
|
|
}
|