mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
NewPanelEditor: Enable new edit mode (#23405)
* WIP: initial commit to transition to new edit mode * More old edit cleanup * Minor update * Refactoring url edit/fullscreen state to simplify logic, now seperate states * Fixed tests and part of the explore integration * Updated snapshot * Fix alert rule links * Fixed issue going back from explore * Updated snapshots * Fixes and changes * Fixed bridge srv issue * Fixed add panel issue * Removed console log * Removed render * Tests: fixes e2e smoketest * Make description optional * Fixed typings * e2e fixes * removed import * updated snapshot Co-authored-by: Hugo Häggmark <hugo.haggmark@grafana.com>
This commit is contained in:
parent
f9c0c22d85
commit
5a4f690807
@ -18,6 +18,7 @@ else
|
||||
|
||||
cp -r ./bin $RUNDIR
|
||||
cp -r ./public $RUNDIR
|
||||
cp -r ./tools $RUNDIR
|
||||
|
||||
mkdir $RUNDIR/conf
|
||||
mkdir $PROV_DIR
|
||||
@ -25,7 +26,6 @@ else
|
||||
mkdir $PROV_DIR/dashboards
|
||||
|
||||
cp ./conf/defaults.ini $RUNDIR/conf/defaults.ini
|
||||
cp ./conf/sample.ini $RUNDIR/conf/custom.ini
|
||||
fi
|
||||
|
||||
echo -e "Copy provisioning setup from devenv"
|
||||
|
@ -209,7 +209,7 @@ const assertAdding3dependantQueryVariablesScenario = (queryVariables: QueryVaria
|
||||
e2e.pages.SaveDashboardModal.save().click();
|
||||
e2e.flows.assertSuccessNotification();
|
||||
|
||||
e2e.pages.Dashboard.Toolbar.backArrow().click();
|
||||
e2e.pages.Components.BackButton.backArrow().click();
|
||||
|
||||
assertVariableLabelsAndComponents(asserts);
|
||||
|
||||
@ -264,7 +264,7 @@ const assertDuplicateItem = (queryVariables: QueryVariableData[]) => {
|
||||
e2e.pages.SaveDashboardModal.save().click();
|
||||
e2e.flows.assertSuccessNotification();
|
||||
|
||||
e2e.pages.Dashboard.Toolbar.backArrow().click();
|
||||
e2e.pages.Components.BackButton.backArrow().click();
|
||||
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemLabels(newItem.label).should('be.visible');
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts(newItem.selectedOption)
|
||||
@ -300,7 +300,7 @@ const assertDeleteItem = (queryVariables: QueryVariableData[]) => {
|
||||
e2e.pages.SaveDashboardModal.save().click();
|
||||
e2e.flows.assertSuccessNotification();
|
||||
|
||||
e2e.pages.Dashboard.Toolbar.backArrow().click();
|
||||
e2e.pages.Components.BackButton.backArrow().click();
|
||||
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemLabels(itemToDelete.label).should('not.exist');
|
||||
|
||||
@ -347,7 +347,7 @@ const assertUpdateItem = (data: QueryVariableData[]) => {
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalHideSelect().select('');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.ConstantVariable.constantOptionsQueryInput().type(updatedItem.query);
|
||||
|
||||
e2e.pages.Dashboard.Toolbar.backArrow().click();
|
||||
e2e.pages.Components.BackButton.backArrow().click();
|
||||
|
||||
e2e()
|
||||
.window()
|
||||
@ -407,7 +407,7 @@ const assertMoveDownItem = (data: QueryVariableData[]) => {
|
||||
});
|
||||
});
|
||||
|
||||
e2e.pages.Dashboard.Toolbar.backArrow().click();
|
||||
e2e.pages.Components.BackButton.backArrow().click();
|
||||
|
||||
assertVariableLabelsAndComponents(queryVariables);
|
||||
|
||||
@ -552,7 +552,7 @@ const assertMoveUpItem = (data: QueryVariableData[]) => {
|
||||
});
|
||||
});
|
||||
|
||||
e2e.pages.Dashboard.Toolbar.backArrow().click();
|
||||
e2e.pages.Components.BackButton.backArrow().click();
|
||||
|
||||
assertVariableLabelsAndComponents(queryVariables);
|
||||
|
||||
|
@ -15,48 +15,15 @@ e2e.scenario({
|
||||
e2e.pages.Dashboard.Toolbar.toolbarItems('Add panel').click();
|
||||
e2e.pages.AddDashboard.ctaButtons('Add Query').click();
|
||||
|
||||
e2e.pages.Dashboard.Panels.EditPanel.tabItems('Queries').click();
|
||||
e2e.pages.Dashboard.Panels.DataSource.TestData.QueryTab.scenarioSelect().select('CSV Metric Values');
|
||||
|
||||
e2e.pages.Dashboard.Panels.EditPanel.tabItems('Visualization').click();
|
||||
|
||||
e2e.pages.Dashboard.Panels.Visualization.Graph.VisualizationTab.xAxisSection()
|
||||
.contains('Show')
|
||||
.click();
|
||||
|
||||
e2e.flows.saveDashboard();
|
||||
e2e.pages.Components.BackButton.backArrow().click();
|
||||
|
||||
e2e.pages.Dashboard.Toolbar.backArrow().click();
|
||||
|
||||
e2e.pages.Dashboard.Panels.Panel.title('Panel Title').click();
|
||||
|
||||
e2e.pages.Dashboard.Panels.Panel.headerItems('Share').click();
|
||||
|
||||
e2e.pages.SharePanelModal.linkToRenderedImage().then(($a: any) => {
|
||||
// extract the fully qualified href property
|
||||
const url = $a.prop('href');
|
||||
|
||||
// Test that the image renderer returns 200 OK
|
||||
e2e().request({ method: 'GET', url, timeout: 120000 });
|
||||
|
||||
// Download image
|
||||
if (!e2e.env('CIRCLE_SHA1')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const theOutputImage = `${e2e.config().screenshotsFolder}/received/smoke-test-scenario.png`;
|
||||
const theTruthImage = `${e2e.config().screenshotsFolder}/expected/smoke-test-scenario.png`;
|
||||
|
||||
e2e().wrap(
|
||||
e2e.imgSrcToBlob(url).then((blob: any) => {
|
||||
e2e.blobToBase64String(blob).then((base64String: string) => {
|
||||
const data = base64String.replace(/^data:image\/\w+;base64,/, '');
|
||||
e2e().writeFile(theOutputImage, data, 'base64');
|
||||
});
|
||||
})
|
||||
);
|
||||
e2e().wait(1000); // give the io a chance to flush image to disk
|
||||
e2e().compareSnapshot({ pathToFileA: theOutputImage, pathToFileB: theTruthImage });
|
||||
});
|
||||
// e2e.pages.Dashboard.Panels.Panel.title('Panel Title').click();
|
||||
// e2e.pages.Dashboard.Panels.Panel.headerItems('Inspect').click();
|
||||
},
|
||||
});
|
||||
|
@ -49,7 +49,7 @@ export interface OptionsEditorItem<TOptions, TSettings, TEditorProps, TValue> ex
|
||||
interface OptionEditorConfig<TOptions, TSettings, TValue = any> {
|
||||
id: keyof TOptions & string;
|
||||
name: string;
|
||||
description: string;
|
||||
description?: string;
|
||||
settings?: TSettings;
|
||||
defaultValue?: TValue;
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ export interface FieldConfigEditorConfig<TOptions, TSettings = any, TValue = any
|
||||
/**
|
||||
* Description of the field config property. Will be displayed in the UI as form element description.
|
||||
*/
|
||||
description: string;
|
||||
description?: string;
|
||||
/**
|
||||
* Array of strings representing category of the field config property. First element in the array will make option render as collapsible section.
|
||||
*/
|
||||
|
@ -142,7 +142,7 @@ export interface PanelOptionsEditorConfig<TOptions, TSettings = any, TValue = an
|
||||
/**
|
||||
* Description of the option. Will be displayed in the UI as form element description.
|
||||
*/
|
||||
description: string;
|
||||
description?: string;
|
||||
/**al
|
||||
* Custom settings of the editor.
|
||||
*/
|
||||
|
@ -3,7 +3,7 @@ import { SelectableValue } from '../types/select';
|
||||
export interface RegistryItem {
|
||||
id: string; // Unique Key -- saved in configs
|
||||
name: string; // Display Name, can change without breaking configs
|
||||
description: string;
|
||||
description?: string;
|
||||
aliasIds?: string[]; // when the ID changes, we may want backwards compatibility ('current' => 'last')
|
||||
|
||||
/**
|
||||
|
@ -4,7 +4,6 @@ export const Dashboard = pageFactory({
|
||||
url: (uid: string) => `/d/${uid}`,
|
||||
selectors: {
|
||||
toolbarItems: (button: string) => `Dashboard navigation bar button ${button}`,
|
||||
backArrow: 'Dashboard settings Go Back button',
|
||||
navBar: () => '.navbar',
|
||||
},
|
||||
});
|
||||
|
@ -15,6 +15,7 @@ import { SaveDashboardModal } from './saveDashboardModal';
|
||||
import { Panel } from './panel';
|
||||
import { SharePanelModal } from './sharePanelModal';
|
||||
import { ConstantVariable, QueryVariable, VariableGeneral, Variables, VariablesSubMenu } from './variables';
|
||||
import { pageFactory } from '../support';
|
||||
|
||||
export const Pages = {
|
||||
Login,
|
||||
@ -53,4 +54,11 @@ export const Pages = {
|
||||
SaveDashboardAsModal,
|
||||
SaveDashboardModal,
|
||||
SharePanelModal,
|
||||
Components: {
|
||||
BackButton: pageFactory({
|
||||
selectors: {
|
||||
backArrow: 'Go Back button',
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
@ -100,7 +100,7 @@ export const DataLinksInlineEditor: React.FC<DataLinksInlineEditorProps> = ({ li
|
||||
)}
|
||||
|
||||
<FullWidthButtonContainer>
|
||||
<Button size="sm" icon="plus-circle" onClick={onDataLinkAdd} variant="secondary">
|
||||
<Button size="sm" icon="plus" onClick={onDataLinkAdd} variant="secondary">
|
||||
Add link
|
||||
</Button>
|
||||
</FullWidthButtonContainer>
|
||||
|
@ -200,7 +200,7 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<FullWidthButtonContainer className={styles.addButton}>
|
||||
<Button size="sm" icon="plus-circle" onClick={() => this.onAddThreshold()} variant="secondary">
|
||||
<Button size="sm" icon="plus" onClick={() => this.onAddThreshold()} variant="secondary">
|
||||
Add threshold
|
||||
</Button>
|
||||
</FullWidthButtonContainer>
|
||||
|
@ -68,7 +68,7 @@ export const ValueMappingsEditor: React.FC<Props> = ({ valueMappings, onChange,
|
||||
<FullWidthButtonContainer>
|
||||
<Button
|
||||
size="sm"
|
||||
icon="plus-circle"
|
||||
icon="plus"
|
||||
onClick={onAdd}
|
||||
aria-label="ValueMappingsEditor add mapping button"
|
||||
variant="secondary"
|
||||
|
@ -112,7 +112,7 @@ func (c *EvalContext) GetDashboardUID() (*models.DashboardRef, error) {
|
||||
return c.dashboardRef, nil
|
||||
}
|
||||
|
||||
const urlFormat = "%s?fullscreen&edit&tab=alert&panelId=%d&orgId=%d"
|
||||
const urlFormat = "%s?tab=alert&editPanel=%d&orgId=%d"
|
||||
|
||||
// GetRuleURL returns the url to the dashboard containing the alert.
|
||||
func (c *EvalContext) GetRuleURL() (string, error) {
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React, { ButtonHTMLAttributes } from 'react';
|
||||
import { css } from 'emotion';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { stylesFactory, useTheme, Tooltip, selectThemeVariant } from '@grafana/ui';
|
||||
import { selectThemeVariant, stylesFactory, Tooltip, useTheme } from '@grafana/ui';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
export type Props = ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
|
||||
@ -11,7 +12,7 @@ export const BackButton: React.FC<Props> = props => {
|
||||
|
||||
return (
|
||||
<Tooltip content="Go back (Esc)" placement="bottom">
|
||||
<button className={styles.wrapper} {...props}>
|
||||
<button className={styles.wrapper} {...props} aria-label={e2e.pages.Components.BackButton.selectors.backArrow}>
|
||||
<i className="gicon gicon-arrow-left" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
@ -20,7 +20,7 @@ export class SideMenu extends PureComponent {
|
||||
<Branding.MenuLogo />
|
||||
</a>,
|
||||
<div className="sidemenu__logo_small_breakpoint" onClick={this.toggleSideMenuSmallBreakpoint} key="hamburger">
|
||||
<Icon name="bars" />
|
||||
<Icon name="bars" size="xl" />
|
||||
<span className="sidemenu__close">
|
||||
<Icon name="times" />
|
||||
Close
|
||||
|
@ -16,6 +16,7 @@ Array [
|
||||
>
|
||||
<Icon
|
||||
name="bars"
|
||||
size="xl"
|
||||
/>
|
||||
<span
|
||||
className="sidemenu__close"
|
||||
|
@ -15,6 +15,7 @@ export class BridgeSrv {
|
||||
private fullPageReloadRoutes: string[];
|
||||
private lastQuery: UrlQueryMap = {};
|
||||
private lastPath = '';
|
||||
private angularUrl: string;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
@ -26,13 +27,16 @@ export class BridgeSrv {
|
||||
private variableSrv: VariableSrv
|
||||
) {
|
||||
this.fullPageReloadRoutes = ['/logout'];
|
||||
this.angularUrl = $location.url();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.$rootScope.$on('$routeUpdate', (evt, data) => {
|
||||
const angularUrl = this.$location.url();
|
||||
const state = store.getState();
|
||||
if (state.location.url !== angularUrl) {
|
||||
|
||||
this.angularUrl = this.$location.url();
|
||||
|
||||
if (state.location.url !== this.angularUrl) {
|
||||
store.dispatch(
|
||||
updateLocation({
|
||||
path: this.$location.path(),
|
||||
@ -44,6 +48,8 @@ export class BridgeSrv {
|
||||
});
|
||||
|
||||
this.$rootScope.$on('$routeChangeSuccess', (evt, data) => {
|
||||
this.angularUrl = this.$location.url();
|
||||
|
||||
store.dispatch(
|
||||
updateLocation({
|
||||
path: this.$location.path(),
|
||||
@ -56,9 +62,12 @@ export class BridgeSrv {
|
||||
// Listen for changes in redux location -> update angular location
|
||||
store.subscribe(() => {
|
||||
const state = store.getState();
|
||||
const angularUrl = this.$location.url();
|
||||
const url = state.location.url;
|
||||
if (angularUrl !== url) {
|
||||
|
||||
if (this.angularUrl !== url) {
|
||||
// store angular url right away as otherwise we end up syncing multiple times
|
||||
this.angularUrl = url;
|
||||
|
||||
this.$timeout(() => {
|
||||
this.$location.url(url);
|
||||
// some state changes should not trigger new browser history
|
||||
@ -66,6 +75,7 @@ export class BridgeSrv {
|
||||
this.$location.replace();
|
||||
}
|
||||
});
|
||||
|
||||
console.log('store updating angular $location.url', url);
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,6 @@ import { store } from 'app/store/store';
|
||||
import { AppEventEmitter, CoreEvents } from 'app/types';
|
||||
|
||||
import Mousetrap from 'mousetrap';
|
||||
import { PanelEvents } from '@grafana/data';
|
||||
import 'mousetrap-global-bind';
|
||||
import { ContextSrv } from './context_srv';
|
||||
import { ILocationService, IRootScopeService, ITimeoutService } from 'angular';
|
||||
@ -133,8 +132,9 @@ export class KeybindingSrv {
|
||||
return;
|
||||
}
|
||||
|
||||
if (search.fullscreen) {
|
||||
appEvents.emit(PanelEvents.panelChangeView, { fullscreen: false, edit: false });
|
||||
if (search.viewPanel) {
|
||||
delete search.viewPanel;
|
||||
this.$location.search(search);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -213,23 +213,16 @@ export class KeybindingSrv {
|
||||
// edit panel
|
||||
this.bind('e', () => {
|
||||
if (dashboard.canEditPanelById(dashboard.meta.focusPanelId)) {
|
||||
appEvents.emit(PanelEvents.panelChangeView, {
|
||||
fullscreen: true,
|
||||
edit: true,
|
||||
panelId: dashboard.meta.focusPanelId,
|
||||
toggle: true,
|
||||
});
|
||||
const search = _.extend(this.$location.search(), { editPanel: dashboard.meta.focusPanelId });
|
||||
this.$location.search(search);
|
||||
}
|
||||
});
|
||||
|
||||
// view panel
|
||||
this.bind('v', () => {
|
||||
if (dashboard.meta.focusPanelId) {
|
||||
appEvents.emit(PanelEvents.panelChangeView, {
|
||||
fullscreen: true,
|
||||
panelId: dashboard.meta.focusPanelId,
|
||||
toggle: true,
|
||||
});
|
||||
const search = _.extend(this.$location.search(), { viewPanel: dashboard.meta.focusPanelId });
|
||||
this.$location.search(search);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -15,7 +15,6 @@ import {
|
||||
LogRowModel,
|
||||
LogsDedupStrategy,
|
||||
LogsModel,
|
||||
PanelModel,
|
||||
RawTimeRange,
|
||||
TimeFragment,
|
||||
TimeRange,
|
||||
@ -33,6 +32,7 @@ import { ExploreUrlState, QueryOptions, QueryTransaction } from 'app/types/explo
|
||||
import { config } from '../config';
|
||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import { DataSourceSrv } from '@grafana/runtime';
|
||||
import { PanelModel } from 'app/features/dashboard/state';
|
||||
|
||||
export const DEFAULT_RANGE = {
|
||||
from: 'now-1h',
|
||||
@ -105,7 +105,7 @@ export async function getExploreUrl(args: GetExploreUrlArguments): Promise<strin
|
||||
};
|
||||
}
|
||||
|
||||
const exploreState = JSON.stringify({ ...state, originPanelId: panel.id });
|
||||
const exploreState = JSON.stringify({ ...state, originPanelId: panel.getSavedId() });
|
||||
url = renderUrl('/explore', { left: exploreState });
|
||||
}
|
||||
return url;
|
||||
|
@ -24,7 +24,7 @@ class AlertRuleItem extends PureComponent<Props> {
|
||||
render() {
|
||||
const { rule, onTogglePause } = this.props;
|
||||
|
||||
const ruleUrl = `${rule.url}?panelId=${rule.panelId}&fullscreen&edit&tab=alert`;
|
||||
const ruleUrl = `${rule.url}?editPanel=${rule.panelId}&tab=alert`;
|
||||
|
||||
return (
|
||||
<li className="alert-rule-item">
|
||||
|
@ -19,7 +19,7 @@ exports[`Render should render component 1`] = `
|
||||
className="alert-rule-item__name"
|
||||
>
|
||||
<a
|
||||
href="https://something.something.darkside?panelId=1&fullscreen&edit&tab=alert"
|
||||
href="https://something.something.darkside?editPanel=1&tab=alert"
|
||||
>
|
||||
<Highlighter
|
||||
highlightClassName="highlight-search-match"
|
||||
@ -71,7 +71,7 @@ exports[`Render should render component 1`] = `
|
||||
</button>
|
||||
<a
|
||||
className="btn btn-small btn-inverse alert-list__btn width-2"
|
||||
href="https://something.something.darkside?panelId=1&fullscreen&edit&tab=alert"
|
||||
href="https://something.something.darkside?editPanel=1&tab=alert"
|
||||
title="Edit alert rule"
|
||||
>
|
||||
<Icon
|
||||
|
@ -36,8 +36,8 @@ export class AnnotationsSrv {
|
||||
.then(results => {
|
||||
// combine the annotations and flatten results
|
||||
let annotations: AnnotationEvent[] = flattenDeep(results[0]);
|
||||
// when in edit mode we need to use editSourceId
|
||||
let panelFilterId = options.panel.editSourceId ?? options.panel.id;
|
||||
// when in edit mode we need to use this function to get the saved id
|
||||
let panelFilterId = options.panel.getSavedId();
|
||||
|
||||
// filter out annotations that do not belong to requesting panel
|
||||
annotations = annotations.filter(item => {
|
||||
|
@ -86,16 +86,13 @@ export class AddPanelWidgetUnconnected extends React.Component<Props, State> {
|
||||
|
||||
const location: LocationUpdate = {
|
||||
query: {
|
||||
panelId: newPanel.id,
|
||||
edit: true,
|
||||
fullscreen: true,
|
||||
editPanel: newPanel.id,
|
||||
},
|
||||
partial: true,
|
||||
};
|
||||
|
||||
if (tab === 'visualization') {
|
||||
location.query.tab = 'visualization';
|
||||
location.query.openVizPicker = true;
|
||||
if (tab === 'visualize') {
|
||||
location.query.tab = 'visualize';
|
||||
}
|
||||
|
||||
reduxStore.dispatch(updateLocation(location));
|
||||
@ -173,10 +170,8 @@ export class AddPanelWidgetUnconnected extends React.Component<Props, State> {
|
||||
</div>
|
||||
<div className="add-panel-widget__btn-container">
|
||||
<div className="add-panel-widget__create">
|
||||
{this.renderOptionLink('search', 'Add Query', this.onCreateNewPanel)}
|
||||
{this.renderOptionLink('chart-line', 'Choose Visualization', () =>
|
||||
this.onCreateNewPanel('visualization')
|
||||
)}
|
||||
{this.renderOptionLink('database', 'Add Query', this.onCreateNewPanel)}
|
||||
{this.renderOptionLink('chart-line', 'Choose Visualization', () => this.onCreateNewPanel('visualize'))}
|
||||
</div>
|
||||
<div className="add-panel-widget__actions">
|
||||
<button className="btn btn-inverse add-panel-widget__action" onClick={this.onCreateNewRow}>
|
||||
|
@ -37,7 +37,7 @@
|
||||
|
||||
.add-panel-widget__link {
|
||||
margin: 0 $space-sm;
|
||||
width: 160px;
|
||||
width: 170px;
|
||||
height: 88px !important;
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ exports[`Render should render component 1`] = `
|
||||
className="add-panel-widget__icon"
|
||||
>
|
||||
<Icon
|
||||
name="search"
|
||||
name="database"
|
||||
size="xl"
|
||||
/>
|
||||
</div>
|
||||
|
@ -2,7 +2,6 @@
|
||||
import React, { PureComponent, FC } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { css } from 'emotion';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
// Utils & Services
|
||||
import { appEvents } from 'app/core/app_events';
|
||||
import { PlaylistSrv } from 'app/features/playlist/playlist_srv';
|
||||
@ -22,7 +21,6 @@ import { sanitizeUrl } from 'app/core/utils/text';
|
||||
|
||||
export interface OwnProps {
|
||||
dashboard: DashboardModel;
|
||||
isEditing: boolean;
|
||||
isFullscreen: boolean;
|
||||
$injector: any;
|
||||
updateLocation: typeof updateLocation;
|
||||
@ -61,7 +59,7 @@ class DashNav extends PureComponent<Props> {
|
||||
|
||||
onClose = () => {
|
||||
this.props.updateLocation({
|
||||
query: { panelId: null, edit: null, fullscreen: null, tab: null },
|
||||
query: { edit: null, viewPanel: null },
|
||||
partial: true,
|
||||
});
|
||||
};
|
||||
@ -140,7 +138,7 @@ class DashNav extends PureComponent<Props> {
|
||||
renderBackButton() {
|
||||
return (
|
||||
<div className="navbar-edit">
|
||||
<BackButton onClick={this.onClose} aria-label={e2e.pages.Dashboard.Toolbar.selectors.backArrow} />
|
||||
<BackButton onClick={this.onClose} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import { AngularComponent, getAngularLoader } from '@grafana/runtime';
|
||||
// Types
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import { BackButton } from 'app/core/components/BackButton/BackButton';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
import { CustomScrollbar } from '@grafana/ui';
|
||||
|
||||
@ -51,7 +50,7 @@ export class DashboardSettings extends PureComponent<Props> {
|
||||
<div className="dashboard-settings">
|
||||
<div className="navbar navbar--shadow">
|
||||
<div className="navbar-edit">
|
||||
<BackButton onClick={this.onClose} aria-label={e2e.pages.Dashboard.Toolbar.selectors.backArrow} />
|
||||
<BackButton onClick={this.onClose} />
|
||||
</div>
|
||||
<div className="navbar-page-btn">
|
||||
{haveFolder && <div className="navbar-page-btn__folder">{folderTitle} / </div>}
|
||||
|
@ -89,7 +89,7 @@ export const OverrideFieldConfigEditor: React.FC<Props> = props => {
|
||||
const renderAddOverride = () => {
|
||||
return (
|
||||
<ValuePicker
|
||||
icon="plus-circle"
|
||||
icon="plus"
|
||||
label="Add override"
|
||||
variant="secondary"
|
||||
options={fieldMatchersUI
|
||||
|
@ -169,9 +169,8 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
<DashboardPanel
|
||||
dashboard={dashboard}
|
||||
panel={panel}
|
||||
isEditing={false}
|
||||
isInEditMode
|
||||
isFullscreen={false}
|
||||
isEditing={true}
|
||||
isViewing={false}
|
||||
isInView={true}
|
||||
/>
|
||||
</div>
|
||||
@ -410,6 +409,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme, props: Props) => {
|
||||
cursor: col-resize;
|
||||
width: ${paneSpaceing};
|
||||
border-right-width: 1px;
|
||||
margin-top: 18px;
|
||||
`
|
||||
),
|
||||
resizerH: cx(
|
||||
@ -418,9 +418,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme, props: Props) => {
|
||||
height: ${paneSpaceing};
|
||||
cursor: row-resize;
|
||||
position: relative;
|
||||
top: 49px;
|
||||
top: 0px;
|
||||
z-index: 1;
|
||||
border-top-width: 1px;
|
||||
margin-left: ${paneSpaceing};
|
||||
`
|
||||
),
|
||||
tabsWrapper: css`
|
||||
|
@ -14,7 +14,7 @@ import store from '../../../../../core/store';
|
||||
|
||||
export function initPanelEditor(sourcePanel: PanelModel, dashboard: DashboardModel): ThunkResult<void> {
|
||||
return dispatch => {
|
||||
const panel = dashboard.initPanelEditor(sourcePanel);
|
||||
const panel = dashboard.initEditPanel(sourcePanel);
|
||||
|
||||
const queryRunner = panel.getQueryRunner();
|
||||
const querySubscription = queryRunner.getData(false).subscribe({
|
||||
|
@ -62,8 +62,6 @@ function dashboardPageScenario(description: string, scenarioFn: (ctx: ScenarioCo
|
||||
urlUid: '11',
|
||||
$injector: {},
|
||||
routeInfo: DashboardRouteInfo.Normal,
|
||||
urlEdit: false,
|
||||
urlFullscreen: false,
|
||||
initPhase: DashboardInitPhase.NotStarted,
|
||||
isInitSlow: false,
|
||||
initDashboard: jest.fn(),
|
||||
@ -133,55 +131,39 @@ describe('DashboardPage', () => {
|
||||
ctx.mount();
|
||||
ctx.setDashboardProp();
|
||||
ctx.wrapper?.setProps({
|
||||
urlFullscreen: true,
|
||||
urlEdit: true,
|
||||
urlPanelId: '1',
|
||||
urlEditPanelId: '1',
|
||||
});
|
||||
});
|
||||
|
||||
it('Should update model state to fullscreen & edit', () => {
|
||||
expect(ctx.dashboard).not.toBe(null);
|
||||
expect(ctx.dashboard?.meta.fullscreen).toBe(true);
|
||||
expect(ctx.dashboard?.meta.isEditing).toBe(true);
|
||||
});
|
||||
|
||||
it('Should update component state to fullscreen and edit', () => {
|
||||
const state = ctx.wrapper?.state();
|
||||
expect(state).not.toBe(null);
|
||||
expect(state?.isEditing).toBe(true);
|
||||
expect(state?.isFullscreen).toBe(true);
|
||||
expect(state?.editPanel).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
dashboardPageScenario('When user goes back to dashboard from panel edit', ctx => {
|
||||
dashboardPageScenario('When user goes back to dashboard from view panel', ctx => {
|
||||
ctx.setup(() => {
|
||||
ctx.mount();
|
||||
ctx.setDashboardProp();
|
||||
ctx.wrapper?.setState({ scrollTop: 100 });
|
||||
ctx.wrapper?.setProps({
|
||||
urlFullscreen: true,
|
||||
urlEdit: true,
|
||||
urlPanelId: '1',
|
||||
urlEditPanelId: '1',
|
||||
});
|
||||
ctx.wrapper?.setProps({
|
||||
urlFullscreen: false,
|
||||
urlEdit: false,
|
||||
urlPanelId: (null as unknown) as string,
|
||||
urlEditPanelId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('Should update model state normal state', () => {
|
||||
expect(ctx.dashboard).not.toBe(null);
|
||||
expect(ctx.dashboard?.meta.fullscreen).toBe(false);
|
||||
expect(ctx.dashboard?.meta.isEditing).toBe(false);
|
||||
expect(ctx.dashboard.panelInEdit).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Should update component state to normal and restore scrollTop', () => {
|
||||
const state = ctx.wrapper?.state();
|
||||
expect(ctx.wrapper).not.toBe(null);
|
||||
expect(state).not.toBe(null);
|
||||
expect(state?.isEditing).toBe(false);
|
||||
expect(state?.isFullscreen).toBe(false);
|
||||
expect(state?.editPanel).toBe(null);
|
||||
expect(state?.scrollTop).toBe(100);
|
||||
});
|
||||
});
|
||||
@ -228,9 +210,7 @@ describe('DashboardPage', () => {
|
||||
schemaVersion: 17,
|
||||
});
|
||||
ctx.wrapper?.setProps({
|
||||
urlEdit: true,
|
||||
urlFullscreen: true,
|
||||
urlPanelId: '0',
|
||||
urlEditPanelId: '0',
|
||||
});
|
||||
});
|
||||
|
||||
@ -238,8 +218,7 @@ describe('DashboardPage', () => {
|
||||
const state = ctx.wrapper?.state();
|
||||
expect(ctx.wrapper).not.toBe(null);
|
||||
expect(state).not.toBe(null);
|
||||
expect(state?.fullscreenPanel).not.toBe(null);
|
||||
expect(state?.fullscreenPanel?.id).toBe(0);
|
||||
expect(state?.editPanel).not.toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
@ -258,13 +237,12 @@ describe('DashboardPage', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('mapStateToProps with bool fullscreen', () => {
|
||||
describe('mapStateToProps with editPanel', () => {
|
||||
const props = mapStateToProps({
|
||||
location: {
|
||||
routeParams: {},
|
||||
query: {
|
||||
fullscreen: true,
|
||||
edit: false,
|
||||
editPanel: '1',
|
||||
},
|
||||
},
|
||||
panelEditorNew: {},
|
||||
@ -273,8 +251,7 @@ describe('DashboardPage', () => {
|
||||
},
|
||||
} as any);
|
||||
|
||||
expect(props.urlFullscreen).toBe(true);
|
||||
expect(props.urlEdit).toBe(false);
|
||||
expect(props.urlEditPanelId).toBe('1');
|
||||
});
|
||||
|
||||
describe('mapStateToProps with string edit true', () => {
|
||||
@ -282,8 +259,7 @@ describe('DashboardPage', () => {
|
||||
location: {
|
||||
routeParams: {},
|
||||
query: {
|
||||
fullscreen: false,
|
||||
edit: 'true',
|
||||
viewPanel: '2',
|
||||
},
|
||||
},
|
||||
panelEditorNew: {},
|
||||
@ -292,7 +268,6 @@ describe('DashboardPage', () => {
|
||||
},
|
||||
} as any);
|
||||
|
||||
expect(props.urlFullscreen).toBe(false);
|
||||
expect(props.urlEdit).toBe(true);
|
||||
expect(props.urlViewPanelId).toBe('2');
|
||||
});
|
||||
});
|
||||
|
@ -15,7 +15,7 @@ import { DashNav } from '../components/DashNav';
|
||||
import { AngularSubMenu } from '../components/SubMenu';
|
||||
import { DashboardSettings } from '../components/DashboardSettings';
|
||||
import { PanelEditor } from '../components/PanelEditor/PanelEditor';
|
||||
import { Alert, CustomScrollbar, Portal } from '@grafana/ui';
|
||||
import { Alert, CustomScrollbar } from '@grafana/ui';
|
||||
// Redux
|
||||
import { initDashboard } from '../state/initDashboard';
|
||||
import { cleanUpDashboard } from '../state/reducers';
|
||||
@ -41,13 +41,12 @@ export interface Props {
|
||||
editview?: string;
|
||||
urlPanelId?: string;
|
||||
urlFolderId?: string;
|
||||
urlEditPanel?: string;
|
||||
inspectPanelId?: string;
|
||||
$scope: any;
|
||||
$injector: any;
|
||||
routeInfo: DashboardRouteInfo;
|
||||
urlEdit: boolean;
|
||||
urlFullscreen: boolean;
|
||||
urlEditPanelId?: string;
|
||||
urlViewPanelId?: string;
|
||||
initPhase: DashboardInitPhase;
|
||||
isInitSlow: boolean;
|
||||
dashboard: DashboardModel | null;
|
||||
@ -61,9 +60,8 @@ export interface Props {
|
||||
}
|
||||
|
||||
export interface State {
|
||||
isEditing: boolean;
|
||||
isFullscreen: boolean;
|
||||
fullscreenPanel: PanelModel | null;
|
||||
editPanel: PanelModel | null;
|
||||
viewPanel: PanelModel | null;
|
||||
scrollTop: number;
|
||||
updateScrollTop?: number;
|
||||
rememberScrollTop: number;
|
||||
@ -72,10 +70,9 @@ export interface State {
|
||||
|
||||
export class DashboardPage extends PureComponent<Props, State> {
|
||||
state: State = {
|
||||
isEditing: false,
|
||||
isFullscreen: false,
|
||||
editPanel: null,
|
||||
viewPanel: null,
|
||||
showLoadingState: false,
|
||||
fullscreenPanel: null,
|
||||
scrollTop: 0,
|
||||
rememberScrollTop: 0,
|
||||
};
|
||||
@ -101,7 +98,8 @@ export class DashboardPage extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
const { dashboard, urlEdit, urlFullscreen, urlPanelId, urlUid } = this.props;
|
||||
const { dashboard, urlEditPanelId, urlViewPanelId, urlUid } = this.props;
|
||||
const { editPanel, viewPanel } = this.state;
|
||||
|
||||
if (!dashboard) {
|
||||
return;
|
||||
@ -118,56 +116,64 @@ export class DashboardPage extends PureComponent<Props, State> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sync url state with model
|
||||
if (urlFullscreen !== dashboard.meta.fullscreen || urlEdit !== dashboard.meta.isEditing) {
|
||||
if (urlPanelId && !isNaN(parseInt(urlPanelId, 10))) {
|
||||
this.onEnterFullscreen(dashboard, urlPanelId);
|
||||
} else {
|
||||
this.onLeaveFullscreen(dashboard);
|
||||
}
|
||||
// entering edit mode
|
||||
if (!editPanel && urlEditPanelId) {
|
||||
this.getPanelByIdFromUrlParam(urlEditPanelId, panel => {
|
||||
this.setState({ editPanel: panel });
|
||||
});
|
||||
}
|
||||
|
||||
// leaving edit mode
|
||||
if (editPanel && !urlEditPanelId) {
|
||||
this.setState({ editPanel: null });
|
||||
}
|
||||
|
||||
// entering view mode
|
||||
if (!viewPanel && urlViewPanelId) {
|
||||
this.getPanelByIdFromUrlParam(urlViewPanelId, panel => {
|
||||
this.setPanelFullscreenClass(true);
|
||||
dashboard.initViewPanel(panel);
|
||||
this.setState({
|
||||
viewPanel: panel,
|
||||
rememberScrollTop: this.state.scrollTop,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// leaving view mode
|
||||
if (viewPanel && !urlViewPanelId) {
|
||||
this.setPanelFullscreenClass(false);
|
||||
dashboard.exitViewPanel(viewPanel);
|
||||
this.setState(
|
||||
{ viewPanel: null, updateScrollTop: this.state.rememberScrollTop },
|
||||
this.triggerPanelsRendering.bind(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
onEnterFullscreen(dashboard: DashboardModel, urlPanelId: string) {
|
||||
const { urlEdit, urlFullscreen } = this.props;
|
||||
getPanelByIdFromUrlParam(urlPanelId: string, callback: (panel: PanelModel) => void) {
|
||||
const { dashboard } = this.props;
|
||||
|
||||
const panelId = parseInt(urlPanelId!, 10);
|
||||
dashboard;
|
||||
// need to expand parent row if this panel is inside a row
|
||||
dashboard.expandParentRowFor(panelId);
|
||||
const panel = dashboard!.getPanelById(panelId);
|
||||
|
||||
const panel = dashboard.getPanelById(panelId);
|
||||
|
||||
if (panel) {
|
||||
dashboard.setViewMode(panel, urlFullscreen, urlEdit);
|
||||
this.setState({
|
||||
isEditing: urlEdit && dashboard.meta.canEdit === true,
|
||||
isFullscreen: urlFullscreen,
|
||||
fullscreenPanel: panel,
|
||||
rememberScrollTop: this.state.scrollTop,
|
||||
if (!panel) {
|
||||
// Panel not found
|
||||
this.props.notifyApp(createErrorNotification(`Panel with id ${urlPanelId} not found`));
|
||||
// Clear url state
|
||||
this.props.updateLocation({
|
||||
query: {
|
||||
edit: null,
|
||||
fullscreen: null,
|
||||
panelId: null,
|
||||
},
|
||||
partial: true,
|
||||
});
|
||||
this.setPanelFullscreenClass(urlFullscreen);
|
||||
} else {
|
||||
this.handleFullscreenPanelNotFound(urlPanelId);
|
||||
}
|
||||
}
|
||||
|
||||
onLeaveFullscreen(dashboard: DashboardModel) {
|
||||
if (this.state.fullscreenPanel) {
|
||||
dashboard.setViewMode(this.state.fullscreenPanel, false, false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(
|
||||
{
|
||||
isEditing: false,
|
||||
isFullscreen: false,
|
||||
fullscreenPanel: null,
|
||||
updateScrollTop: this.state.rememberScrollTop,
|
||||
},
|
||||
this.triggerPanelsRendering.bind(this)
|
||||
);
|
||||
|
||||
this.setPanelFullscreenClass(false);
|
||||
dashboard!.expandParentRowFor(panelId);
|
||||
callback(panel);
|
||||
}
|
||||
|
||||
triggerPanelsRendering() {
|
||||
@ -179,20 +185,6 @@ export class DashboardPage extends PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
handleFullscreenPanelNotFound(urlPanelId: string) {
|
||||
// Panel not found
|
||||
this.props.notifyApp(createErrorNotification(`Panel with id ${urlPanelId} not found`));
|
||||
// Clear url state
|
||||
this.props.updateLocation({
|
||||
query: {
|
||||
edit: null,
|
||||
fullscreen: null,
|
||||
panelId: null,
|
||||
},
|
||||
partial: true,
|
||||
});
|
||||
}
|
||||
|
||||
setPanelFullscreenClass(isFullscreen: boolean) {
|
||||
$('body').toggleClass('panel-in-fullscreen', isFullscreen);
|
||||
}
|
||||
@ -252,13 +244,13 @@ export class DashboardPage extends PureComponent<Props, State> {
|
||||
isInitSlow,
|
||||
initError,
|
||||
inspectPanelId,
|
||||
urlEditPanel,
|
||||
inspectTab,
|
||||
isNewEditorOpen,
|
||||
updateLocation,
|
||||
} = this.props;
|
||||
|
||||
const { isEditing, isFullscreen, scrollTop, updateScrollTop } = this.state;
|
||||
const { editPanel, viewPanel, scrollTop, updateScrollTop } = this.state;
|
||||
const { featureToggles } = getConfig();
|
||||
|
||||
if (!dashboard) {
|
||||
if (isInitSlow) {
|
||||
@ -274,21 +266,13 @@ export class DashboardPage extends PureComponent<Props, State> {
|
||||
|
||||
// Find the panel to inspect
|
||||
const inspectPanel = inspectPanelId ? dashboard.getPanelById(parseInt(inspectPanelId, 10)) : null;
|
||||
// find panel being edited
|
||||
const editPanel = urlEditPanel ? dashboard.getPanelById(parseInt(urlEditPanel, 10)) : null;
|
||||
|
||||
// Only trigger render when the scroll has moved by 25
|
||||
const approximateScrollTop = Math.round(scrollTop / 25) * 25;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DashNav
|
||||
dashboard={dashboard}
|
||||
isEditing={isEditing}
|
||||
isFullscreen={isFullscreen}
|
||||
$injector={$injector}
|
||||
onAddPanel={this.onAddPanel}
|
||||
/>
|
||||
<DashNav dashboard={dashboard} isFullscreen={!!viewPanel} $injector={$injector} onAddPanel={this.onAddPanel} />
|
||||
<div className="scroll-canvas scroll-canvas--dashboard">
|
||||
<CustomScrollbar
|
||||
autoHeightMin="100%"
|
||||
@ -300,12 +284,12 @@ export class DashboardPage extends PureComponent<Props, State> {
|
||||
{initError && this.renderInitFailedState()}
|
||||
|
||||
<div className={gridWrapperClasses}>
|
||||
{!getConfig().featureToggles.newVariables && <AngularSubMenu dashboard={dashboard} />}
|
||||
{!editPanel && getConfig().featureToggles.newVariables && <SubMenu dashboard={dashboard} />}
|
||||
{!editPanel && !featureToggles.newVariables && <AngularSubMenu dashboard={dashboard} />}
|
||||
{!editPanel && featureToggles.newVariables && <SubMenu dashboard={dashboard} />}
|
||||
<DashboardGrid
|
||||
dashboard={dashboard}
|
||||
isEditing={isEditing}
|
||||
isFullscreen={isFullscreen}
|
||||
viewPanel={viewPanel}
|
||||
editPanel={editPanel}
|
||||
isNewEditorOpen={isNewEditorOpen}
|
||||
scrollTop={approximateScrollTop}
|
||||
/>
|
||||
@ -314,11 +298,7 @@ export class DashboardPage extends PureComponent<Props, State> {
|
||||
</div>
|
||||
|
||||
{inspectPanel && <PanelInspector dashboard={dashboard} panel={inspectPanel} selectedTab={inspectTab} />}
|
||||
{editPanel && (
|
||||
<Portal>
|
||||
<PanelEditor dashboard={dashboard} sourcePanel={editPanel} />
|
||||
</Portal>
|
||||
)}
|
||||
{editPanel && <PanelEditor dashboard={dashboard} sourcePanel={editPanel} />}
|
||||
{editview && <DashboardSettings dashboard={dashboard} updateLocation={updateLocation} />}
|
||||
</div>
|
||||
);
|
||||
@ -331,10 +311,9 @@ export const mapStateToProps = (state: StoreState) => ({
|
||||
urlType: state.location.routeParams.type,
|
||||
editview: state.location.query.editview,
|
||||
urlPanelId: state.location.query.panelId,
|
||||
urlEditPanel: state.location.query.editPanel,
|
||||
urlFolderId: state.location.query.folderId,
|
||||
urlFullscreen: !!state.location.query.fullscreen,
|
||||
urlEdit: !!state.location.query.edit,
|
||||
urlEditPanelId: state.location.query.editPanel,
|
||||
urlViewPanelId: state.location.query.viewPanel,
|
||||
inspectPanelId: state.location.query.inspect,
|
||||
initPhase: state.dashboard.initPhase,
|
||||
isInitSlow: state.dashboard.isInitSlow,
|
||||
|
@ -89,7 +89,7 @@ export class SoloPanelPage extends Component<Props, State> {
|
||||
|
||||
return (
|
||||
<div className="panel-solo">
|
||||
<DashboardPanel dashboard={dashboard} panel={panel} isEditing={false} isFullscreen={false} isInView={true} />
|
||||
<DashboardPanel dashboard={dashboard} panel={panel} isEditing={false} isViewing={false} isInView={true} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -40,8 +40,6 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1`
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"fullscreen": false,
|
||||
"isEditing": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
@ -98,7 +96,6 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1`
|
||||
"version": 0,
|
||||
}
|
||||
}
|
||||
isEditing={false}
|
||||
isFullscreen={false}
|
||||
onAddPanel={[Function]}
|
||||
/>
|
||||
@ -156,8 +153,6 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1`
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"fullscreen": false,
|
||||
"isEditing": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
@ -252,8 +247,6 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1`
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"fullscreen": false,
|
||||
"isEditing": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
@ -310,9 +303,9 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1`
|
||||
"version": 0,
|
||||
}
|
||||
}
|
||||
isEditing={false}
|
||||
isFullscreen={false}
|
||||
editPanel={null}
|
||||
scrollTop={0}
|
||||
viewPanel={null}
|
||||
/>
|
||||
</div>
|
||||
</CustomScrollbar>
|
||||
@ -378,8 +371,6 @@ exports[`DashboardPage When dashboard has editview url state should render setti
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"fullscreen": false,
|
||||
"isEditing": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
@ -436,7 +427,6 @@ exports[`DashboardPage When dashboard has editview url state should render setti
|
||||
"version": 0,
|
||||
}
|
||||
}
|
||||
isEditing={false}
|
||||
isFullscreen={false}
|
||||
onAddPanel={[Function]}
|
||||
/>
|
||||
@ -494,8 +484,6 @@ exports[`DashboardPage When dashboard has editview url state should render setti
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"fullscreen": false,
|
||||
"isEditing": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
@ -590,8 +578,6 @@ exports[`DashboardPage When dashboard has editview url state should render setti
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"fullscreen": false,
|
||||
"isEditing": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
@ -648,9 +634,9 @@ exports[`DashboardPage When dashboard has editview url state should render setti
|
||||
"version": 0,
|
||||
}
|
||||
}
|
||||
isEditing={false}
|
||||
isFullscreen={false}
|
||||
editPanel={null}
|
||||
scrollTop={0}
|
||||
viewPanel={null}
|
||||
/>
|
||||
</div>
|
||||
</CustomScrollbar>
|
||||
@ -692,8 +678,6 @@ exports[`DashboardPage When dashboard has editview url state should render setti
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"fullscreen": false,
|
||||
"isEditing": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
|
@ -57,8 +57,8 @@ function dashboardGridScenario(description: string, scenarioFn: (ctx: ScenarioCo
|
||||
setupFn = fn;
|
||||
},
|
||||
props: {
|
||||
isEditing: false,
|
||||
isFullscreen: false,
|
||||
editPanel: null,
|
||||
viewPanel: null,
|
||||
scrollTop: 0,
|
||||
dashboard: getTestDashboard(),
|
||||
},
|
||||
|
@ -15,7 +15,6 @@ import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core
|
||||
import { DashboardPanel } from './DashboardPanel';
|
||||
import { DashboardModel, PanelModel } from '../state';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { PanelEvents } from '@grafana/data';
|
||||
import { panelAdded, panelRemoved } from '../state/PanelModel';
|
||||
|
||||
let lastGridWidth = 1200;
|
||||
@ -33,7 +32,7 @@ interface GridWrapperProps {
|
||||
className: string;
|
||||
isResizable?: boolean;
|
||||
isDraggable?: boolean;
|
||||
isFullscreen?: boolean;
|
||||
viewPanel: PanelModel | null;
|
||||
}
|
||||
|
||||
function GridWrapper({
|
||||
@ -48,7 +47,7 @@ function GridWrapper({
|
||||
className,
|
||||
isResizable,
|
||||
isDraggable,
|
||||
isFullscreen,
|
||||
viewPanel,
|
||||
}: GridWrapperProps) {
|
||||
const width = size.width > 0 ? size.width : lastGridWidth;
|
||||
|
||||
@ -56,7 +55,7 @@ function GridWrapper({
|
||||
if (width !== lastGridWidth) {
|
||||
if (ignoreNextWidthChange) {
|
||||
ignoreNextWidthChange = false;
|
||||
} else if (!isFullscreen && Math.abs(width - lastGridWidth) > 8) {
|
||||
} else if (!viewPanel && Math.abs(width - lastGridWidth) > 8) {
|
||||
onWidthChange();
|
||||
lastGridWidth = width;
|
||||
}
|
||||
@ -95,8 +94,8 @@ const SizedReactLayoutGrid = sizeMe({ monitorWidth: true })(GridWrapper);
|
||||
|
||||
export interface Props {
|
||||
dashboard: DashboardModel;
|
||||
isEditing: boolean;
|
||||
isFullscreen: boolean;
|
||||
editPanel: PanelModel | null;
|
||||
viewPanel: PanelModel | null;
|
||||
scrollTop: number;
|
||||
isNewEditorOpen?: boolean;
|
||||
}
|
||||
@ -111,7 +110,6 @@ export class DashboardGrid extends PureComponent<Props> {
|
||||
dashboard.on(panelAdded, this.triggerForceUpdate);
|
||||
dashboard.on(panelRemoved, this.triggerForceUpdate);
|
||||
dashboard.on(CoreEvents.repeatsProcessed, this.triggerForceUpdate);
|
||||
dashboard.on(PanelEvents.viewModeChanged, this.onViewModeChanged);
|
||||
dashboard.on(CoreEvents.rowCollapsed, this.triggerForceUpdate);
|
||||
dashboard.on(CoreEvents.rowExpanded, this.triggerForceUpdate);
|
||||
}
|
||||
@ -121,7 +119,6 @@ export class DashboardGrid extends PureComponent<Props> {
|
||||
dashboard.off(panelAdded, this.triggerForceUpdate);
|
||||
dashboard.off(panelRemoved, this.triggerForceUpdate);
|
||||
dashboard.off(CoreEvents.repeatsProcessed, this.triggerForceUpdate);
|
||||
dashboard.off(PanelEvents.viewModeChanged, this.onViewModeChanged);
|
||||
dashboard.off(CoreEvents.rowCollapsed, this.triggerForceUpdate);
|
||||
dashboard.off(CoreEvents.rowExpanded, this.triggerForceUpdate);
|
||||
}
|
||||
@ -181,10 +178,6 @@ export class DashboardGrid extends PureComponent<Props> {
|
||||
}
|
||||
};
|
||||
|
||||
onViewModeChanged = () => {
|
||||
ignoreNextWidthChange = true;
|
||||
};
|
||||
|
||||
updateGridPos = (item: ReactGridLayout.Layout, layout: ReactGridLayout.Layout[]) => {
|
||||
this.panelMap[item.i!].updateGridPos(item);
|
||||
|
||||
@ -207,7 +200,7 @@ export class DashboardGrid extends PureComponent<Props> {
|
||||
};
|
||||
|
||||
isInView = (panel: PanelModel): boolean => {
|
||||
if (panel.fullscreen || panel.isEditing) {
|
||||
if (panel.isViewing || panel.isEditing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -248,7 +241,7 @@ export class DashboardGrid extends PureComponent<Props> {
|
||||
const panelElements = [];
|
||||
|
||||
for (const panel of this.props.dashboard.panels) {
|
||||
const panelClasses = classNames({ 'react-grid-item--fullscreen': panel.fullscreen });
|
||||
const panelClasses = classNames({ 'react-grid-item--fullscreen': panel.isViewing });
|
||||
const id = panel.id.toString();
|
||||
panel.isInView = this.isInView(panel);
|
||||
|
||||
@ -276,14 +269,14 @@ export class DashboardGrid extends PureComponent<Props> {
|
||||
panel={panel}
|
||||
dashboard={this.props.dashboard}
|
||||
isEditing={panel.isEditing}
|
||||
isFullscreen={panel.fullscreen}
|
||||
isViewing={panel.isViewing}
|
||||
isInView={panel.isInView}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dashboard, isFullscreen } = this.props;
|
||||
const { dashboard, viewPanel } = this.props;
|
||||
|
||||
return (
|
||||
<SizedReactLayoutGrid
|
||||
@ -296,7 +289,7 @@ export class DashboardGrid extends PureComponent<Props> {
|
||||
onDragStop={this.onDragStop}
|
||||
onResize={this.onResize}
|
||||
onResizeStop={this.onResizeStop}
|
||||
isFullscreen={isFullscreen}
|
||||
viewPanel={viewPanel}
|
||||
>
|
||||
{this.renderPanels()}
|
||||
</SizedReactLayoutGrid>
|
||||
|
@ -6,8 +6,6 @@ import { connect, MapStateToProps, MapDispatchToProps } from 'react-redux';
|
||||
|
||||
// Components
|
||||
import { PanelChrome } from './PanelChrome';
|
||||
import { PanelEditor } from '../panel_editor/PanelEditor';
|
||||
import { PanelResizer } from './PanelResizer';
|
||||
import { PanelChromeAngular } from './PanelChromeAngular';
|
||||
|
||||
// Actions
|
||||
@ -23,8 +21,7 @@ export interface OwnProps {
|
||||
panel: PanelModel;
|
||||
dashboard: DashboardModel;
|
||||
isEditing: boolean;
|
||||
isInEditMode?: boolean;
|
||||
isFullscreen: boolean;
|
||||
isViewing: boolean;
|
||||
isInView: boolean;
|
||||
}
|
||||
|
||||
@ -74,12 +71,10 @@ export class DashboardPanelUnconnected extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
renderPanel(plugin: PanelPlugin) {
|
||||
const { dashboard, panel, isFullscreen, isEditing, isInView, isInEditMode, updateLocation } = this.props;
|
||||
|
||||
const autoSizerStyle = { height: isEditing ? '100%' : '' };
|
||||
const { dashboard, panel, isViewing, isInView, isEditing, updateLocation } = this.props;
|
||||
|
||||
return (
|
||||
<AutoSizer style={autoSizerStyle}>
|
||||
<AutoSizer>
|
||||
{({ width, height }) => {
|
||||
if (width === 0) {
|
||||
return null;
|
||||
@ -91,7 +86,8 @@ export class DashboardPanelUnconnected extends PureComponent<Props, State> {
|
||||
plugin={plugin}
|
||||
panel={panel}
|
||||
dashboard={dashboard}
|
||||
isFullscreen={isFullscreen}
|
||||
isViewing={isViewing}
|
||||
isEditing={isEditing}
|
||||
isInView={isInView}
|
||||
width={width}
|
||||
height={height}
|
||||
@ -104,9 +100,9 @@ export class DashboardPanelUnconnected extends PureComponent<Props, State> {
|
||||
plugin={plugin}
|
||||
panel={panel}
|
||||
dashboard={dashboard}
|
||||
isFullscreen={isFullscreen}
|
||||
isViewing={isViewing}
|
||||
isEditing={isEditing}
|
||||
isInView={isInView}
|
||||
isInEditMode={isInEditMode}
|
||||
width={width}
|
||||
height={height}
|
||||
updateLocation={updateLocation}
|
||||
@ -118,7 +114,7 @@ export class DashboardPanelUnconnected extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { panel, dashboard, isFullscreen, isEditing, plugin } = this.props;
|
||||
const { isViewing, plugin } = this.props;
|
||||
const { isLazy } = this.state;
|
||||
|
||||
// if we have not loaded plugin exports yet, wait
|
||||
@ -131,34 +127,14 @@ export class DashboardPanelUnconnected extends PureComponent<Props, State> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const editorContainerClasses = classNames({
|
||||
'panel-editor-container': isEditing,
|
||||
'panel-height-helper': !isEditing,
|
||||
});
|
||||
|
||||
const panelWrapperClass = classNames({
|
||||
'panel-wrapper': true,
|
||||
'panel-wrapper--edit': isEditing,
|
||||
'panel-wrapper--view': isFullscreen && !isEditing,
|
||||
'panel-wrapper--view': isViewing,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={editorContainerClasses}>
|
||||
<PanelResizer
|
||||
isEditing={isEditing}
|
||||
panel={panel}
|
||||
render={styles => (
|
||||
<div
|
||||
className={panelWrapperClass}
|
||||
onMouseEnter={this.onMouseEnter}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
style={styles}
|
||||
>
|
||||
{this.renderPanel(plugin)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
{panel.isEditing && <PanelEditor panel={panel} plugin={plugin} dashboard={dashboard} />}
|
||||
<div className={panelWrapperClass} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
{this.renderPanel(plugin)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -33,9 +33,9 @@ export interface Props {
|
||||
panel: PanelModel;
|
||||
dashboard: DashboardModel;
|
||||
plugin: PanelPlugin;
|
||||
isFullscreen: boolean;
|
||||
isViewing: boolean;
|
||||
isEditing?: boolean;
|
||||
isInView: boolean;
|
||||
isInEditMode?: boolean;
|
||||
width: number;
|
||||
height: number;
|
||||
updateLocation: typeof updateLocation;
|
||||
@ -69,7 +69,8 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { panel, dashboard, isInEditMode } = this.props;
|
||||
const { panel, dashboard, isEditing } = this.props;
|
||||
|
||||
panel.events.on(PanelEvents.refresh, this.onRefresh);
|
||||
panel.events.on(PanelEvents.render, this.onRender);
|
||||
dashboard.panelInitialized(this.props.panel);
|
||||
@ -85,7 +86,7 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
isFirstLoad: false,
|
||||
});
|
||||
} else {
|
||||
if (isInEditMode) {
|
||||
if (isEditing) {
|
||||
this.querySubscription = panel
|
||||
.getQueryRunner()
|
||||
.getData()
|
||||
@ -319,7 +320,7 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dashboard, panel, isFullscreen, width, height, updateLocation } = this.props;
|
||||
const { dashboard, panel, isViewing, isEditing, width, height, updateLocation } = this.props;
|
||||
const { errorMessage, data } = this.state;
|
||||
const { transparent } = panel;
|
||||
|
||||
@ -340,7 +341,8 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
scopedVars={panel.scopedVars}
|
||||
links={panel.links}
|
||||
error={errorMessage}
|
||||
isFullscreen={isFullscreen}
|
||||
isEditing={isEditing}
|
||||
isViewing={isViewing}
|
||||
data={data}
|
||||
updateLocation={updateLocation}
|
||||
/>
|
||||
|
@ -24,7 +24,8 @@ interface OwnProps {
|
||||
panel: PanelModel;
|
||||
dashboard: DashboardModel;
|
||||
plugin: PanelPlugin;
|
||||
isFullscreen: boolean;
|
||||
isViewing: boolean;
|
||||
isEditing: boolean;
|
||||
isInView: boolean;
|
||||
width: number;
|
||||
height: number;
|
||||
@ -217,7 +218,7 @@ export class PanelChromeAngularUnconnected extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dashboard, panel, isFullscreen, plugin, angularComponent, updateLocation } = this.props;
|
||||
const { dashboard, panel, isViewing, isEditing, plugin, angularComponent, updateLocation } = this.props;
|
||||
const { errorMessage, data, alertState } = this.state;
|
||||
const { transparent } = panel;
|
||||
|
||||
@ -246,7 +247,8 @@ export class PanelChromeAngularUnconnected extends PureComponent<Props, State> {
|
||||
angularComponent={angularComponent}
|
||||
links={panel.links}
|
||||
error={errorMessage}
|
||||
isFullscreen={isFullscreen}
|
||||
isViewing={isViewing}
|
||||
isEditing={isEditing}
|
||||
data={data}
|
||||
updateLocation={updateLocation}
|
||||
/>
|
||||
|
@ -25,7 +25,8 @@ export interface Props {
|
||||
angularComponent?: AngularComponent | null;
|
||||
links?: DataLink[];
|
||||
error?: string;
|
||||
isFullscreen: boolean;
|
||||
isViewing: boolean;
|
||||
isEditing: boolean;
|
||||
data: PanelData;
|
||||
updateLocation: typeof updateLocation;
|
||||
}
|
||||
@ -121,13 +122,13 @@ export class PanelHeader extends Component<Props, State> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { panel, scopedVars, error, isFullscreen, data } = this.props;
|
||||
const { panel, scopedVars, error, isViewing, isEditing, data } = this.props;
|
||||
const { menuItems } = this.state;
|
||||
const title = templateSrv.replaceWithText(panel.title, scopedVars);
|
||||
|
||||
const panelHeaderClass = classNames({
|
||||
'panel-header': true,
|
||||
'grid-drag-handle': !isFullscreen,
|
||||
'grid-drag-handle': !(isViewing || isEditing),
|
||||
});
|
||||
|
||||
// dedupe on severity
|
||||
|
@ -4,7 +4,6 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
|
||||
<SizeMe(GridWrapper)
|
||||
className="layout"
|
||||
isDraggable={true}
|
||||
isFullscreen={false}
|
||||
isResizable={true}
|
||||
layout={
|
||||
Array [
|
||||
@ -43,6 +42,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
|
||||
onResize={[Function]}
|
||||
onResizeStop={[Function]}
|
||||
onWidthChange={[Function]}
|
||||
viewPanel={null}
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
@ -96,13 +96,8 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
|
||||
"fn": [Function],
|
||||
"once": false,
|
||||
},
|
||||
"view-mode-changed": EE {
|
||||
"context": [Circular],
|
||||
"fn": [Function],
|
||||
"once": false,
|
||||
},
|
||||
},
|
||||
"_eventsCount": 6,
|
||||
"_eventsCount": 5,
|
||||
},
|
||||
},
|
||||
"getVariables": [Function],
|
||||
@ -117,8 +112,6 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"fullscreen": false,
|
||||
"isEditing": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
@ -345,13 +338,8 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
|
||||
"fn": [Function],
|
||||
"once": false,
|
||||
},
|
||||
"view-mode-changed": EE {
|
||||
"context": [Circular],
|
||||
"fn": [Function],
|
||||
"once": false,
|
||||
},
|
||||
},
|
||||
"_eventsCount": 6,
|
||||
"_eventsCount": 5,
|
||||
},
|
||||
},
|
||||
"getVariables": [Function],
|
||||
@ -366,8 +354,6 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"fullscreen": false,
|
||||
"isEditing": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
@ -594,13 +580,8 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
|
||||
"fn": [Function],
|
||||
"once": false,
|
||||
},
|
||||
"view-mode-changed": EE {
|
||||
"context": [Circular],
|
||||
"fn": [Function],
|
||||
"once": false,
|
||||
},
|
||||
},
|
||||
"_eventsCount": 6,
|
||||
"_eventsCount": 5,
|
||||
},
|
||||
},
|
||||
"getVariables": [Function],
|
||||
@ -615,8 +596,6 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"fullscreen": false,
|
||||
"isEditing": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
@ -843,13 +822,8 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
|
||||
"fn": [Function],
|
||||
"once": false,
|
||||
},
|
||||
"view-mode-changed": EE {
|
||||
"context": [Circular],
|
||||
"fn": [Function],
|
||||
"once": false,
|
||||
},
|
||||
},
|
||||
"_eventsCount": 6,
|
||||
"_eventsCount": 5,
|
||||
},
|
||||
},
|
||||
"getVariables": [Function],
|
||||
@ -864,8 +838,6 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
|
||||
"canSave": true,
|
||||
"canShare": true,
|
||||
"canStar": true,
|
||||
"fullscreen": false,
|
||||
"isEditing": false,
|
||||
"showSettings": true,
|
||||
},
|
||||
"originalTemplating": Array [],
|
||||
|
@ -53,6 +53,7 @@ export class ChangeTracker {
|
||||
if (this.originalPath === $location.path()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.ignoreChanges()) {
|
||||
return true;
|
||||
}
|
||||
|
@ -1,6 +1,3 @@
|
||||
import { ILocationService } from 'angular';
|
||||
import { PanelEvents } from '@grafana/data';
|
||||
|
||||
import coreModule from 'app/core/core_module';
|
||||
import { appEvents } from 'app/core/app_events';
|
||||
import { DashboardModel } from '../state/DashboardModel';
|
||||
@ -14,8 +11,7 @@ export class DashboardSrv {
|
||||
dashboard: DashboardModel;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $rootScope: GrafanaRootScope, private $location: ILocationService) {
|
||||
appEvents.on(PanelEvents.panelChangeView, this.onPanelChangeView);
|
||||
constructor(private $rootScope: GrafanaRootScope) {
|
||||
appEvents.on(CoreEvents.removePanel, this.onRemovePanel);
|
||||
}
|
||||
|
||||
@ -36,48 +32,6 @@ export class DashboardSrv {
|
||||
removePanel(dashboard, dashboard.getPanelById(panelId), true);
|
||||
};
|
||||
|
||||
onPanelChangeView = ({
|
||||
fullscreen = false,
|
||||
edit = false,
|
||||
panelId,
|
||||
}: {
|
||||
fullscreen?: boolean;
|
||||
edit?: boolean;
|
||||
panelId?: number;
|
||||
}) => {
|
||||
const urlParams = this.$location.search();
|
||||
|
||||
// handle toggle logic
|
||||
// I hate using these truthy converters (!!) but in this case
|
||||
// I think it's appropriate. edit can be null/false/undefined and
|
||||
// here i want all of those to compare the same
|
||||
if (fullscreen === urlParams.fullscreen && edit === !!urlParams.edit) {
|
||||
const paramsToRemove = ['fullscreen', 'edit', 'panelId', 'tab'];
|
||||
for (const key of paramsToRemove) {
|
||||
delete urlParams[key];
|
||||
}
|
||||
|
||||
this.$location.search(urlParams);
|
||||
return;
|
||||
}
|
||||
|
||||
const newUrlParams = {
|
||||
...urlParams,
|
||||
fullscreen: fullscreen || undefined,
|
||||
edit: edit || undefined,
|
||||
tab: edit ? urlParams.tab : undefined,
|
||||
panelId,
|
||||
};
|
||||
|
||||
Object.keys(newUrlParams).forEach(key => {
|
||||
if (newUrlParams[key] === undefined) {
|
||||
delete newUrlParams[key];
|
||||
}
|
||||
});
|
||||
|
||||
this.$location.search(newUrlParams);
|
||||
};
|
||||
|
||||
saveJSONDashboard(json: string) {
|
||||
const parsedJson = JSON.parse(json);
|
||||
return getBackendSrv().saveDashboard(parsedJson, {
|
||||
|
@ -51,6 +51,7 @@ export class DashboardModel {
|
||||
gnetId: any;
|
||||
panels: PanelModel[];
|
||||
panelInEdit?: PanelModel;
|
||||
panelInView: PanelModel;
|
||||
|
||||
// ------------------
|
||||
// not persisted
|
||||
@ -69,6 +70,7 @@ export class DashboardModel {
|
||||
originalTime: true,
|
||||
originalTemplating: true,
|
||||
panelInEdit: true,
|
||||
panelInView: true,
|
||||
getVariablesFromState: true,
|
||||
};
|
||||
|
||||
@ -144,8 +146,6 @@ export class DashboardModel {
|
||||
meta.canEdit = meta.canEdit !== false;
|
||||
meta.showSettings = meta.canEdit;
|
||||
meta.canMakeEditable = meta.canSave && !this.editable;
|
||||
meta.fullscreen = false;
|
||||
meta.isEditing = false;
|
||||
|
||||
if (!this.editable) {
|
||||
meta.canEdit = false;
|
||||
@ -263,15 +263,6 @@ export class DashboardModel {
|
||||
}
|
||||
}
|
||||
|
||||
setViewMode(panel: PanelModel, fullscreen: boolean, isEditing: boolean) {
|
||||
this.meta.fullscreen = fullscreen;
|
||||
this.meta.isEditing = isEditing && this.meta.canEdit;
|
||||
|
||||
panel.setViewMode(fullscreen, this.meta.isEditing);
|
||||
|
||||
this.events.emit(PanelEvents.viewModeChanged, panel);
|
||||
}
|
||||
|
||||
timeRangeUpdated(timeRange: TimeRange) {
|
||||
this.events.emit(CoreEvents.timeRangeUpdated, timeRange);
|
||||
if (getConfig().featureToggles.newVariables) {
|
||||
@ -317,14 +308,24 @@ export class DashboardModel {
|
||||
}
|
||||
|
||||
otherPanelInFullscreen(panel: PanelModel) {
|
||||
return (this.meta.fullscreen && !panel.fullscreen) || this.panelInEdit;
|
||||
return (this.panelInEdit || this.panelInView) && !(panel.isViewing || panel.isEditing);
|
||||
}
|
||||
|
||||
initPanelEditor(sourcePanel: PanelModel): PanelModel {
|
||||
initEditPanel(sourcePanel: PanelModel): PanelModel {
|
||||
this.panelInEdit = sourcePanel.getEditClone();
|
||||
return this.panelInEdit;
|
||||
}
|
||||
|
||||
initViewPanel(panel: PanelModel) {
|
||||
this.panelInView = panel;
|
||||
panel.setIsViewing(true);
|
||||
}
|
||||
|
||||
exitViewPanel(panel: PanelModel) {
|
||||
this.panelInView = undefined;
|
||||
panel.setIsViewing(false);
|
||||
}
|
||||
|
||||
exitPanelEditor() {
|
||||
this.panelInEdit = undefined;
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ export interface GridPos {
|
||||
|
||||
const notPersistedProperties: { [str: string]: boolean } = {
|
||||
events: true,
|
||||
fullscreen: true,
|
||||
isViewing: true,
|
||||
isEditing: true,
|
||||
isInView: true,
|
||||
hasRefreshed: true,
|
||||
@ -134,7 +134,7 @@ export class PanelModel implements DataConfigSource {
|
||||
transparent: boolean;
|
||||
|
||||
// non persisted
|
||||
fullscreen: boolean;
|
||||
isViewing: boolean;
|
||||
isEditing: boolean;
|
||||
isInView: boolean;
|
||||
hasRefreshed: boolean;
|
||||
@ -215,10 +215,8 @@ export class PanelModel implements DataConfigSource {
|
||||
return model;
|
||||
}
|
||||
|
||||
setViewMode(fullscreen: boolean, isEditing: boolean) {
|
||||
this.fullscreen = fullscreen;
|
||||
this.isEditing = isEditing;
|
||||
this.events.emit(PanelEvents.viewModeChanged);
|
||||
setIsViewing(isViewing: boolean) {
|
||||
this.isViewing = isViewing;
|
||||
}
|
||||
|
||||
updateGridPos(newPos: GridPos) {
|
||||
@ -394,6 +392,7 @@ export class PanelModel implements DataConfigSource {
|
||||
sourceModel.editSourceId = this.id;
|
||||
|
||||
const clone = new PanelModel(sourceModel);
|
||||
clone.isEditing = true;
|
||||
const sourceQueryRunner = this.getQueryRunner();
|
||||
|
||||
// pipe last result to new clone query runner
|
||||
@ -466,6 +465,14 @@ export class PanelModel implements DataConfigSource {
|
||||
|
||||
this.getQueryRunner().resendLastResult();
|
||||
}
|
||||
|
||||
/*
|
||||
* Panel have a different id while in edit mode (to more easily be able to discard changes)
|
||||
* Use this to always get the underlying source id
|
||||
* */
|
||||
getSavedId(): number {
|
||||
return this.editSourceId ?? this.id;
|
||||
}
|
||||
}
|
||||
|
||||
function getPluginVersion(plugin: PanelPlugin): string {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
import { store } from 'app/store/store';
|
||||
import config from 'app/core/config';
|
||||
import { getDataSourceSrv, getLocationSrv, AngularComponent } from '@grafana/runtime';
|
||||
import { PanelMenuItem } from '@grafana/data';
|
||||
import { copyPanel, duplicatePanel, editPanelJson, removePanel, sharePanel } from 'app/features/dashboard/utils/panel';
|
||||
@ -22,9 +21,7 @@ export function getPanelMenu(
|
||||
store.dispatch(
|
||||
updateLocation({
|
||||
query: {
|
||||
panelId: panel.id,
|
||||
edit: null,
|
||||
fullscreen: true,
|
||||
viewPanel: panel.id,
|
||||
},
|
||||
partial: true,
|
||||
})
|
||||
@ -32,20 +29,6 @@ export function getPanelMenu(
|
||||
};
|
||||
|
||||
const onEditPanel = (event: React.MouseEvent<any>) => {
|
||||
event.preventDefault();
|
||||
store.dispatch(
|
||||
updateLocation({
|
||||
query: {
|
||||
panelId: panel.id,
|
||||
edit: true,
|
||||
fullscreen: true,
|
||||
},
|
||||
partial: true,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const onNewEditPanel = (event: React.MouseEvent<any>) => {
|
||||
event.preventDefault();
|
||||
store.dispatch(
|
||||
updateLocation({
|
||||
@ -143,18 +126,9 @@ export function getPanelMenu(
|
||||
shortcut: 'p i',
|
||||
});
|
||||
|
||||
if (config.featureToggles.newEdit) {
|
||||
menu.push({
|
||||
text: 'New edit',
|
||||
iconClassName: 'edit',
|
||||
onClick: onNewEditPanel,
|
||||
shortcut: 'p i',
|
||||
});
|
||||
}
|
||||
|
||||
const subMenu: PanelMenuItem[] = [];
|
||||
|
||||
if (!panel.fullscreen && dashboard.canEditPanel(panel)) {
|
||||
if (dashboard.canEditPanel(panel) && !(panel.isViewing || panel.isEditing)) {
|
||||
subMenu.push({
|
||||
text: 'Duplicate',
|
||||
onClick: onDuplicatePanel,
|
||||
|
@ -1,4 +1,3 @@
|
||||
import omitBy from 'lodash/omitBy';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { hot } from 'react-hot-loader';
|
||||
@ -133,18 +132,15 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
|
||||
});
|
||||
}
|
||||
|
||||
const dashViewOptions = {
|
||||
fullscreen: withChanges || dash.meta.fullscreen,
|
||||
edit: withChanges || dash.meta.isEditing,
|
||||
};
|
||||
const query: any = {};
|
||||
|
||||
this.props.updateLocation({
|
||||
path: `/d/${dash.uid}/:${titleSlug}`,
|
||||
query: {
|
||||
...omitBy(dashViewOptions, v => !v),
|
||||
panelId: originPanelId,
|
||||
},
|
||||
});
|
||||
if (withChanges || dash.panelInEdit) {
|
||||
query.editPanel = originPanelId;
|
||||
} else if (dash.panelInView) {
|
||||
query.viewPanel = originPanelId;
|
||||
}
|
||||
|
||||
this.props.updateLocation({ path: `/d/${dash.uid}/:${titleSlug}`, query });
|
||||
};
|
||||
|
||||
// Remove explore specific parameters from queries
|
||||
|
@ -95,7 +95,7 @@ export class PanelCtrl {
|
||||
}
|
||||
|
||||
otherPanelInFullscreenMode() {
|
||||
return this.dashboard.meta.fullscreen && !this.panel.fullscreen;
|
||||
return this.dashboard.otherPanelInFullscreen(this.panel);
|
||||
}
|
||||
|
||||
render(payload?: any) {
|
||||
|
@ -65,17 +65,6 @@ module.directive('grafanaPanel', ($rootScope, $document, $timeout) => {
|
||||
});
|
||||
}
|
||||
|
||||
function onViewModeChanged() {
|
||||
// first wait one pass for dashboard fullscreen view mode to take effect (classses being applied)
|
||||
setTimeout(() => {
|
||||
// then wait another cycle (this might not be needed)
|
||||
$timeout(() => {
|
||||
ctrl.render();
|
||||
resizeScrollableContent();
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
|
||||
function onPanelModelRender(payload?: any) {
|
||||
ctrl.height = scope.$parent.$parent.size.height;
|
||||
ctrl.width = scope.$parent.$parent.size.width;
|
||||
@ -89,7 +78,6 @@ module.directive('grafanaPanel', ($rootScope, $document, $timeout) => {
|
||||
panel.events.on(PanelEvents.refresh, onPanelModelRefresh);
|
||||
panel.events.on(PanelEvents.render, onPanelModelRender);
|
||||
panel.events.on(PanelEvents.panelSizeChanged, onPanelSizeChanged);
|
||||
panel.events.on(PanelEvents.viewModeChanged, onViewModeChanged);
|
||||
|
||||
scope.$on('$destroy', () => {
|
||||
elem.off();
|
||||
|
@ -43,7 +43,7 @@ export function runSharedRequest(options: QueryRunnerOptions): Observable<PanelD
|
||||
|
||||
// If we are in fullscreen the other panel will not execute any queries
|
||||
// So we have to trigger it from here
|
||||
if (currentPanel.fullscreen) {
|
||||
if (currentPanel.isViewing || currentPanel.isEditing) {
|
||||
const { datasource, targets } = listenToPanel;
|
||||
const modified = {
|
||||
...options,
|
||||
|
@ -13,7 +13,6 @@ export const plugin = new PanelPlugin<BarGaugeOptions>(BarGaugePanel)
|
||||
.addRadio({
|
||||
path: 'displayMode',
|
||||
name: 'Display mode',
|
||||
description: 'Controls the bar style',
|
||||
settings: {
|
||||
options: displayModes,
|
||||
},
|
||||
@ -24,7 +23,7 @@ export const plugin = new PanelPlugin<BarGaugeOptions>(BarGaugePanel)
|
||||
name: 'Show unfilled area',
|
||||
description: 'When enabled renders the unfilled region as gray',
|
||||
defaultValue: true,
|
||||
showIf: options => options.displayMode !== 'lcd',
|
||||
showIf: (options: BarGaugeOptions) => options.displayMode !== 'lcd',
|
||||
});
|
||||
})
|
||||
.setPanelChangeHandler(sharedSingleStatPanelChangedHandler)
|
||||
|
@ -18,9 +18,7 @@ export interface DashboardMeta {
|
||||
canAdmin?: boolean;
|
||||
url?: string;
|
||||
folderId?: number;
|
||||
fullscreen?: boolean;
|
||||
fromExplore?: boolean;
|
||||
isEditing?: boolean;
|
||||
canMakeEditable?: boolean;
|
||||
submenuEnabled?: boolean;
|
||||
provisioned?: boolean;
|
||||
|
@ -8,11 +8,6 @@
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
&--edit {
|
||||
height: 40%;
|
||||
margin: 0 $dashboard-padding;
|
||||
}
|
||||
|
||||
&--view {
|
||||
flex: 1 1 0;
|
||||
height: 90%;
|
||||
|
Loading…
Reference in New Issue
Block a user