import { css } from '@emotion/css'; import React, { useEffect, useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { config, locationService } from '@grafana/runtime'; import { Badge, Button, ButtonGroup, Dropdown, Icon, Menu, ToolbarButton, ToolbarButtonRow, useStyles2, } from '@grafana/ui'; import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbar/NavToolbarSeparator'; import { contextSrv } from 'app/core/core'; import { Trans, t } from 'app/core/internationalization'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; import { PanelEditor } from '../panel-edit/PanelEditor'; import { ShareModal } from '../sharing/ShareModal'; import { DashboardInteractions } from '../utils/interactions'; import { DynamicDashNavButtonModel, dynamicDashNavActions } from '../utils/registerDynamicDashNavAction'; import { DashboardScene } from './DashboardScene'; import { GoToSnapshotOriginButton } from './GoToSnapshotOriginButton'; import { LibraryVizPanel } from './LibraryVizPanel'; interface Props { dashboard: DashboardScene; } export const NavToolbarActions = React.memo(({ dashboard }) => { const actions = ; return ; }); NavToolbarActions.displayName = 'NavToolbarActions'; /** * This part is split into a separate component to help test this */ export function ToolbarActions({ dashboard }: Props) { const { isEditing, viewPanelScene, isDirty, uid, meta, editview, editPanel, hasCopiedPanel: copiedPanel, } = dashboard.useState(); const { isPlaying } = playlistSrv.useState(); const [isAddPanelMenuOpen, setIsAddPanelMenuOpen] = useState(false); const canSaveAs = contextSrv.hasEditPermissionInFolders; const toolbarActions: ToolbarAction[] = []; const styles = useStyles2(getStyles); const isEditingPanel = Boolean(editPanel); const isViewingPanel = Boolean(viewPanelScene); const isEditingLibraryPanel = useEditingLibraryPanel(editPanel); const hasCopiedPanel = Boolean(copiedPanel); // Means we are not in settings view, fullscreen panel or edit panel const isShowingDashboard = !editview && !isViewingPanel && !isEditingPanel; const isEditingAndShowingDashboard = isEditing && isShowingDashboard; if (!isEditingPanel) { // This adds the precence indicators in enterprise addDynamicActions(toolbarActions, dynamicDashNavActions.left, 'left-actions'); } toolbarActions.push({ group: 'icon-actions', condition: uid && Boolean(meta.canStar) && isShowingDashboard && !isEditing, render: () => { let desc = meta.isStarred ? t('dashboard.toolbar.unmark-favorite', 'Unmark as favorite') : t('dashboard.toolbar.mark-favorite', 'Mark as favorite'); return ( } key="star-dashboard-button" data-testid={selectors.components.NavToolbar.markAsFavorite} onClick={() => { DashboardInteractions.toolbarFavoritesClick(); dashboard.onStarDashboard(); }} /> ); }, }); if (meta.publicDashboardEnabled) { toolbarActions.push({ group: 'icon-actions', condition: uid && Boolean(meta.canStar) && isShowingDashboard && !isEditing, render: () => { return ( ); }, }); } const isDevEnv = config.buildInfo.env === 'development'; toolbarActions.push({ group: 'icon-actions', condition: isDevEnv && uid && isShowingDashboard && !isEditing, render: () => ( { locationService.partial({ scenes: false }); }} /> ), }); toolbarActions.push({ group: 'icon-actions', condition: meta.isSnapshot && !isEditing, render: () => ( ), }); if (!isEditingPanel && !isEditing) { // This adds the alert rules button and the dashboard insights button addDynamicActions(toolbarActions, dynamicDashNavActions.right, 'icon-actions'); } toolbarActions.push({ group: 'add-panel', condition: isEditingAndShowingDashboard, render: () => ( { setIsAddPanelMenuOpen(isOpen); DashboardInteractions.toolbarAddClick(); }} overlay={() => ( { const id = dashboard.onCreateNewPanel(); DashboardInteractions.toolbarAddButtonClicked({ item: 'add_visualization' }); locationService.partial({ editPanel: id }); }} /> { dashboard.onShowAddLibraryPanelDrawer(); DashboardInteractions.toolbarAddButtonClicked({ item: 'add_library_panel' }); }} /> { dashboard.onCreateNewRow(); DashboardInteractions.toolbarAddButtonClicked({ item: 'add_row' }); }} /> { dashboard.pastePanel(); DashboardInteractions.toolbarAddButtonClicked({ item: 'paste_panel' }); }} /> )} placement="bottom" offset={[0, 6]} > ), }); toolbarActions.push({ group: 'playlist-actions', condition: isPlaying && isShowingDashboard && !isEditing, render: () => ( playlistSrv.prev()} /> ), }); toolbarActions.push({ group: 'playlist-actions', condition: isPlaying && isShowingDashboard && !isEditing, render: () => ( playlistSrv.stop()} data-testid={selectors.pages.Dashboard.DashNav.playlistControls.stop} > Stop playlist ), }); toolbarActions.push({ group: 'playlist-actions', condition: isPlaying && isShowingDashboard && !isEditing, render: () => ( playlistSrv.next()} narrow /> ), }); toolbarActions.push({ group: 'back-button', condition: (isViewingPanel || isEditingPanel) && !isEditingLibraryPanel, render: () => ( ), }); toolbarActions.push({ group: 'back-button', condition: Boolean(editview), render: () => ( ), }); toolbarActions.push({ group: 'main-buttons', condition: uid && !isEditing && !meta.isSnapshot && !isPlaying, render: () => ( ), }); toolbarActions.push({ group: 'main-buttons', condition: !isEditing && dashboard.canEditDashboard() && !isViewingPanel && !isPlaying, render: () => ( ), }); toolbarActions.push({ group: 'settings', condition: isEditing && dashboard.canEditDashboard() && isShowingDashboard, render: () => ( ), }); toolbarActions.push({ group: 'main-buttons', condition: isEditing && !meta.isNew && isShowingDashboard, render: () => ( ), }); toolbarActions.push({ group: 'main-buttons', condition: isEditingPanel && !isEditingLibraryPanel && !editview && !meta.isNew && !isViewingPanel, render: () => ( ), }); toolbarActions.push({ group: 'main-buttons', condition: isEditingPanel && isEditingLibraryPanel && !editview && !isViewingPanel, render: () => ( ), }); toolbarActions.push({ group: 'main-buttons', condition: isEditingPanel && isEditingLibraryPanel && !editview && !isViewingPanel, render: () => ( ), }); toolbarActions.push({ group: 'main-buttons', condition: isEditingPanel && isEditingLibraryPanel && !editview && !isViewingPanel, render: () => ( ), }); toolbarActions.push({ group: 'main-buttons', condition: isEditing && !isEditingLibraryPanel && (meta.canSave || canSaveAs), render: () => { // if we only can save if (meta.isNew) { return ( ); } // If we only can save as copy if (canSaveAs && !meta.canSave && !meta.canMakeEditable) { return ( ); } // If we can do both save and save as copy we show a button group with dropdown menu const menu = ( { DashboardInteractions.toolbarSaveClick(); dashboard.openSaveDrawer({}); }} /> { DashboardInteractions.toolbarSaveAsClick(); dashboard.openSaveDrawer({ saveAsCopy: true }); }} /> ); return (