diff --git a/packages/grafana-ui/src/utils/index.ts b/packages/grafana-ui/src/utils/index.ts index d959255e1f1..40b00059b47 100644 --- a/packages/grafana-ui/src/utils/index.ts +++ b/packages/grafana-ui/src/utils/index.ts @@ -5,6 +5,7 @@ export * from './dataLinks'; export * from './tags'; export * from './scrollbar'; export * from './measureText'; +export * from './useForceUpdate'; export { default as ansicolor } from './ansicolor'; import * as DOMUtil from './dom'; // includes Element.closest polyfill diff --git a/packages/grafana-ui/src/utils/useForceUpdate.ts b/packages/grafana-ui/src/utils/useForceUpdate.ts new file mode 100644 index 00000000000..e1e17230f44 --- /dev/null +++ b/packages/grafana-ui/src/utils/useForceUpdate.ts @@ -0,0 +1,7 @@ +import { useState } from 'react'; + +/** @internal */ +export function useForceUpdate() { + const [value, setValue] = useState(0); // integer state + return () => setValue(value + 1); // update the state to force render +} diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx index 4cb52e2cbb1..dd76f1faf28 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx @@ -3,23 +3,21 @@ import { QueryGroup } from 'app/features/query/components/QueryGroup'; import { PanelModel } from '../../state'; import { getLocationSrv } from '@grafana/runtime'; import { QueryGroupOptions } from 'app/types'; +import { DataQuery } from '@grafana/data'; interface Props { + /** Current panel */ panel: PanelModel; + /** Added here to make component re-render when queries change from outside */ + queries: DataQuery[]; } -interface State { - options: QueryGroupOptions; -} - -export class PanelEditorQueries extends PureComponent { +export class PanelEditorQueries extends PureComponent { constructor(props: Props) { super(props); - - this.state = { options: this.buildQueryOptions(props) }; } - buildQueryOptions({ panel }: Props): QueryGroupOptions { + buildQueryOptions(panel: PanelModel): QueryGroupOptions { return { dataSource: { name: panel.datasource, @@ -51,20 +49,17 @@ export class PanelEditorQueries extends PureComponent { const newDataSourceName = options.dataSource.default ? null : options.dataSource.name!; const dataSourceChanged = newDataSourceName !== panel.datasource; - panel.updateQueries(options); if (dataSourceChanged) { // trigger queries when changing data source setTimeout(this.onRunQueries, 10); } - - this.setState({ options: options }); }; render() { const { panel } = this.props; - const { options } = this.state; + const options = this.buildQueryOptions(panel); return ( void; } -export class PanelEditorTabs extends PureComponent { - private eventSubs = new Subscription(); +export const PanelEditorTabs: FC = React.memo(({ panel, dashboard, tabs, onChangeTab }) => { + const forceUpdate = useForceUpdate(); + const styles = useStyles(getStyles); - componentDidMount() { - const { events } = this.props.panel; - this.eventSubs.add(events.subscribe(PanelQueriesChangedEvent, this.triggerForceUpdate)); - this.eventSubs.add(events.subscribe(PanelTransformationsChangedEvent, this.triggerForceUpdate)); - } + useEffect(() => { + const eventSubs = new Subscription(); + eventSubs.add(panel.events.subscribe(PanelQueriesChangedEvent, forceUpdate)); + eventSubs.add(panel.events.subscribe(PanelTransformationsChangedEvent, forceUpdate)); + return () => eventSubs.unsubscribe(); + }, [panel, forceUpdate]); - componentWillUnmount() { - this.eventSubs.unsubscribe(); - } - - triggerForceUpdate = () => { - this.forceUpdate(); - }; - - getCounter = (tab: PanelEditorTab) => { - const { panel } = this.props; - - switch (tab.id) { - case PanelEditorTabId.Query: - return panel.targets.length; - case PanelEditorTabId.Alert: - return panel.alert ? 1 : 0; - case PanelEditorTabId.Transform: - const transformations = panel.getTransformations() ?? []; - return transformations.length; - } + const activeTab = tabs.find((item) => item.active)!; + if (tabs.length === 0) { return null; - }; - - render() { - const { dashboard, onChangeTab, tabs, panel } = this.props; - const styles = getPanelEditorTabsStyles(); - const activeTab = tabs.find((item) => item.active)!; - - if (tabs.length === 0) { - return null; - } - - return ( -
- - {tabs.map((tab) => { - return ( - onChangeTab(tab)} - icon={tab.icon as IconName} - counter={this.getCounter(tab)} - /> - ); - })} - - - {activeTab.id === PanelEditorTabId.Query && } - {activeTab.id === PanelEditorTabId.Alert && } - {activeTab.id === PanelEditorTabId.Transform && } - -
- ); } + + return ( +
+ + {tabs.map((tab) => { + return ( + onChangeTab(tab)} + icon={tab.icon as IconName} + counter={getCounter(panel, tab)} + /> + ); + })} + + + {activeTab.id === PanelEditorTabId.Query && } + {activeTab.id === PanelEditorTabId.Alert && } + {activeTab.id === PanelEditorTabId.Transform && } + +
+ ); +}); + +PanelEditorTabs.displayName = 'PanelEditorTabs'; + +function getCounter(panel: PanelModel, tab: PanelEditorTab) { + switch (tab.id) { + case PanelEditorTabId.Query: + return panel.targets.length; + case PanelEditorTabId.Alert: + return panel.alert ? 1 : 0; + case PanelEditorTabId.Transform: + const transformations = panel.getTransformations() ?? []; + return transformations.length; + } + + return null; } -const getPanelEditorTabsStyles = stylesFactory(() => { - const { theme } = config; - +const getStyles = (theme: GrafanaTheme) => { return { wrapper: css` display: flex; @@ -111,4 +99,4 @@ const getPanelEditorTabsStyles = stylesFactory(() => { } `, }; -}); +};