mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
NewPanelEditor: Panel editor tabs in state (url) (#22102)
* tabs & style tweaks * Styling updates * ok look * tweaks * Updated snapshots * Moved transforms * Updated
This commit is contained in:
parent
8080bbc8ec
commit
df1d43167a
@ -123,7 +123,7 @@ export interface PluginConfigPage<T extends PluginMeta> {
|
||||
|
||||
export class GrafanaPlugin<T extends PluginMeta = PluginMeta> {
|
||||
// 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
|
||||
loadError?: boolean;
|
||||
@ -142,4 +142,8 @@ export class GrafanaPlugin<T extends PluginMeta = PluginMeta> {
|
||||
this.configPages.push(tab);
|
||||
return this;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.meta = {} as T;
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
flex-direction: row;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
border-left: 2px solid transparent;
|
||||
&:hover {
|
||||
background: ${optionBgHover};
|
||||
}
|
||||
|
@ -8,21 +8,16 @@ import {
|
||||
DynamicConfigValue,
|
||||
VariableSuggestionsScope,
|
||||
} from '@grafana/data';
|
||||
import {
|
||||
standardFieldConfigEditorRegistry,
|
||||
Forms,
|
||||
fieldMatchersUI,
|
||||
ControlledCollapse,
|
||||
ValuePicker,
|
||||
} from '@grafana/ui';
|
||||
import { standardFieldConfigEditorRegistry, Forms, fieldMatchersUI, ValuePicker } from '@grafana/ui';
|
||||
import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
|
||||
import { OptionsGroup } from './OptionsGroup';
|
||||
|
||||
interface Props {
|
||||
config: FieldConfigSource;
|
||||
custom?: FieldConfigEditorRegistry; // custom fields
|
||||
include?: string[]; // Ordered list of which fields should be shown/included
|
||||
onChange: (config: FieldConfigSource) => void;
|
||||
|
||||
// Helpful for IntelliSense
|
||||
/* Helpful for IntelliSense */
|
||||
data: DataFrame[];
|
||||
}
|
||||
|
||||
@ -33,6 +28,7 @@ export class FieldConfigEditor extends React.PureComponent<Props> {
|
||||
private setDefaultValue = (name: string, value: any, custom: boolean) => {
|
||||
const defaults = { ...this.props.config.defaults };
|
||||
const remove = value === undefined || value === null || '';
|
||||
|
||||
if (custom) {
|
||||
if (defaults.custom) {
|
||||
if (remove) {
|
||||
@ -236,17 +232,14 @@ export class FieldConfigEditor extends React.PureComponent<Props> {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<ControlledCollapse label="Standard Field Configuration" collapsible>
|
||||
{this.renderStandardConfigs()}
|
||||
</ControlledCollapse>
|
||||
{this.props.custom && (
|
||||
<ControlledCollapse label="Standard Field Configuration">{this.renderCustomConfigs()}</ControlledCollapse>
|
||||
)}
|
||||
<OptionsGroup title="Field configuration">{this.renderStandardConfigs()}</OptionsGroup>
|
||||
|
||||
<ControlledCollapse label="Field Overrides" collapsible>
|
||||
{this.props.custom && <OptionsGroup title="Visualization options">{this.renderCustomConfigs()}</OptionsGroup>}
|
||||
|
||||
<OptionsGroup title="Field Overrides">
|
||||
{this.renderOverrides()}
|
||||
{this.renderAddOverride()}
|
||||
</ControlledCollapse>
|
||||
</OptionsGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,48 @@
|
||||
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};
|
||||
`,
|
||||
};
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { GrafanaTheme, FieldConfigSource, PanelData, PanelPlugin, SelectableValue } from '@grafana/data';
|
||||
import { stylesFactory, Forms, CustomScrollbar, selectThemeVariant, ControlledCollapse } from '@grafana/ui';
|
||||
import { stylesFactory, Forms, CustomScrollbar, selectThemeVariant } from '@grafana/ui';
|
||||
import { css, cx } from 'emotion';
|
||||
import config from 'app/core/config';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
@ -15,7 +15,7 @@ import { connect, MapStateToProps, MapDispatchToProps } from 'react-redux';
|
||||
import { updateLocation } from '../../../../core/reducers/location';
|
||||
import { Unsubscribable } from 'rxjs';
|
||||
import { PanelTitle } from './PanelTitle';
|
||||
import { DisplayMode, displayModes } from './types';
|
||||
import { DisplayMode, displayModes, PanelEditorTab } from './types';
|
||||
import { PanelEditorTabs } from './PanelEditorTabs';
|
||||
import { DashNavTimeControls } from '../DashNav/DashNavTimeControls';
|
||||
import { LocationState } from 'app/types';
|
||||
@ -23,6 +23,8 @@ import { calculatePanelSize } from './utils';
|
||||
import { initPanelEditor, panelEditorCleanUp } from './state/actions';
|
||||
import { setDisplayMode, toggleOptionsView, setDiscardChanges } from './state/reducers';
|
||||
import { FieldConfigEditor } from './FieldConfigEditor';
|
||||
import { OptionsGroup } from './OptionsGroup';
|
||||
import { getPanelEditorTabs } from './state/selectors';
|
||||
|
||||
interface OwnProps {
|
||||
dashboard: DashboardModel;
|
||||
@ -37,6 +39,7 @@ interface ConnectedProps {
|
||||
mode: DisplayMode;
|
||||
isPanelOptionsVisible: boolean;
|
||||
initDone: boolean;
|
||||
tabs: PanelEditorTab[];
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
@ -76,6 +79,10 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
});
|
||||
};
|
||||
|
||||
onChangeTab = (tab: PanelEditorTab) => {
|
||||
this.props.updateLocation({ query: { tab: tab.id }, partial: true });
|
||||
};
|
||||
|
||||
onFieldConfigsChange = (fieldOptions: FieldConfigSource) => {
|
||||
// NOTE: for now, assume this is from 'fieldOptions' -- TODO? put on panel model directly?
|
||||
const { panel } = this.props;
|
||||
@ -135,7 +142,10 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
|
||||
onDragFinished = () => {
|
||||
document.body.style.cursor = 'auto';
|
||||
console.log('TODO, save splitter settings');
|
||||
};
|
||||
|
||||
onDragStarted = () => {
|
||||
document.body.style.cursor = 'row-resize';
|
||||
};
|
||||
|
||||
onPanelTitleChange = (title: string) => {
|
||||
@ -152,16 +162,17 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
};
|
||||
|
||||
renderHorizontalSplit(styles: any) {
|
||||
const { dashboard, panel, mode } = this.props;
|
||||
const { dashboard, panel, mode, tabs, data } = this.props;
|
||||
|
||||
return (
|
||||
<SplitPane
|
||||
split="horizontal"
|
||||
minSize={50}
|
||||
primary="second"
|
||||
defaultSize="40%"
|
||||
primary="first"
|
||||
defaultSize="45%"
|
||||
pane2Style={{ minHeight: 0 }}
|
||||
resizerClassName={styles.resizerH}
|
||||
onDragStarted={() => (document.body.style.cursor = 'row-resize')}
|
||||
onDragStarted={this.onDragStarted}
|
||||
onDragFinished={this.onDragFinished}
|
||||
>
|
||||
<div className={styles.panelWrapper}>
|
||||
@ -188,7 +199,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
</AutoSizer>
|
||||
</div>
|
||||
<div className={styles.noScrollPaneContent}>
|
||||
<PanelEditorTabs panel={panel} dashboard={dashboard} />
|
||||
<PanelEditorTabs panel={panel} dashboard={dashboard} tabs={tabs} onChangeTab={this.onChangeTab} data={data} />
|
||||
</div>
|
||||
</SplitPane>
|
||||
);
|
||||
@ -212,22 +223,35 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
<PanelTitle value={panel.title} onChange={this.onPanelTitleChange} />
|
||||
</div>
|
||||
<div className={styles.toolbarLeft}>
|
||||
<Forms.Select
|
||||
value={displayModes.find(v => v.value === mode)}
|
||||
options={displayModes}
|
||||
onChange={this.onDiplayModeChange}
|
||||
/>
|
||||
<Forms.Button icon="fa fa-cog" variant="secondary" onClick={this.onTogglePanelOptions} />
|
||||
<Forms.Button variant="destructive" onClick={this.onDiscard}>
|
||||
Discard
|
||||
</Forms.Button>
|
||||
|
||||
<div className={styles.toolbarItem}>
|
||||
<Forms.Button
|
||||
className={styles.toolbarItem}
|
||||
icon="fa fa-remove"
|
||||
variant="destructive"
|
||||
onClick={this.onDiscard}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.toolbarItem}>
|
||||
<Forms.Select
|
||||
value={displayModes.find(v => v.value === mode)}
|
||||
options={displayModes}
|
||||
onChange={this.onDiplayModeChange}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.toolbarItem}>
|
||||
<Forms.Button
|
||||
className={styles.toolbarItem}
|
||||
icon="fa fa-sliders"
|
||||
variant="secondary"
|
||||
onClick={this.onTogglePanelOptions}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<DashNavTimeControls dashboard={dashboard} location={location} updateLocation={updateLocation} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.panes}>
|
||||
<div className={styles.editorBody}>
|
||||
{isPanelOptionsVisible ? (
|
||||
<SplitPane
|
||||
split="vertical"
|
||||
@ -239,14 +263,10 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
onDragFinished={this.onDragFinished}
|
||||
>
|
||||
{this.renderHorizontalSplit(styles)}
|
||||
<div className={styles.noScrollPaneContent}>
|
||||
<div className={styles.panelOptionsPane}>
|
||||
<CustomScrollbar>
|
||||
<div style={{ padding: '10px' }}>
|
||||
{this.renderFieldOptions()}
|
||||
<ControlledCollapse label="Visualization Settings" collapsible>
|
||||
{this.renderVisSettings()}
|
||||
</ControlledCollapse>
|
||||
</div>
|
||||
{this.renderFieldOptions()}
|
||||
<OptionsGroup title="Old settings">{this.renderVisSettings()}</OptionsGroup>
|
||||
</CustomScrollbar>
|
||||
</div>
|
||||
</SplitPane>
|
||||
@ -259,15 +279,20 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps: MapStateToProps<ConnectedProps, OwnProps, StoreState> = (state, props) => ({
|
||||
location: state.location,
|
||||
plugin: state.plugins.panels[props.sourcePanel.type],
|
||||
panel: state.panelEditorNew.getPanel(),
|
||||
mode: state.panelEditorNew.mode,
|
||||
isPanelOptionsVisible: state.panelEditorNew.isPanelOptionsVisible,
|
||||
data: state.panelEditorNew.getData(),
|
||||
initDone: state.panelEditorNew.initDone,
|
||||
});
|
||||
const mapStateToProps: MapStateToProps<ConnectedProps, OwnProps, StoreState> = (state, props) => {
|
||||
const plugin = state.plugins.panels[props.sourcePanel.type];
|
||||
|
||||
return {
|
||||
location: state.location,
|
||||
plugin: plugin,
|
||||
panel: state.panelEditorNew.getPanel(),
|
||||
mode: state.panelEditorNew.mode,
|
||||
isPanelOptionsVisible: state.panelEditorNew.isPanelOptionsVisible,
|
||||
data: state.panelEditorNew.getData(),
|
||||
initDone: state.panelEditorNew.initDone,
|
||||
tabs: getPanelEditorTabs(state.location, plugin),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = {
|
||||
updateLocation,
|
||||
@ -284,20 +309,22 @@ export const PanelEditor = connect(mapStateToProps, mapDispatchToProps)(PanelEdi
|
||||
* Styles
|
||||
*/
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
const handleColor = selectThemeVariant(
|
||||
{
|
||||
dark: theme.colors.dark9,
|
||||
light: theme.colors.gray6,
|
||||
},
|
||||
theme.type
|
||||
);
|
||||
const handleColor = theme.colors.blueLight;
|
||||
const background = selectThemeVariant({ light: theme.colors.white, dark: theme.colors.inputBlack }, theme.type);
|
||||
|
||||
const resizer = css`
|
||||
padding: 3px;
|
||||
font-style: italic;
|
||||
background: ${theme.colors.panelBg};
|
||||
background: transparent;
|
||||
border-top: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 0;
|
||||
border-left: 0;
|
||||
border-color: transparent;
|
||||
border-style: solid;
|
||||
transition: 0.2s border-color ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background: ${handleColor};
|
||||
border-color: ${handleColor};
|
||||
}
|
||||
`;
|
||||
|
||||
@ -311,9 +338,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: ${theme.colors.pageBg};
|
||||
background: ${background};
|
||||
`,
|
||||
panelWrapper: css`
|
||||
padding: 0 2px 2px ${theme.spacing.sm};
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`,
|
||||
@ -321,33 +349,49 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
resizer,
|
||||
css`
|
||||
cursor: col-resize;
|
||||
width: 8px;
|
||||
border-right-width: 1px;
|
||||
`
|
||||
),
|
||||
resizerH: cx(
|
||||
resizer,
|
||||
css`
|
||||
height: 8px;
|
||||
cursor: row-resize;
|
||||
position: relative;
|
||||
top: 49px;
|
||||
z-index: 1;
|
||||
border-top-width: 1px;
|
||||
`
|
||||
),
|
||||
noScrollPaneContent: css`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
`,
|
||||
panelOptionsPane: css`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: ${theme.colors.pageBg};
|
||||
border-top: 1px solid ${theme.colors.pageHeaderBorder};
|
||||
border-left: 1px solid ${theme.colors.pageHeaderBorder};
|
||||
`,
|
||||
toolbar: css`
|
||||
padding: ${theme.spacing.sm};
|
||||
height: 48px;
|
||||
height: 55px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
`,
|
||||
panes: css`
|
||||
height: calc(100% - 48px);
|
||||
editorBody: css`
|
||||
height: calc(100% - 55px);
|
||||
position: relative;
|
||||
`,
|
||||
toolbarLeft: css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`,
|
||||
toolbarItem: css`
|
||||
margin-right: ${theme.spacing.sm};
|
||||
`,
|
||||
centeringContainer: css`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { config } from 'app/core/config';
|
||||
import { css } from 'emotion';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import useMeasure from 'react-use/lib/useMeasure';
|
||||
import { TabsBar, Tab, stylesFactory, TabContent } from '@grafana/ui';
|
||||
import { EditorTab, allTabs } from './types';
|
||||
import { TabsBar, Tab, stylesFactory, TabContent, TransformationsEditor } from '@grafana/ui';
|
||||
import { DataTransformerConfig, LoadingState, PanelData } from '@grafana/data';
|
||||
import { PanelEditorTab, PanelEditorTabId } from './types';
|
||||
import { DashboardModel } from '../../state';
|
||||
import { QueriesTab } from '../../panel_editor/QueriesTab';
|
||||
import { PanelModel } from '../../state/PanelModel';
|
||||
@ -12,60 +12,69 @@ import { AlertTab } from 'app/features/alerting/AlertTab';
|
||||
interface PanelEditorTabsProps {
|
||||
panel: PanelModel;
|
||||
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 { theme } = config;
|
||||
|
||||
return {
|
||||
wrapper: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
`,
|
||||
content: css`
|
||||
tabBar: css`
|
||||
padding: 0 ${theme.spacing.sm};
|
||||
`,
|
||||
tabContent: css`
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,49 @@
|
||||
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;
|
||||
});
|
@ -1,4 +1,15 @@
|
||||
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 {
|
||||
Fill = 0,
|
||||
@ -11,15 +22,3 @@ export const displayModes = [
|
||||
{ value: DisplayMode.Fit, label: 'Fit', description: 'Fit in the space keeping ratio' },
|
||||
{ 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 },
|
||||
];
|
||||
|
@ -1,13 +1,12 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { css } from 'emotion';
|
||||
// Components
|
||||
import { EditorTabBody, EditorToolbarView } from './EditorTabBody';
|
||||
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
||||
import { QueryInspector } from './QueryInspector';
|
||||
import { QueryOptions } from './QueryOptions';
|
||||
import { PanelOptionsGroup, TransformationsEditor, AlphaNotice } from '@grafana/ui';
|
||||
import { PanelOptionsGroup } from '@grafana/ui';
|
||||
import { QueryEditorRows } from './QueryEditorRows';
|
||||
// Services
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
@ -16,15 +15,7 @@ import config from 'app/core/config';
|
||||
// Types
|
||||
import { PanelModel } from '../state/PanelModel';
|
||||
import { DashboardModel } from '../state/DashboardModel';
|
||||
import {
|
||||
LoadingState,
|
||||
DataTransformerConfig,
|
||||
DefaultTimeRange,
|
||||
DataSourceSelectItem,
|
||||
DataQuery,
|
||||
PanelData,
|
||||
PluginState,
|
||||
} from '@grafana/data';
|
||||
import { LoadingState, DefaultTimeRange, DataSourceSelectItem, DataQuery, PanelData } from '@grafana/data';
|
||||
import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp';
|
||||
import { addQuery } from 'app/core/utils/query';
|
||||
import { Unsubscribable } from 'rxjs';
|
||||
@ -219,11 +210,6 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
onTransformersChange = (transformers: DataTransformerConfig[]) => {
|
||||
this.props.panel.setTransformations(transformers);
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
setScrollTop = (event: React.MouseEvent<HTMLElement>) => {
|
||||
const target = event.target as HTMLElement;
|
||||
this.setState({ scrollTop: target.scrollTop });
|
||||
@ -256,7 +242,7 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { scrollTop, data } = this.state;
|
||||
const { scrollTop } = this.state;
|
||||
const queryInspector: EditorToolbarView = {
|
||||
title: 'Query Inspector',
|
||||
render: this.renderQueryInspector,
|
||||
@ -268,8 +254,6 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
render: this.renderHelp,
|
||||
};
|
||||
|
||||
const enableTransformations = config.featureToggles.transformations;
|
||||
|
||||
return (
|
||||
<EditorTabBody
|
||||
heading="Query"
|
||||
@ -278,33 +262,7 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
setScrollTop={this.setScrollTop}
|
||||
scrollTop={scrollTop}
|
||||
>
|
||||
<>
|
||||
{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>
|
||||
)}
|
||||
</>
|
||||
<>{this.renderQueryBody()}</>
|
||||
</EditorTabBody>
|
||||
);
|
||||
}
|
||||
|
@ -103,6 +103,7 @@ exports[`Render should render alpha info text 1`] = `
|
||||
DataSourcePlugin {
|
||||
"DataSourceClass": Object {},
|
||||
"components": Object {},
|
||||
"meta": Object {},
|
||||
}
|
||||
}
|
||||
/>
|
||||
@ -292,6 +293,7 @@ exports[`Render should render is ready only message 1`] = `
|
||||
DataSourcePlugin {
|
||||
"DataSourceClass": Object {},
|
||||
"components": Object {},
|
||||
"meta": Object {},
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { DashboardAcl } from './acl';
|
||||
import { DataQuery } from '@grafana/data';
|
||||
import { AngularComponent } from '@grafana/runtime';
|
||||
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
||||
|
||||
export interface DashboardDTO {
|
||||
@ -70,7 +69,6 @@ export interface QueriesToUpdateOnDashboardLoad {
|
||||
|
||||
export interface PanelState {
|
||||
pluginId: string;
|
||||
angularPanel?: AngularComponent;
|
||||
}
|
||||
|
||||
export interface DashboardState {
|
||||
|
Loading…
Reference in New Issue
Block a user