Commit df1d4316 by Torkel Ödegaard Committed by GitHub

NewPanelEditor: Panel editor tabs in state (url) (#22102)

* tabs & style tweaks

* Styling updates

* ok look

* tweaks

* Updated snapshots

* Moved transforms

* Updated
parent 8080bbc8
...@@ -123,7 +123,7 @@ export interface PluginConfigPage<T extends PluginMeta> { ...@@ -123,7 +123,7 @@ export interface PluginConfigPage<T extends PluginMeta> {
export class GrafanaPlugin<T extends PluginMeta = PluginMeta> { export class GrafanaPlugin<T extends PluginMeta = PluginMeta> {
// Meta is filled in by the plugin loading system // Meta is filled in by the plugin loading system
meta?: T; meta: T;
// This is set if the plugin system had errors loading the plugin // This is set if the plugin system had errors loading the plugin
loadError?: boolean; loadError?: boolean;
...@@ -142,4 +142,8 @@ export class GrafanaPlugin<T extends PluginMeta = PluginMeta> { ...@@ -142,4 +142,8 @@ export class GrafanaPlugin<T extends PluginMeta = PluginMeta> {
this.configPages.push(tab); this.configPages.push(tab);
return this; return this;
} }
constructor() {
this.meta = {} as T;
}
} }
...@@ -25,6 +25,7 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme) => { ...@@ -25,6 +25,7 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme) => {
flex-direction: row; flex-direction: row;
white-space: nowrap; white-space: nowrap;
cursor: pointer; cursor: pointer;
border-left: 2px solid transparent;
&:hover { &:hover {
background: ${optionBgHover}; background: ${optionBgHover};
} }
......
...@@ -8,21 +8,16 @@ import { ...@@ -8,21 +8,16 @@ import {
DynamicConfigValue, DynamicConfigValue,
VariableSuggestionsScope, VariableSuggestionsScope,
} from '@grafana/data'; } from '@grafana/data';
import { import { standardFieldConfigEditorRegistry, Forms, fieldMatchersUI, ValuePicker } from '@grafana/ui';
standardFieldConfigEditorRegistry,
Forms,
fieldMatchersUI,
ControlledCollapse,
ValuePicker,
} from '@grafana/ui';
import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv'; import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
import { OptionsGroup } from './OptionsGroup';
interface Props { interface Props {
config: FieldConfigSource; config: FieldConfigSource;
custom?: FieldConfigEditorRegistry; // custom fields custom?: FieldConfigEditorRegistry; // custom fields
include?: string[]; // Ordered list of which fields should be shown/included include?: string[]; // Ordered list of which fields should be shown/included
onChange: (config: FieldConfigSource) => void; onChange: (config: FieldConfigSource) => void;
/* Helpful for IntelliSense */
// Helpful for IntelliSense
data: DataFrame[]; data: DataFrame[];
} }
...@@ -33,6 +28,7 @@ export class FieldConfigEditor extends React.PureComponent<Props> { ...@@ -33,6 +28,7 @@ export class FieldConfigEditor extends React.PureComponent<Props> {
private setDefaultValue = (name: string, value: any, custom: boolean) => { private setDefaultValue = (name: string, value: any, custom: boolean) => {
const defaults = { ...this.props.config.defaults }; const defaults = { ...this.props.config.defaults };
const remove = value === undefined || value === null || ''; const remove = value === undefined || value === null || '';
if (custom) { if (custom) {
if (defaults.custom) { if (defaults.custom) {
if (remove) { if (remove) {
...@@ -236,17 +232,14 @@ export class FieldConfigEditor extends React.PureComponent<Props> { ...@@ -236,17 +232,14 @@ export class FieldConfigEditor extends React.PureComponent<Props> {
render() { render() {
return ( return (
<div> <div>
<ControlledCollapse label="Standard Field Configuration" collapsible> <OptionsGroup title="Field configuration">{this.renderStandardConfigs()}</OptionsGroup>
{this.renderStandardConfigs()}
</ControlledCollapse> {this.props.custom && <OptionsGroup title="Visualization options">{this.renderCustomConfigs()}</OptionsGroup>}
{this.props.custom && (
<ControlledCollapse label="Standard Field Configuration">{this.renderCustomConfigs()}</ControlledCollapse> <OptionsGroup title="Field Overrides">
)}
<ControlledCollapse label="Field Overrides" collapsible>
{this.renderOverrides()} {this.renderOverrides()}
{this.renderAddOverride()} {this.renderAddOverride()}
</ControlledCollapse> </OptionsGroup>
</div> </div>
); );
} }
......
import React, { useState, FC } from 'react';
import { css } from 'emotion';
import { GrafanaTheme } from '@grafana/data';
import { useTheme, Icon, stylesFactory } from '@grafana/ui';
interface Props {
title: string;
}
export const OptionsGroup: FC<Props> = ({ title, children }) => {
const [isExpanded, toggleExpand] = useState(false);
const theme = useTheme();
const styles = getStyles(theme);
return (
<div className={styles.box}>
<div className={styles.header} onClick={() => toggleExpand(!isExpanded)}>
{title}
<div className={styles.toggle}>
<Icon name={isExpanded ? 'chevron-down' : 'chevron-left'} />
</div>
</div>
{isExpanded && <div className={styles.body}>{children}</div>}
</div>
);
};
const getStyles = stylesFactory((theme: GrafanaTheme) => {
return {
box: css`
border-bottom: 1px solid ${theme.colors.pageHeaderBorder};
`,
toggle: css`
font-size: ${theme.typography.size.lg};
`,
header: css`
display: flex;
cursor: pointer;
justify-content: space-between;
align-items: center;
padding: ${theme.spacing.sm} ${theme.spacing.md};
font-weight: ${theme.typography.weight.semibold};
`,
body: css`
padding: ${theme.spacing.md};
`,
};
});
import React, { useState } from 'react'; import React from 'react';
import { config } from 'app/core/config';
import { css } from 'emotion'; import { css } from 'emotion';
import AutoSizer from 'react-virtualized-auto-sizer'; import { TabsBar, Tab, stylesFactory, TabContent, TransformationsEditor } from '@grafana/ui';
import useMeasure from 'react-use/lib/useMeasure'; import { DataTransformerConfig, LoadingState, PanelData } from '@grafana/data';
import { TabsBar, Tab, stylesFactory, TabContent } from '@grafana/ui'; import { PanelEditorTab, PanelEditorTabId } from './types';
import { EditorTab, allTabs } from './types';
import { DashboardModel } from '../../state'; import { DashboardModel } from '../../state';
import { QueriesTab } from '../../panel_editor/QueriesTab'; import { QueriesTab } from '../../panel_editor/QueriesTab';
import { PanelModel } from '../../state/PanelModel'; import { PanelModel } from '../../state/PanelModel';
...@@ -12,60 +12,69 @@ import { AlertTab } from 'app/features/alerting/AlertTab'; ...@@ -12,60 +12,69 @@ import { AlertTab } from 'app/features/alerting/AlertTab';
interface PanelEditorTabsProps { interface PanelEditorTabsProps {
panel: PanelModel; panel: PanelModel;
dashboard: DashboardModel; dashboard: DashboardModel;
tabs: PanelEditorTab[];
onChangeTab: (tab: PanelEditorTab) => void;
data: PanelData;
} }
export const PanelEditorTabs: React.FC<PanelEditorTabsProps> = ({ panel, dashboard, tabs, data, onChangeTab }) => {
const styles = getPanelEditorTabsStyles();
const activeTab = tabs.find(item => item.active);
if (tabs.length === 0) {
return null;
}
const onTransformersChange = (transformers: DataTransformerConfig[]) => {
panel.setTransformations(transformers);
};
return (
<div className={styles.wrapper}>
<TabsBar className={styles.tabBar}>
{tabs.map(tab => {
return <Tab key={tab.id} label={tab.text} active={tab.active} onChangeTab={() => onChangeTab(tab)} />;
})}
</TabsBar>
<TabContent className={styles.tabContent}>
{activeTab.id === PanelEditorTabId.Queries && <QueriesTab panel={panel} dashboard={dashboard} />}
{activeTab.id === PanelEditorTabId.Alert && <AlertTab panel={panel} dashboard={dashboard} />}
{activeTab.id === PanelEditorTabId.Transform && data.state !== LoadingState.NotStarted && (
<TransformationsEditor
transformations={panel.transformations || []}
onChange={onTransformersChange}
dataFrames={data.series}
/>
)}
</TabContent>
</div>
);
};
const getPanelEditorTabsStyles = stylesFactory(() => { const getPanelEditorTabsStyles = stylesFactory(() => {
const { theme } = config;
return { return {
wrapper: css` wrapper: css`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
`, `,
content: css` tabBar: css`
padding: 0 ${theme.spacing.sm};
`,
tabContent: css`
padding: 0;
display: flex;
flex-direction: column;
flex-grow: 1; flex-grow: 1;
min-height: 0;
background: ${theme.colors.pageBg};
border-right: 1px solid ${theme.colors.pageHeaderBorder};
.toolbar {
background: transparent;
}
`, `,
}; };
}); });
export const PanelEditorTabs: React.FC<PanelEditorTabsProps> = ({ panel, dashboard }) => {
const [activeTab, setActiveTab] = useState(EditorTab.Query);
const [tabsBarRef, tabsBarMeasurements] = useMeasure();
const styles = getPanelEditorTabsStyles();
return (
<div className={styles.wrapper}>
<div>
<TabsBar ref={tabsBarRef}>
{allTabs.map(t => {
if (t.show(panel)) {
return (
<Tab
label={t.label}
active={activeTab === t.tab}
onChangeTab={() => {
setActiveTab(t.tab);
}}
/>
);
}
return null;
})}
</TabsBar>
</div>
<div style={{ flexGrow: 1 }}>
<TabContent style={{ height: `calc(100% - ${tabsBarMeasurements.height}px)` }}>
<AutoSizer>
{({ width, height }) => {
return (
<div style={{ width, height }}>
{activeTab === EditorTab.Query && <QueriesTab panel={panel} dashboard={dashboard} />}
{activeTab === EditorTab.Alerts && <AlertTab panel={panel} dashboard={dashboard} />}
{activeTab === EditorTab.Transform && <div>TODO: Show Transform</div>}
</div>
);
}}
</AutoSizer>
</TabContent>
</div>
</div>
);
};
import memoizeOne from 'memoize-one';
import { LocationState } from 'app/types';
import { PanelPlugin } from '@grafana/data';
import { PanelEditorTab, PanelEditorTabId } from '../types';
export const getPanelEditorTabs = memoizeOne((location: LocationState, plugin?: PanelPlugin) => {
const tabs: PanelEditorTab[] = [];
if (!plugin) {
return tabs;
}
let defaultTab = PanelEditorTabId.Visualization;
if (!plugin.meta.skipDataQuery) {
defaultTab = PanelEditorTabId.Queries;
tabs.push({
id: PanelEditorTabId.Queries,
text: 'Queries',
active: false,
});
tabs.push({
id: PanelEditorTabId.Transform,
text: 'Transform',
active: false,
});
}
tabs.push({
id: PanelEditorTabId.Visualization,
text: 'Visualization',
active: false,
});
if (plugin.meta.id === 'graph') {
tabs.push({
id: PanelEditorTabId.Alert,
text: 'Alert',
active: false,
});
}
const activeTab = tabs.find(item => item.id === (location.query.tab || defaultTab));
activeTab.active = true;
return tabs;
});
import { PanelModel } from '../../state/PanelModel'; export interface PanelEditorTab {
id: string;
text: string;
active: boolean;
}
export enum PanelEditorTabId {
Queries = 'queries',
Transform = 'transform',
Visualization = 'visualization',
Alert = 'alert',
}
export enum DisplayMode { export enum DisplayMode {
Fill = 0, Fill = 0,
...@@ -11,15 +22,3 @@ export const displayModes = [ ...@@ -11,15 +22,3 @@ export const displayModes = [
{ value: DisplayMode.Fit, label: 'Fit', description: 'Fit in the space keeping ratio' }, { value: DisplayMode.Fit, label: 'Fit', description: 'Fit in the space keeping ratio' },
{ value: DisplayMode.Exact, label: 'Exact', description: 'Same size as the dashboard' }, { value: DisplayMode.Exact, label: 'Exact', description: 'Same size as the dashboard' },
]; ];
export enum EditorTab {
Query = 'query',
Alerts = 'alerts',
Transform = 'xform',
}
export const allTabs = [
{ tab: EditorTab.Query, label: 'Query', show: (panel: PanelModel) => true },
{ tab: EditorTab.Alerts, label: 'Alerts', show: (panel: PanelModel) => true },
{ tab: EditorTab.Transform, label: 'Transform', show: (panel: PanelModel) => true },
];
// Libraries // Libraries
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import _ from 'lodash'; import _ from 'lodash';
import { css } from 'emotion';
// Components // Components
import { EditorTabBody, EditorToolbarView } from './EditorTabBody'; import { EditorTabBody, EditorToolbarView } from './EditorTabBody';
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker'; import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
import { QueryInspector } from './QueryInspector'; import { QueryInspector } from './QueryInspector';
import { QueryOptions } from './QueryOptions'; import { QueryOptions } from './QueryOptions';
import { PanelOptionsGroup, TransformationsEditor, AlphaNotice } from '@grafana/ui'; import { PanelOptionsGroup } from '@grafana/ui';
import { QueryEditorRows } from './QueryEditorRows'; import { QueryEditorRows } from './QueryEditorRows';
// Services // Services
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
...@@ -16,15 +15,7 @@ import config from 'app/core/config'; ...@@ -16,15 +15,7 @@ import config from 'app/core/config';
// Types // Types
import { PanelModel } from '../state/PanelModel'; import { PanelModel } from '../state/PanelModel';
import { DashboardModel } from '../state/DashboardModel'; import { DashboardModel } from '../state/DashboardModel';
import { import { LoadingState, DefaultTimeRange, DataSourceSelectItem, DataQuery, PanelData } from '@grafana/data';
LoadingState,
DataTransformerConfig,
DefaultTimeRange,
DataSourceSelectItem,
DataQuery,
PanelData,
PluginState,
} from '@grafana/data';
import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp'; import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp';
import { addQuery } from 'app/core/utils/query'; import { addQuery } from 'app/core/utils/query';
import { Unsubscribable } from 'rxjs'; import { Unsubscribable } from 'rxjs';
...@@ -219,11 +210,6 @@ export class QueriesTab extends PureComponent<Props, State> { ...@@ -219,11 +210,6 @@ export class QueriesTab extends PureComponent<Props, State> {
this.forceUpdate(); this.forceUpdate();
}; };
onTransformersChange = (transformers: DataTransformerConfig[]) => {
this.props.panel.setTransformations(transformers);
this.forceUpdate();
};
setScrollTop = (event: React.MouseEvent<HTMLElement>) => { setScrollTop = (event: React.MouseEvent<HTMLElement>) => {
const target = event.target as HTMLElement; const target = event.target as HTMLElement;
this.setState({ scrollTop: target.scrollTop }); this.setState({ scrollTop: target.scrollTop });
...@@ -256,7 +242,7 @@ export class QueriesTab extends PureComponent<Props, State> { ...@@ -256,7 +242,7 @@ export class QueriesTab extends PureComponent<Props, State> {
}; };
render() { render() {
const { scrollTop, data } = this.state; const { scrollTop } = this.state;
const queryInspector: EditorToolbarView = { const queryInspector: EditorToolbarView = {
title: 'Query Inspector', title: 'Query Inspector',
render: this.renderQueryInspector, render: this.renderQueryInspector,
...@@ -268,8 +254,6 @@ export class QueriesTab extends PureComponent<Props, State> { ...@@ -268,8 +254,6 @@ export class QueriesTab extends PureComponent<Props, State> {
render: this.renderHelp, render: this.renderHelp,
}; };
const enableTransformations = config.featureToggles.transformations;
return ( return (
<EditorTabBody <EditorTabBody
heading="Query" heading="Query"
...@@ -278,33 +262,7 @@ export class QueriesTab extends PureComponent<Props, State> { ...@@ -278,33 +262,7 @@ export class QueriesTab extends PureComponent<Props, State> {
setScrollTop={this.setScrollTop} setScrollTop={this.setScrollTop}
scrollTop={scrollTop} scrollTop={scrollTop}
> >
<> <>{this.renderQueryBody()}</>
{this.renderQueryBody()}
{enableTransformations && (
<PanelOptionsGroup
title={
<>
Query results
<AlphaNotice
state={PluginState.alpha}
className={css`
margin-left: 16px;
`}
/>
</>
}
>
{this.state.data.state !== LoadingState.NotStarted && (
<TransformationsEditor
transformations={this.props.panel.transformations || []}
onChange={this.onTransformersChange}
dataFrames={data.series}
/>
)}
</PanelOptionsGroup>
)}
</>
</EditorTabBody> </EditorTabBody>
); );
} }
......
...@@ -103,6 +103,7 @@ exports[`Render should render alpha info text 1`] = ` ...@@ -103,6 +103,7 @@ exports[`Render should render alpha info text 1`] = `
DataSourcePlugin { DataSourcePlugin {
"DataSourceClass": Object {}, "DataSourceClass": Object {},
"components": Object {}, "components": Object {},
"meta": Object {},
} }
} }
/> />
...@@ -292,6 +293,7 @@ exports[`Render should render is ready only message 1`] = ` ...@@ -292,6 +293,7 @@ exports[`Render should render is ready only message 1`] = `
DataSourcePlugin { DataSourcePlugin {
"DataSourceClass": Object {}, "DataSourceClass": Object {},
"components": Object {}, "components": Object {},
"meta": Object {},
} }
} }
/> />
......
import { DashboardAcl } from './acl'; import { DashboardAcl } from './acl';
import { DataQuery } from '@grafana/data'; import { DataQuery } from '@grafana/data';
import { AngularComponent } from '@grafana/runtime';
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
export interface DashboardDTO { export interface DashboardDTO {
...@@ -70,7 +69,6 @@ export interface QueriesToUpdateOnDashboardLoad { ...@@ -70,7 +69,6 @@ export interface QueriesToUpdateOnDashboardLoad {
export interface PanelState { export interface PanelState {
pluginId: string; pluginId: string;
angularPanel?: AngularComponent;
} }
export interface DashboardState { export interface DashboardState {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment