diff --git a/.eslintrc b/.eslintrc index 01ec2d6a0d1..5cf34853391 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,19 +1,14 @@ { "extends": ["@grafana/eslint-config"], "root": true, - "plugins": [ - "no-only-tests" - ], + "plugins": ["no-only-tests"], "rules": { "no-only-tests/no-only-tests": "error", "react/prop-types": "off" }, "overrides": [ { - "files": [ - "packages/grafana-ui/src/components/uPlot/**/*.{ts,tsx}", - "public/app/**/*.{ts,tsx}" - ], + "files": ["packages/grafana-ui/src/components/uPlot/**/*.{ts,tsx}"], "rules": { "react-hooks/rules-of-hooks": "off", "react-hooks/exhaustive-deps": "off" diff --git a/packages/grafana-ui/src/components/DataSourceSettings/SigV4AuthSettings.tsx b/packages/grafana-ui/src/components/DataSourceSettings/SigV4AuthSettings.tsx index 9edd7f7d62a..c6bb4e44aee 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/SigV4AuthSettings.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/SigV4AuthSettings.tsx @@ -46,6 +46,7 @@ export const SigV4AuthSettings: React.FC = (props) => { useEffect(() => { const sigV4AuthType = dataSourceConfig.jsonData.sigV4AuthType || 'default'; onJsonDataChange('sigV4AuthType', sigV4AuthType); + // We can't enforce the eslint rule here because we only want to run this once. // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/public/app/core/components/TransformersUI/FilterByValueTransformer/FilterByValueFilterEditor.tsx b/public/app/core/components/TransformersUI/FilterByValueTransformer/FilterByValueFilterEditor.tsx index ffbf3236dae..a3b7a9db993 100644 --- a/public/app/core/components/TransformersUI/FilterByValueTransformer/FilterByValueFilterEditor.tsx +++ b/public/app/core/components/TransformersUI/FilterByValueTransformer/FilterByValueFilterEditor.tsx @@ -21,19 +21,10 @@ export const FilterByValueFilterEditor: React.FC = (props) => { const { fieldsAsOptions, fieldByDisplayName } = fieldsInfo; const fieldName = getFieldName(filter, fieldsAsOptions) ?? ''; const field = fieldByDisplayName[fieldName]; - - if (!field) { - return null; - } - const matcherOptions = getMatcherOptions(field); const matcherId = getSelectedMatcherId(filter, matcherOptions); const editor = valueMatchersUI.getIfExists(matcherId); - if (!editor || !editor.component) { - return null; - } - const onChangeField = useCallback( (selectable?: SelectableValue) => { if (!selectable?.value) { @@ -77,6 +68,10 @@ export const FilterByValueFilterEditor: React.FC = (props) => { [onChange, filter] ); + if (!field || !editor || !editor.component) { + return null; + } + return (
diff --git a/public/app/core/components/TransformersUI/FilterByValueTransformer/FilterByValueTransformerEditor.tsx b/public/app/core/components/TransformersUI/FilterByValueTransformer/FilterByValueTransformerEditor.tsx index e3770874d79..3229a7fab07 100644 --- a/public/app/core/components/TransformersUI/FilterByValueTransformer/FilterByValueTransformerEditor.tsx +++ b/public/app/core/components/TransformersUI/FilterByValueTransformer/FilterByValueTransformerEditor.tsx @@ -59,7 +59,7 @@ export const FilterByValueTransformerEditor: React.FC { diff --git a/public/app/core/components/TransformersUI/FilterByValueTransformer/ValueMatchers/BasicMatcherEditor.tsx b/public/app/core/components/TransformersUI/FilterByValueTransformer/ValueMatchers/BasicMatcherEditor.tsx index a25f472d386..00499b90cc3 100644 --- a/public/app/core/components/TransformersUI/FilterByValueTransformer/ValueMatchers/BasicMatcherEditor.tsx +++ b/public/app/core/components/TransformersUI/FilterByValueTransformer/ValueMatchers/BasicMatcherEditor.tsx @@ -7,7 +7,7 @@ import { convertToType } from './utils'; export function basicMatcherEditor( config: ValueMatcherEditorConfig ): React.FC>> { - return function render({ options, onChange, field }) { + return function Render({ options, onChange, field }) { const { validator, converter = convertToType } = config; const { value } = options; const [isInvalid, setInvalid] = useState(!validator(value)); diff --git a/public/app/core/components/TransformersUI/GroupByTransformerEditor.tsx b/public/app/core/components/TransformersUI/GroupByTransformerEditor.tsx index 234fbc5aec5..96c0cc3b464 100644 --- a/public/app/core/components/TransformersUI/GroupByTransformerEditor.tsx +++ b/public/app/core/components/TransformersUI/GroupByTransformerEditor.tsx @@ -40,7 +40,9 @@ export const GroupByTransformerEditor: React.FC { onChange({ ...options, sort: [cfg] }); }, - [options] + [onChange, options] ); const sorts = options.sort?.length ? options.sort : [{} as SortByField]; diff --git a/public/app/core/components/connectWithCleanUp.tsx b/public/app/core/components/connectWithCleanUp.tsx index 1f150a45a29..57584187643 100644 --- a/public/app/core/components/connectWithCleanUp.tsx +++ b/public/app/core/components/connectWithCleanUp.tsx @@ -27,7 +27,7 @@ export const connectWithCleanUp = < return function cleanUp() { dispatch(cleanUpAction({ stateSelector })); }; - }, []); + }, [dispatch]); // @ts-ignore return ; }; diff --git a/public/app/features/admin/AdminEditOrgPage.tsx b/public/app/features/admin/AdminEditOrgPage.tsx index e0822c713e3..ab22f9ad542 100644 --- a/public/app/features/admin/AdminEditOrgPage.tsx +++ b/public/app/features/admin/AdminEditOrgPage.tsx @@ -46,7 +46,7 @@ export const AdminEditOrgPage: FC = ({ match }) => { useEffect(() => { fetchOrg(); fetchOrgUsers().then((res) => setUsers(res)); - }, []); + }, [fetchOrg, fetchOrgUsers]); const updateOrgName = async (name: string) => { return await getBackendSrv().put('/api/orgs/' + orgId, { ...orgState.value, name }); diff --git a/public/app/features/admin/AdminListOrgsPage.tsx b/public/app/features/admin/AdminListOrgsPage.tsx index 164ddda487a..b46de682e71 100644 --- a/public/app/features/admin/AdminListOrgsPage.tsx +++ b/public/app/features/admin/AdminListOrgsPage.tsx @@ -23,7 +23,7 @@ export const AdminListOrgsPages: FC = () => { useEffect(() => { fetchOrgs(); - }, []); + }, [fetchOrgs]); return ( diff --git a/public/app/features/admin/UserCreatePage.tsx b/public/app/features/admin/UserCreatePage.tsx index 4dad3cafb92..d76ed84da26 100644 --- a/public/app/features/admin/UserCreatePage.tsx +++ b/public/app/features/admin/UserCreatePage.tsx @@ -24,10 +24,13 @@ const createUser = async (user: UserDTO) => getBackendSrv().post('/api/admin/use const UserCreatePage: React.FC = ({ navModel }) => { const history = useHistory(); - const onSubmit = useCallback(async (data: UserDTO) => { - await createUser(data); - history.push('/admin/users'); - }, []); + const onSubmit = useCallback( + async (data: UserDTO) => { + await createUser(data); + history.push('/admin/users'); + }, + [history] + ); return ( diff --git a/public/app/features/admin/UserListAdminPage.tsx b/public/app/features/admin/UserListAdminPage.tsx index 5b0463f56f4..77e4885bcd4 100644 --- a/public/app/features/admin/UserListAdminPage.tsx +++ b/public/app/features/admin/UserListAdminPage.tsx @@ -31,13 +31,14 @@ type Props = OwnProps & ConnectedProps & DispatchProps; const UserListAdminPageUnConnected: React.FC = (props) => { const styles = getStyles(); + const { fetchUsers, navModel, query, changeQuery, users, showPaging, totalPages, page, changePage } = props; useEffect(() => { - props.fetchUsers(); - }, []); + fetchUsers(); + }, [fetchUsers]); return ( - + <>
@@ -48,9 +49,9 @@ const UserListAdminPageUnConnected: React.FC = (props) => { placeholder="Search user by login, email or name" tabIndex={1} autoFocus={true} - value={props.query} + value={query} spellCheck={false} - onChange={(event) => props.changeQuery(event.currentTarget.value)} + onChange={(event) => changeQuery(event.currentTarget.value)} prefix={} /> @@ -77,12 +78,10 @@ const UserListAdminPageUnConnected: React.FC = (props) => { - {props.users.map(renderUser)} + {users.map(renderUser)}
- {props.showPaging && ( - - )} + {showPaging && }
diff --git a/public/app/features/alerting/NotificationsListPage.tsx b/public/app/features/alerting/NotificationsListPage.tsx index 2f4234ba20c..e391e11a875 100644 --- a/public/app/features/alerting/NotificationsListPage.tsx +++ b/public/app/features/alerting/NotificationsListPage.tsx @@ -23,7 +23,7 @@ const NotificationsListPage: FC = () => { fetchNotifications().then((res) => { setNotifications(res); }); - }, []); + }, [fetchNotifications]); const deleteNotification = (id: number) => { appEvents.publish( diff --git a/public/app/features/alerting/components/AlertingQueryPreview.tsx b/public/app/features/alerting/components/AlertingQueryPreview.tsx index 6a7e0537838..27b8f95b8c2 100644 --- a/public/app/features/alerting/components/AlertingQueryPreview.tsx +++ b/public/app/features/alerting/components/AlertingQueryPreview.tsx @@ -31,7 +31,7 @@ interface Props { export const AlertingQueryPreview: FC = ({ getInstances, onRunQueries, onTest, queries, queryRunner }) => { const [activeTab, setActiveTab] = useState(Tabs.Query); const styles = getStyles(config.theme); - const observable = useMemo(() => queryRunner.getData({ withFieldConfig: true, withTransforms: true }), []); + const observable = useMemo(() => queryRunner.getData({ withFieldConfig: true, withTransforms: true }), [queryRunner]); const data = useObservable(observable); const instances = getInstances(); diff --git a/public/app/features/alerting/components/NotificationChannelForm.tsx b/public/app/features/alerting/components/NotificationChannelForm.tsx index 8f7f079b917..bb13130168a 100644 --- a/public/app/features/alerting/components/NotificationChannelForm.tsx +++ b/public/app/features/alerting/components/NotificationChannelForm.tsx @@ -38,22 +38,21 @@ export const NotificationChannelForm: FC = ({ }) => { const styles = getStyles(useTheme()); - /* - Finds fields that have dependencies on other fields and removes duplicates. - Needs to be prefixed with settings. - */ - const fieldsToWatch = - new Set( - selectedChannel?.options - .filter((o) => o.showWhen.field) - .map((option) => { - return `settings.${option.showWhen.field}`; - }) - ) || []; - useEffect(() => { + /* + Finds fields that have dependencies on other fields and removes duplicates. + Needs to be prefixed with settings. + */ + const fieldsToWatch = + new Set( + selectedChannel?.options + .filter((o) => o.showWhen.field) + .map((option) => { + return `settings.${option.showWhen.field}`; + }) + ) || []; watch(['type', 'sendReminder', 'uploadImage', ...fieldsToWatch]); - }, [fieldsToWatch]); + }, [selectedChannel?.options, watch]); const currentFormValues = getValues(); diff --git a/public/app/features/dashboard/components/DashboardSettings/AutoRefreshIntervals.tsx b/public/app/features/dashboard/components/DashboardSettings/AutoRefreshIntervals.tsx index 352754219bb..7c1b7c706de 100644 --- a/public/app/features/dashboard/components/DashboardSettings/AutoRefreshIntervals.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/AutoRefreshIntervals.tsx @@ -22,7 +22,7 @@ export const AutoRefreshIntervals: FC = ({ useEffect(() => { const intervals = getIntervalsFunc(refreshIntervals ?? defaultIntervals); setIntervals(intervals); - }, [refreshIntervals]); + }, [getIntervalsFunc, refreshIntervals]); const intervalsString = useMemo(() => { if (!Array.isArray(intervals)) { @@ -52,7 +52,7 @@ export const AutoRefreshIntervals: FC = ({ setInvalidIntervalsMessage(invalidMessage); }, - [intervals, onRefreshIntervalChange, setInvalidIntervalsMessage] + [getIntervalsFunc, intervals, onRefreshIntervalChange, validateIntervalsFunc] ); return ( diff --git a/public/app/features/dashboard/components/Inspector/PanelInspector.tsx b/public/app/features/dashboard/components/Inspector/PanelInspector.tsx index ac5bc231ffd..849a85b60f8 100644 --- a/public/app/features/dashboard/components/Inspector/PanelInspector.tsx +++ b/public/app/features/dashboard/components/Inspector/PanelInspector.tsx @@ -3,7 +3,6 @@ import { connect, MapStateToProps } from 'react-redux'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { PanelPlugin } from '@grafana/data'; import { locationService } from '@grafana/runtime'; - import { StoreState } from 'app/types'; import { GetDataOptions } from '../../../query/state/PanelQueryRunner'; import { usePanelLatestData } from '../PanelEditor/usePanelLatestData'; @@ -24,10 +23,6 @@ export interface ConnectedProps { export type Props = OwnProps & ConnectedProps; const PanelInspectorUnconnected: React.FC = ({ panel, dashboard, plugin }) => { - if (!plugin) { - return null; - } - const [dataOptions, setDataOptions] = useState({ withTransforms: false, withFieldConfig: true, @@ -36,7 +31,7 @@ const PanelInspectorUnconnected: React.FC = ({ panel, dashboard, plugin } const location = useLocation(); const { data, isLoading, error } = usePanelLatestData(panel, dataOptions); const metaDs = useDatasourceMetadata(data); - const tabs = useInspectTabs(plugin, dashboard, error, metaDs); + const tabs = useInspectTabs(dashboard, plugin, error, metaDs); const defaultTab = new URLSearchParams(location.search).get('inspectTab') as InspectTab; const onClose = () => { @@ -46,6 +41,10 @@ const PanelInspectorUnconnected: React.FC = ({ panel, dashboard, plugin } }); }; + if (!plugin) { + return null; + } + return ( { * Configures tabs for PanelInspector */ export const useInspectTabs = ( - plugin: PanelPlugin, dashboard: DashboardModel, + plugin: PanelPlugin | undefined | null, error?: DataQueryError, metaDs?: DataSourceApi ) => { diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx index b9d63c7a1a6..638ade8b1d3 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx @@ -31,7 +31,7 @@ export const OptionsPaneCategory: FC = React.memo( if (!isExpanded && forceOpen && forceOpen > 0) { setIsExpanded(true); } - }, [forceOpen]); + }, [forceOpen, isExpanded]); const onToggle = useCallback(() => { setSavedState({ isExpanded: !isExpanded }); diff --git a/public/app/features/dashboard/components/PanelEditor/PanelNotSupported.test.tsx b/public/app/features/dashboard/components/PanelEditor/PanelNotSupported.test.tsx index 272dc6cf8d5..deae9f5e640 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelNotSupported.test.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelNotSupported.test.tsx @@ -1,18 +1,25 @@ -import React from 'react'; +import { locationService } from '@grafana/runtime'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { Provider } from 'react-redux'; +import createMockStore from 'redux-mock-store'; import { PanelNotSupported, Props } from './PanelNotSupported'; import { PanelEditorTabId } from './types'; -import { locationService } from '@grafana/runtime'; const setupTestContext = (options: Partial) => { const defaults: Props = { message: '', dispatch: jest.fn(), }; + const store = createMockStore(); const props = { ...defaults, ...options }; - render(); + render( + + + + ); return { props }; }; diff --git a/public/app/features/dashboard/components/PanelEditor/PanelNotSupported.tsx b/public/app/features/dashboard/components/PanelEditor/PanelNotSupported.tsx index d643ad3b9ac..d4c25bc8709 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelNotSupported.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelNotSupported.tsx @@ -13,10 +13,11 @@ export interface Props { } export const PanelNotSupported: FC = ({ message, dispatch: propsDispatch }) => { - const dispatch = propsDispatch ? propsDispatch : useDispatch(); + let dispatch = useDispatch(); + dispatch = propsDispatch ?? dispatch; const onBackToQueries = useCallback(() => { locationService.partial({ tab: PanelEditorTabId.Query }); - }, [dispatch]); + }, []); return ( diff --git a/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx b/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx index b63b5f6cef7..3de476c8130 100644 --- a/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx +++ b/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx @@ -25,13 +25,16 @@ export const VisualizationSelectPane: FC = ({ panel }) => { const styles = useStyles(getStyles); const searchRef = useRef(null); - const onPluginTypeChange = (meta: PanelPluginMeta) => { - if (meta.id === plugin.meta.id) { - dispatch(toggleVizPicker(false)); - } else { - dispatch(changePanelPlugin(panel, meta.id)); - } - }; + const onPluginTypeChange = useCallback( + (meta: PanelPluginMeta) => { + if (meta.id === plugin.meta.id) { + dispatch(toggleVizPicker(false)); + } else { + dispatch(changePanelPlugin(panel, meta.id)); + } + }, + [dispatch, panel, plugin.meta.id] + ); // Give Search input focus when using radio button switch list mode useEffect(() => { @@ -55,7 +58,7 @@ export const VisualizationSelectPane: FC = ({ panel }) => { } } }, - [onPluginTypeChange] + [onPluginTypeChange, plugin.meta] ); const suffix = diff --git a/public/app/features/dashboard/components/PanelEditor/usePanelLatestData.ts b/public/app/features/dashboard/components/PanelEditor/usePanelLatestData.ts index c54249950ab..c7808c5cb57 100644 --- a/public/app/features/dashboard/components/PanelEditor/usePanelLatestData.ts +++ b/public/app/features/dashboard/components/PanelEditor/usePanelLatestData.ts @@ -35,6 +35,7 @@ export const usePanelLatestData = (panel: PanelModel, options: GetDataOptions): * Adding separate options to dependencies array to avoid additional hook for comparing previous options with current. * Otherwise, passing different references to the same object may cause troubles. */ + // eslint-disable-next-line react-hooks/exhaustive-deps }, [panel, options.withFieldConfig, options.withTransforms]); return { diff --git a/public/app/features/dashboard/components/PanelEditor/utils.ts b/public/app/features/dashboard/components/PanelEditor/utils.ts index 0c5ceeacfd1..f0ab59bf0d2 100644 --- a/public/app/features/dashboard/components/PanelEditor/utils.ts +++ b/public/app/features/dashboard/components/PanelEditor/utils.ts @@ -27,7 +27,7 @@ export function calculatePanelSize(mode: DisplayMode, width: number, height: num }; } -export function supportsDataQuery(plugin: PanelPlugin | undefined): boolean { +export function supportsDataQuery(plugin: PanelPlugin | undefined | null): boolean { return plugin?.meta.skipDataQuery === false; } diff --git a/public/app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect.tsx b/public/app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect.tsx index 4c2c687656b..72c306bffed 100644 --- a/public/app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect.tsx +++ b/public/app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect.tsx @@ -32,7 +32,7 @@ export const RepeatRowSelect: FC = ({ repeat, onChange }) => { }); return options; - }, variables); + }, [variables]); const onSelectChange = useCallback((option: SelectableValue) => onChange(option.value), [onChange]); diff --git a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx index bc909e593eb..36cd04a4d76 100644 --- a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx @@ -28,7 +28,7 @@ export const SaveDashboardErrorProxy: React.FC = ( if (error.data && isHandledError(error.data.status)) { error.isHandled = true; } - }, []); + }, [error]); return ( <> diff --git a/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx b/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx index 57c6ae0f368..78693a9d2d1 100644 --- a/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx @@ -20,7 +20,7 @@ export const SaveProvisionedDashboardForm: React.FC = ({ type: 'application/json;charset=utf-8', }); saveAs(blob, dashboard.title + '-' + new Date().getTime() + '.json'); - }, [dashboardJSON]); + }, [dashboard.title, dashboardJSON]); const onCopyToClipboardSuccess = useCallback(() => { appEvents.emit(AppEvents.alertSuccess, ['Dashboard JSON copied to clipboard']); diff --git a/public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx b/public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx index 8fd9f597b3d..e0faa2f22fe 100644 --- a/public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx @@ -39,7 +39,7 @@ export const useDashboardSave = (dashboard: DashboardModel) => { locationService.replace(newUrl); } } - }, [state]); + }, [dashboard, state]); return { state, onDashboardSave }; }; diff --git a/public/app/features/dashboard/components/SubMenu/DashboardLinks.tsx b/public/app/features/dashboard/components/SubMenu/DashboardLinks.tsx index bdc03e6c6a6..a71ed5d5404 100644 --- a/public/app/features/dashboard/components/SubMenu/DashboardLinks.tsx +++ b/public/app/features/dashboard/components/SubMenu/DashboardLinks.tsx @@ -17,10 +17,6 @@ export interface Props { } export const DashboardLinks: FC = ({ dashboard, links }) => { - if (!links.length) { - return null; - } - // Emulate forceUpdate (https://reactjs.org/docs/hooks-faq.html#is-there-something-like-forceupdate) const [, forceUpdate] = useReducer((x) => x + 1, 0); @@ -39,6 +35,10 @@ export const DashboardLinks: FC = ({ dashboard, links }) => { }; }); + if (!links.length) { + return null; + } + return ( <> {links.map((link: DashboardLink, index: number) => { diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx index 2b9f1340596..059cb1d1c90 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx @@ -61,10 +61,11 @@ export const TransformationEditor = ({ [ uiConfig.editor, uiConfig.transformation.defaultOptions, - config.transformation.id, config.transformation.options, + config.transformation.id, input, onChange, + index, ] ); diff --git a/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx b/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx index e2f769be0da..25f4eb3aacb 100644 --- a/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx +++ b/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx @@ -28,7 +28,6 @@ export const useDashboardRestore = (version: number) => { }); appEvents.emit(AppEvents.alertSuccess, ['Dashboard restored', 'Restored from version ' + version]); } - }, [state]); - + }, [state, version]); return { state, onRestoreDashboard }; }; diff --git a/public/app/features/dashboard/components/VizTypePicker/VizTypePicker.tsx b/public/app/features/dashboard/components/VizTypePicker/VizTypePicker.tsx index 8ddacbfd08b..b0fc2cdd41d 100644 --- a/public/app/features/dashboard/components/VizTypePicker/VizTypePicker.tsx +++ b/public/app/features/dashboard/components/VizTypePicker/VizTypePicker.tsx @@ -62,7 +62,7 @@ export const VizTypePicker: React.FC = ({ searchQuery, onTypeChange, curr const getFilteredPluginList = useCallback((): PanelPluginMeta[] => { return filterPluginList(pluginsList, searchQuery, current); - }, [searchQuery]); + }, [current, pluginsList, searchQuery]); const renderVizPlugin = (plugin: PanelPluginMeta, index: number) => { const isCurrent = plugin.id === current.id; diff --git a/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx b/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx index 0b589567da8..f4bd86230c2 100644 --- a/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx @@ -1,6 +1,6 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect } from 'react'; import { css } from 'emotion'; -import { uniqBy, debounce } from 'lodash'; +import { uniqBy } from 'lodash'; // Types import { RichHistoryQuery, ExploreId } from 'app/types/explore'; @@ -21,6 +21,7 @@ import { import RichHistoryCard from './RichHistoryCard'; import { sortOrderOptions } from './RichHistory'; import { FilterInput } from 'app/core/components/FilterInput/FilterInput'; +import { useDebounce } from 'react-use'; export interface Props { queries: RichHistoryQuery[]; @@ -139,6 +140,7 @@ export function RichHistoryQueriesTab(props: Props) { const [timeFilter, setTimeFilter] = useState<[number, number]>([0, retentionPeriod]); const [filteredQueries, setFilteredQueries] = useState([]); const [searchInput, setSearchInput] = useState(''); + const [debouncedSearchInput, setDebouncedSearchInput] = useState(''); const theme = useTheme(); const styles = getStyles(theme, height); @@ -146,19 +148,12 @@ export function RichHistoryQueriesTab(props: Props) { const datasourcesRetrievedFromQueryHistory = uniqBy(queries, 'datasourceName').map((d) => d.datasourceName); const listOfDatasources = createDatasourcesList(datasourcesRetrievedFromQueryHistory); - const filterAndSortQueriesDebounced = useCallback( - debounce((searchValue: string) => { - setFilteredQueries( - filterAndSortQueries( - queries, - sortOrder, - datasourceFilters?.map((d) => d.value) as string[] | null, - searchValue, - timeFilter - ) - ); - }, 300), - [timeFilter, queries, sortOrder, datasourceFilters] + useDebounce( + () => { + setDebouncedSearchInput(searchInput); + }, + 300, + [searchInput] ); useEffect(() => { @@ -167,11 +162,11 @@ export function RichHistoryQueriesTab(props: Props) { queries, sortOrder, datasourceFilters?.map((d) => d.value) as string[] | null, - searchInput, + debouncedSearchInput, timeFilter ) ); - }, [timeFilter, queries, sortOrder, datasourceFilters]); + }, [timeFilter, queries, sortOrder, datasourceFilters, debouncedSearchInput]); /* mappedQueriesToHeadings is an object where query headings (stringified dates/data sources) * are keys and arrays with queries that belong to that headings are values. @@ -219,7 +214,6 @@ export function RichHistoryQueriesTab(props: Props) { value={searchInput} onChange={(value: string) => { setSearchInput(value); - filterAndSortQueriesDebounced(value); }} />
diff --git a/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx b/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx index fa0d57eb15a..e90ee4c9c34 100644 --- a/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx @@ -1,6 +1,6 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect } from 'react'; import { css } from 'emotion'; -import { uniqBy, debounce } from 'lodash'; +import { uniqBy } from 'lodash'; // Types import { RichHistoryQuery, ExploreId } from 'app/types/explore'; @@ -14,6 +14,7 @@ import { filterAndSortQueries, createDatasourcesList, SortOrder } from 'app/core import RichHistoryCard from './RichHistoryCard'; import { sortOrderOptions } from './RichHistory'; import { FilterInput } from 'app/core/components/FilterInput/FilterInput'; +import { useDebounce } from 'react-use'; export interface Props { queries: RichHistoryQuery[]; @@ -82,38 +83,33 @@ export function RichHistoryStarredTab(props: Props) { const [filteredQueries, setFilteredQueries] = useState([]); const [searchInput, setSearchInput] = useState(''); + const [debouncedSearchInput, setDebouncedSearchInput] = useState(''); const theme = useTheme(); const styles = getStyles(theme); const datasourcesRetrievedFromQueryHistory = uniqBy(queries, 'datasourceName').map((d) => d.datasourceName); const listOfDatasources = createDatasourcesList(datasourcesRetrievedFromQueryHistory); - const starredQueries = queries.filter((q) => q.starred === true); - const filterAndSortQueriesDebounced = useCallback( - debounce((searchValue: string) => { - setFilteredQueries( - filterAndSortQueries( - starredQueries, - sortOrder, - datasourceFilters?.map((d) => d.value) as string[] | null, - searchValue - ) - ); - }, 300), - [queries, sortOrder, datasourceFilters] + useDebounce( + () => { + setDebouncedSearchInput(searchInput); + }, + 300, + [searchInput] ); useEffect(() => { + const starredQueries = queries.filter((q) => q.starred === true); setFilteredQueries( filterAndSortQueries( starredQueries, sortOrder, datasourceFilters?.map((d) => d.value) as string[] | null, - searchInput + debouncedSearchInput ) ); - }, [queries, sortOrder, datasourceFilters]); + }, [queries, sortOrder, datasourceFilters, debouncedSearchInput]); return (
@@ -136,7 +132,6 @@ export function RichHistoryStarredTab(props: Props) { value={searchInput} onChange={(value: string) => { setSearchInput(value); - filterAndSortQueriesDebounced(value); }} />
diff --git a/public/app/features/explore/TraceView/TraceView.tsx b/public/app/features/explore/TraceView/TraceView.tsx index 0696ab327ec..563c7d7efa4 100644 --- a/public/app/features/explore/TraceView/TraceView.tsx +++ b/public/app/features/explore/TraceView/TraceView.tsx @@ -1,34 +1,35 @@ -import React, { useCallback, useMemo, useState } from 'react'; +import { DataFrame, DataFrameView, TraceSpanRow } from '@grafana/data'; +import { colors, useTheme } from '@grafana/ui'; import { ThemeOptions, ThemeProvider, ThemeType, Trace, - TraceKeyValuePair, - TraceLink, TracePageHeader, TraceProcess, TraceResponse, - TraceSpan, TraceTimelineViewer, transformTraceData, TTraceTimeline, UIElementsContext, } from '@jaegertracing/jaeger-ui-components'; +import { TraceToLogsData } from 'app/core/components/TraceToLogsSettings'; +import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { StoreState } from 'app/types'; +import { ExploreId, SplitOpen } from 'app/types/explore'; +import React, { useCallback, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { createSpanLinkFactory } from './createSpanLink'; import { UIElements } from './uiElements'; -import { useViewRange } from './useViewRange'; -import { useSearch } from './useSearch'; import { useChildrenState } from './useChildrenState'; import { useDetailState } from './useDetailState'; import { useHoverIndentGuide } from './useHoverIndentGuide'; -import { colors, useTheme } from '@grafana/ui'; -import { DataFrame, DataFrameView, TraceSpanRow } from '@grafana/data'; -import { createSpanLinkFactory } from './createSpanLink'; -import { useSelector } from 'react-redux'; -import { StoreState } from 'app/types'; -import { ExploreId, SplitOpen } from 'app/types/explore'; -import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; -import { TraceToLogsData } from 'app/core/components/TraceToLogsSettings'; +import { useSearch } from './useSearch'; +import { useViewRange } from './useViewRange'; + +function noop(): {} { + return {}; +} type Props = { dataFrames: DataFrame[]; @@ -37,9 +38,6 @@ type Props = { }; export function TraceView(props: Props) { - if (!props.dataFrames.length) { - return null; - } const { expandOne, collapseOne, childrenToggle, collapseAll, childrenHiddenIDs, expandAll } = useChildrenState(); const { detailStates, @@ -102,8 +100,9 @@ export function TraceView(props: Props) { traceToLogsOptions, ]); const scrollElement = document.getElementsByClassName('scrollbar-view')[0]; + const onSlimViewClicked = useCallback(() => setSlim(!slim), [slim]); - if (!traceProp) { + if (!props.dataFrames?.length || !traceProp) { return null; } @@ -112,14 +111,14 @@ export function TraceView(props: Props) { {}, [])} - focusUiFindMatches={useCallback(() => {}, [])} + clearSearch={noop} + focusUiFindMatches={noop} hideMap={false} hideSummary={false} - nextResult={useCallback(() => {}, [])} - onSlimViewClicked={useCallback(() => setSlim(!slim), [])} - onTraceGraphViewClicked={useCallback(() => {}, [])} - prevResult={useCallback(() => {}, [])} + nextResult={noop} + onSlimViewClicked={onSlimViewClicked} + onTraceGraphViewClicked={noop} + prevResult={noop} resultCount={0} slimView={slim} textFilter={null} @@ -133,23 +132,23 @@ export function TraceView(props: Props) { hideSearchButtons={true} /> {}, [])} - scrollToFirstVisibleSpan={useCallback(() => {}, [])} + registerAccessors={noop} + scrollToFirstVisibleSpan={noop} findMatchesIDs={spanFindMatches} trace={traceProp} traceTimeline={traceTimeline} updateNextViewRangeTime={updateNextViewRangeTime} updateViewRangeTime={updateViewRangeTime} viewRange={viewRange} - focusSpan={useCallback(() => {}, [])} - createLinkToExternalSpan={useCallback(() => '', [])} + focusSpan={noop} + createLinkToExternalSpan={noop as any} setSpanNameColumnWidth={setSpanNameColumnWidth} collapseAll={collapseAll} collapseOne={collapseOne} expandAll={expandAll} expandOne={expandOne} childrenToggle={childrenToggle} - clearShouldScrollToFirstUiFindMatch={useCallback(() => {}, [])} + clearShouldScrollToFirstUiFindMatch={noop} detailLogItemToggle={detailLogItemToggle} detailLogsToggle={detailLogsToggle} detailWarningsToggle={detailWarningsToggle} @@ -158,13 +157,10 @@ export function TraceView(props: Props) { detailProcessToggle={detailProcessToggle} detailTagsToggle={detailTagsToggle} detailToggle={toggleDetail} - setTrace={useCallback((trace: Trace | null, uiFind: string | null) => {}, [])} + setTrace={noop} addHoverIndentGuideId={addHoverIndentGuideId} removeHoverIndentGuideId={removeHoverIndentGuideId} - linksGetter={useCallback( - (span: TraceSpan, items: TraceKeyValuePair[], itemIndex: number) => [] as TraceLink[], - [] - )} + linksGetter={noop as any} uiFind={search} createSpanLink={createSpanLink} scrollElement={scrollElement} @@ -177,6 +173,9 @@ export function TraceView(props: Props) { function transformDataFrames(frames: DataFrame[]): Trace | null { // At this point we only show single trace. const frame = frames[0]; + if (!frame) { + return null; + } let data: TraceResponse = frame.fields.length === 1 ? // For backward compatibility when we sent whole json response in a single field/value diff --git a/public/app/features/explore/TraceView/useDetailState.ts b/public/app/features/explore/TraceView/useDetailState.ts index 2c6822b8f70..2d60a8ca78d 100644 --- a/public/app/features/explore/TraceView/useDetailState.ts +++ b/public/app/features/explore/TraceView/useDetailState.ts @@ -40,20 +40,30 @@ export function useDetailState() { detailStates, toggleDetail, detailLogItemToggle, - detailLogsToggle: useCallback(makeDetailSubsectionToggle('logs', detailStates, setDetailStates), [detailStates]), - detailWarningsToggle: useCallback(makeDetailSubsectionToggle('warnings', detailStates, setDetailStates), [ - detailStates, - ]), - detailStackTracesToggle: useCallback(makeDetailSubsectionToggle('stackTraces', detailStates, setDetailStates), [ - detailStates, - ]), - detailReferencesToggle: useCallback(makeDetailSubsectionToggle('references', detailStates, setDetailStates), [ - detailStates, - ]), - detailProcessToggle: useCallback(makeDetailSubsectionToggle('process', detailStates, setDetailStates), [ - detailStates, - ]), - detailTagsToggle: useCallback(makeDetailSubsectionToggle('tags', detailStates, setDetailStates), [detailStates]), + detailLogsToggle: useCallback( + (spanID: string) => makeDetailSubsectionToggle('logs', detailStates, setDetailStates)(spanID), + [detailStates] + ), + detailWarningsToggle: useCallback( + (spanID: string) => makeDetailSubsectionToggle('warnings', detailStates, setDetailStates)(spanID), + [detailStates] + ), + detailStackTracesToggle: useCallback( + (spanID: string) => makeDetailSubsectionToggle('stackTraces', detailStates, setDetailStates)(spanID), + [detailStates] + ), + detailReferencesToggle: useCallback( + (spanID: string) => makeDetailSubsectionToggle('references', detailStates, setDetailStates)(spanID), + [detailStates] + ), + detailProcessToggle: useCallback( + (spanID: string) => makeDetailSubsectionToggle('process', detailStates, setDetailStates)(spanID), + [detailStates] + ), + detailTagsToggle: useCallback( + (spanID: string) => makeDetailSubsectionToggle('tags', detailStates, setDetailStates)(spanID), + [detailStates] + ), }; } diff --git a/public/app/features/library-panels/components/DeleteLibraryPanelModal/DeleteLibraryPanelModal.tsx b/public/app/features/library-panels/components/DeleteLibraryPanelModal/DeleteLibraryPanelModal.tsx index 50b6caf9445..1e46b5679a1 100644 --- a/public/app/features/library-panels/components/DeleteLibraryPanelModal/DeleteLibraryPanelModal.tsx +++ b/public/app/features/library-panels/components/DeleteLibraryPanelModal/DeleteLibraryPanelModal.tsx @@ -23,7 +23,7 @@ export const DeleteLibraryPanelModal: FC = ({ libraryPanel, onDismiss, on const asyncDispatch = useMemo(() => asyncDispatcher(dispatch), [dispatch]); useEffect(() => { asyncDispatch(getConnectedDashboards(libraryPanel)); - }, []); + }, [asyncDispatch, libraryPanel]); const connected = Boolean(dashboardTitles.length); const done = loadingState === LoadingState.Done; diff --git a/public/app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal.tsx b/public/app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal.tsx index 3a6d3a5f133..3f5d9452692 100644 --- a/public/app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal.tsx +++ b/public/app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal.tsx @@ -60,7 +60,7 @@ export const SaveLibraryPanelModal: React.FC = ({ const discardAndClose = useCallback(() => { onDiscard(); onDismiss(); - }, []); + }, [onDiscard, onDismiss]); return ( diff --git a/public/app/features/library-panels/utils/usePanelSave.ts b/public/app/features/library-panels/utils/usePanelSave.ts index 8af7b01d88b..ad77fa94e20 100644 --- a/public/app/features/library-panels/utils/usePanelSave.ts +++ b/public/app/features/library-panels/utils/usePanelSave.ts @@ -23,7 +23,7 @@ export const usePanelSave = () => { if (state.value) { dispatch(notifyApp(createPanelLibrarySuccessNotification('Library panel saved'))); } - }, [state]); + }, [dispatch, state]); return { state, saveLibraryPanel }; }; diff --git a/public/app/features/manage-dashboards/components/ImportDashboardForm.tsx b/public/app/features/manage-dashboards/components/ImportDashboardForm.tsx index 5de324d6123..a3c54f6db04 100644 --- a/public/app/features/manage-dashboards/components/ImportDashboardForm.tsx +++ b/public/app/features/manage-dashboards/components/ImportDashboardForm.tsx @@ -49,7 +49,7 @@ export const ImportDashboardForm: FC = ({ if (isSubmitted && (errors.title || errors.uid)) { onSubmit(getValues({ nest: true }), {} as any); } - }, [errors]); + }, [errors, getValues, isSubmitted, onSubmit]); return ( <> diff --git a/public/app/features/panel/PanelRenderer.tsx b/public/app/features/panel/PanelRenderer.tsx index c6b32a0a6bc..8fc187cc0bd 100644 --- a/public/app/features/panel/PanelRenderer.tsx +++ b/public/app/features/panel/PanelRenderer.tsx @@ -111,5 +111,5 @@ const useFieldOverrides = ( timeZone, }), }; - }, [fieldConfigRegistry, timeZone, fieldConfig, series]); + }, [fieldConfigRegistry, fieldConfig, data, series, timeZone]); }; diff --git a/public/app/features/playlist/usePlaylist.tsx b/public/app/features/playlist/usePlaylist.tsx index 6bce65aadc2..b3e7e872889 100644 --- a/public/app/features/playlist/usePlaylist.tsx +++ b/public/app/features/playlist/usePlaylist.tsx @@ -17,7 +17,7 @@ export function usePlaylist(playlistId?: number) { setLoading(false); }; initPlaylist(); - }, []); + }, [playlistId]); return { playlist, loading }; } diff --git a/public/app/features/playlist/usePlaylistItems.tsx b/public/app/features/playlist/usePlaylistItems.tsx index f4848a93024..7407f88389e 100644 --- a/public/app/features/playlist/usePlaylistItems.tsx +++ b/public/app/features/playlist/usePlaylistItems.tsx @@ -61,14 +61,14 @@ export function usePlaylistItems(playlistItems?: PlaylistItem[]) { (item: PlaylistItem) => { movePlaylistItem(item, -1); }, - [items] + [movePlaylistItem] ); const moveDown = useCallback( (item: PlaylistItem) => { movePlaylistItem(item, 1); }, - [items] + [movePlaylistItem] ); const deleteItem = useCallback( diff --git a/public/app/features/sandbox/TestStuffPage.tsx b/public/app/features/sandbox/TestStuffPage.tsx index 0d5cc1d7916..a0bbaa99244 100644 --- a/public/app/features/sandbox/TestStuffPage.tsx +++ b/public/app/features/sandbox/TestStuffPage.tsx @@ -37,7 +37,7 @@ export const TestStuffPage: FC = () => { /** * Subscribe to data */ - const observable = useMemo(() => queryRunner.getData({ withFieldConfig: true, withTransforms: true }), []); + const observable = useMemo(() => queryRunner.getData({ withFieldConfig: true, withTransforms: true }), [queryRunner]); const data = useObservable(observable); return ( diff --git a/public/app/features/search/components/SearchItem.tsx b/public/app/features/search/components/SearchItem.tsx index e3658fb3438..a9c75d14388 100644 --- a/public/app/features/search/components/SearchItem.tsx +++ b/public/app/features/search/components/SearchItem.tsx @@ -27,9 +27,12 @@ const getIconFromMeta = (meta = ''): IconName => { export const SearchItem: FC = ({ item, editable, onToggleChecked, onTagSelected }) => { const styles = useStyles(getStyles); - const tagSelected = useCallback((tag: string, event: React.MouseEvent) => { - onTagSelected(tag); - }, []); + const tagSelected = useCallback( + (tag: string, event: React.MouseEvent) => { + onTagSelected(tag); + }, + [onTagSelected] + ); const toggleItem = useCallback( (event: React.MouseEvent) => { @@ -38,7 +41,7 @@ export const SearchItem: FC = ({ item, editable, onToggleChecked, onTagSe onToggleChecked(item); } }, - [item] + [item, onToggleChecked] ); const folderTitle = item.folderTitle || 'General'; diff --git a/public/app/features/search/components/SectionHeader.tsx b/public/app/features/search/components/SectionHeader.tsx index b033cd447f5..5a115c68927 100644 --- a/public/app/features/search/components/SectionHeader.tsx +++ b/public/app/features/search/components/SectionHeader.tsx @@ -37,7 +37,7 @@ export const SectionHeader: FC = ({ onToggleChecked(section); } }, - [section] + [onToggleChecked, section] ); return ( diff --git a/public/app/features/search/hooks/useSearch.ts b/public/app/features/search/hooks/useSearch.ts index de6c8c866cb..df62b9a6bd3 100644 --- a/public/app/features/search/hooks/useSearch.ts +++ b/public/app/features/search/hooks/useSearch.ts @@ -31,7 +31,7 @@ export const useSearch: UseSearch = (query, reducer, params = {}) => { // Set loading state before debounced search useEffect(() => { dispatch({ type: SEARCH_START }); - }, [query.tag, query.sort, query.starred, query.layout]); + }, [query.tag, query.sort, query.starred, query.layout, dispatch]); useDebounce(search, 300, [query, queryParsing]); diff --git a/public/app/features/variables/adhoc/picker/AdHocFilterBuilder.tsx b/public/app/features/variables/adhoc/picker/AdHocFilterBuilder.tsx index 22fb121f4cb..a3a311e2d3e 100644 --- a/public/app/features/variables/adhoc/picker/AdHocFilterBuilder.tsx +++ b/public/app/features/variables/adhoc/picker/AdHocFilterBuilder.tsx @@ -40,7 +40,7 @@ export const AdHocFilterBuilder: FC = ({ datasource, appendBefore, onComp setKey(null); setOperator('='); }, - [onCompleted, key, setKey, setOperator] + [onCompleted, operator, key] ); if (key === null) { diff --git a/public/app/features/variables/editor/LegacyVariableQueryEditor.tsx b/public/app/features/variables/editor/LegacyVariableQueryEditor.tsx index ea31655608e..4f68ce807ab 100644 --- a/public/app/features/variables/editor/LegacyVariableQueryEditor.tsx +++ b/public/app/features/variables/editor/LegacyVariableQueryEditor.tsx @@ -12,12 +12,9 @@ export const LEGACY_VARIABLE_QUERY_EDITOR_NAME = 'Grafana-LegacyVariableQueryEdi export const LegacyVariableQueryEditor: FC = ({ onChange, query }) => { const styles = useStyles(getStyles); const [value, setValue] = useState(query); - const onValueChange = useCallback( - (event: React.FormEvent) => { - setValue(event.currentTarget.value); - }, - [onChange] - ); + const onValueChange = (event: React.FormEvent) => { + setValue(event.currentTarget.value); + }; const onBlur = useCallback( (event: React.FormEvent) => { diff --git a/public/app/features/variables/editor/SelectionOptionsEditor.tsx b/public/app/features/variables/editor/SelectionOptionsEditor.tsx index dc3952ebff3..050bbd2afe0 100644 --- a/public/app/features/variables/editor/SelectionOptionsEditor.tsx +++ b/public/app/features/variables/editor/SelectionOptionsEditor.tsx @@ -14,33 +14,38 @@ export interface SelectionOptionsEditorProps void; } -export const SelectionOptionsEditor: FunctionComponent = (props) => { +export const SelectionOptionsEditor: FunctionComponent = ({ + onMultiChanged: onMultiChangedProps, + onPropChange, + variable, +}) => { const onMultiChanged = useCallback( (event: ChangeEvent) => { - props.onMultiChanged(toVariableIdentifier(props.variable), event.target.checked); + onMultiChangedProps(toVariableIdentifier(variable), event.target.checked); }, - [props.onMultiChanged, props.variable] + [onMultiChangedProps, variable] ); const onIncludeAllChanged = useCallback( (event: ChangeEvent) => { - props.onPropChange({ propName: 'includeAll', propValue: event.target.checked }); + onPropChange({ propName: 'includeAll', propValue: event.target.checked }); }, - [props.onPropChange] + [onPropChange] ); const onAllValueChanged = useCallback( (event: FormEvent) => { - props.onPropChange({ propName: 'allValue', propValue: event.currentTarget.value }); + onPropChange({ propName: 'allValue', propValue: event.currentTarget.value }); }, - [props.onPropChange] + [onPropChange] ); + return ( - {props.variable.includeAll && ( + {variable.includeAll && ( ) { - const options = useMemo(() => getVariableTypes(), [getVariableTypes]); + const options = useMemo(() => getVariableTypes(), []); const value = useMemo(() => options.find((o) => o.value === type) ?? options[0], [options, type]); return ( diff --git a/public/app/features/variables/inspect/NetworkGraph.tsx b/public/app/features/variables/inspect/NetworkGraph.tsx index ef67aa56120..f2601de1a9d 100644 --- a/public/app/features/variables/inspect/NetworkGraph.tsx +++ b/public/app/features/variables/inspect/NetworkGraph.tsx @@ -19,7 +19,7 @@ interface DispatchProps {} export type Props = OwnProps & ConnectedProps & DispatchProps; export const NetworkGraph: FC = ({ nodes, edges, direction, width, height, onDoubleClick }) => { - let network: any = null; + const network = useRef(null); const ref = useRef(null); const onNodeDoubleClick = useCallback( @@ -55,16 +55,16 @@ export const NetworkGraph: FC = ({ nodes, edges, direction, width, height }, }; - network = new vis.Network(ref.current, data, options); - network.on('doubleClick', onNodeDoubleClick); + network.current = new vis.Network(ref.current, data, options); + network.current?.on('doubleClick', onNodeDoubleClick); return () => { // unsubscribe event handlers - if (network) { - network.off('doubleClick'); + if (network.current) { + network.current.off('doubleClick'); } }; - }, []); + }, [direction, edges, nodes, onNodeDoubleClick]); return (
diff --git a/public/app/features/variables/textbox/TextBoxVariablePicker.tsx b/public/app/features/variables/textbox/TextBoxVariablePicker.tsx index efb3b907268..33fc06f52d4 100644 --- a/public/app/features/variables/textbox/TextBoxVariablePicker.tsx +++ b/public/app/features/variables/textbox/TextBoxVariablePicker.tsx @@ -37,7 +37,7 @@ export function TextBoxVariablePicker({ variable, onVariableChange }: Props): Re } variableAdapters.get(variable.type).updateOptions(variable); - }, [dispatch, variable, updatedValue]); + }, [variable, updatedValue, dispatch, onVariableChange]); const onChange = useCallback((event: ChangeEvent) => setUpdatedValue(event.target.value), [ setUpdatedValue, diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Aggregations.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Aggregations.tsx index 0a94d9b8651..f0ca64042a0 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Aggregations.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Aggregations.tsx @@ -57,19 +57,19 @@ export const Aggregations: FC = (props) => { }; const useAggregationOptionsByMetric = ({ metricDescriptor }: Props): Array> => { + const valueType = metricDescriptor?.valueType; + const metricKind = metricDescriptor?.metricKind; + return useMemo(() => { - if (!metricDescriptor) { + if (!valueType || !metricKind) { return []; } - return getAggregationOptionsByMetric( - metricDescriptor.valueType as ValueTypes, - metricDescriptor.metricKind as MetricKind - ).map((a) => ({ + return getAggregationOptionsByMetric(valueType as ValueTypes, metricKind as MetricKind).map((a) => ({ ...a, label: a.text, })); - }, [metricDescriptor?.metricKind, metricDescriptor?.valueType]); + }, [valueType, metricKind]); }; const useSelectedFromOptions = (aggOptions: Array>, props: Props) => { diff --git a/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.tsx index 0447c4d46a0..a0c35559957 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.tsx @@ -51,14 +51,15 @@ function Editor({ variableOptionGroup, }: React.PropsWithChildren) { const [state, setState] = useState(defaultState); + const { projectName, metricType, groupBys, editorMode } = query; useEffect(() => { - if (query && query.projectName && query.metricType) { + if (projectName && metricType) { datasource - .getLabels(query.metricType, refId, query.projectName, query.groupBys) - .then((labels) => setState({ ...state, labels })); + .getLabels(metricType, refId, projectName, groupBys) + .then((labels) => setState((prevState) => ({ ...prevState, labels }))); } - }, [query.projectName, query.groupBys, query.metricType]); + }, [datasource, groupBys, metricType, projectName, refId]); const onChange = (metricQuery: MetricQuery) => { onQueryChange({ ...query, ...metricQuery }); @@ -81,14 +82,14 @@ function Editor({ <> { onChange({ ...query, projectName }); }} /> - {query.editorMode === EditorMode.Visual && ( + {editorMode === EditorMode.Visual && ( )} - {query.editorMode === EditorMode.MQL && ( + {editorMode === EditorMode.MQL && ( onQueryChange({ ...query, query: q })} onRunQuery={onRunQuery} diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Metrics.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Metrics.tsx index f376e2be775..7c539fec121 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Metrics.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Metrics.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import _ from 'lodash'; import { TemplateSrv } from '@grafana/runtime'; @@ -37,48 +37,54 @@ export function Metrics(props: Props) { projectName: null, }); - const { services, service, metrics } = state; - const { metricType, templateVariableOptions, projectName } = props; + const { services, service, metrics, metricDescriptors } = state; + const { metricType, templateVariableOptions, projectName, templateSrv, datasource, onChange, children } = props; - const loadMetricDescriptors = async () => { - if (projectName) { - const metricDescriptors = await props.datasource.getMetricTypes(props.projectName); - const services = getServicesList(metricDescriptors); - const metrics = getMetricsList(metricDescriptors); - const service = metrics.length > 0 ? metrics[0].service : ''; - const metricDescriptor = getSelectedMetricDescriptor(metricDescriptors, props.metricType); - setState({ ...state, metricDescriptors, services, metrics, service: service, metricDescriptor }); - } - }; + const getSelectedMetricDescriptor = useCallback( + (metricDescriptors: MetricDescriptor[], metricType: string) => { + return metricDescriptors.find((md) => md.type === templateSrv.replace(metricType))!; + }, + [templateSrv] + ); useEffect(() => { + const getMetricsList = (metricDescriptors: MetricDescriptor[]) => { + const selectedMetricDescriptor = getSelectedMetricDescriptor(metricDescriptors, metricType); + if (!selectedMetricDescriptor) { + return []; + } + const metricsByService = metricDescriptors + .filter((m) => m.service === selectedMetricDescriptor.service) + .map((m) => ({ + service: m.service, + value: m.type, + label: m.displayName, + description: m.description, + })); + return metricsByService; + }; + + const loadMetricDescriptors = async () => { + if (projectName) { + const metricDescriptors = await datasource.getMetricTypes(projectName); + const services = getServicesList(metricDescriptors); + const metrics = getMetricsList(metricDescriptors); + const service = metrics.length > 0 ? metrics[0].service : ''; + const metricDescriptor = getSelectedMetricDescriptor(metricDescriptors, metricType); + setState((prevState) => ({ + ...prevState, + metricDescriptors, + services, + metrics, + service: service, + metricDescriptor, + })); + } + }; loadMetricDescriptors(); - }, [projectName]); - - const getSelectedMetricDescriptor = (metricDescriptors: MetricDescriptor[], metricType: string) => { - return metricDescriptors.find((md) => md.type === props.templateSrv.replace(metricType))!; - }; - - const getMetricsList = (metricDescriptors: MetricDescriptor[]) => { - const selectedMetricDescriptor = getSelectedMetricDescriptor(metricDescriptors, props.metricType); - if (!selectedMetricDescriptor) { - return []; - } - const metricsByService = metricDescriptors - .filter((m) => m.service === selectedMetricDescriptor.service) - .map((m) => ({ - service: m.service, - value: m.type, - label: m.displayName, - description: m.description, - })); - return metricsByService; - }; + }, [datasource, getSelectedMetricDescriptor, metricType, projectName]); const onServiceChange = ({ value: service }: any) => { - const { metricDescriptors } = state; - const { metricType, templateSrv } = props; - const metrics = metricDescriptors .filter((m: MetricDescriptor) => m.service === templateSrv.replace(service)) .map((m: MetricDescriptor) => ({ @@ -98,7 +104,7 @@ export function Metrics(props: Props) { const onMetricTypeChange = ({ value }: SelectableValue, extra: any = {}) => { const metricDescriptor = getSelectedMetricDescriptor(state.metricDescriptors, value!); setState({ ...state, metricDescriptor, ...extra }); - props.onChange({ ...metricDescriptor, type: value! }); + onChange({ ...metricDescriptor, type: value! }); }; const getServicesList = (metricDescriptors: MetricDescriptor[]) => { @@ -150,7 +156,7 @@ export function Metrics(props: Props) {
- {props.children(state.metricDescriptor)} + {children(state.metricDescriptor)} ); } diff --git a/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.tsx index 8695896fc05..061050dbc38 100644 --- a/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.tsx @@ -15,6 +15,7 @@ export type Props = DataSourcePluginOptionsEditorProps = (props: Props) => { const [datasource, setDatasource] = useState(); + const { options } = props; const addWarning = (message: string) => { store.dispatch(notifyApp(createWarningNotification('CloudWatch Authentication', message))); @@ -22,23 +23,19 @@ export const ConfigEditor: FC = (props: Props) => { useEffect(() => { getDatasourceSrv() - .loadDatasource(props.options.name) + .loadDatasource(options.name) .then((datasource: CloudWatchDatasource) => setDatasource(datasource)); - if (props.options.jsonData.authType === 'arn') { + if (options.jsonData.authType === 'arn') { addWarning('Since grafana 7.3 authentication type "arn" is deprecated, falling back to default SDK provider'); - } else if ( - props.options.jsonData.authType === 'credentials' && - !props.options.jsonData.profile && - !props.options.jsonData.database - ) { + } else if (options.jsonData.authType === 'credentials' && !options.jsonData.profile && !options.jsonData.database) { addWarning( 'As of grafana 7.3 authentication type "credentials" should be used only for shared file credentials. \ If you don\'t have a credentials file, switch to the default SDK provider for extracting credentials \ from environment variables or IAM roles' ); } - }, []); + }, [options.jsonData.authType, options.jsonData.database, options.jsonData.profile, options.name]); return ( <> @@ -53,7 +50,7 @@ export const ConfigEditor: FC = (props: Props) => { diff --git a/public/app/plugins/datasource/cloudwatch/components/Dimensions.tsx b/public/app/plugins/datasource/cloudwatch/components/Dimensions.tsx index 6380b81741e..8354f7f9206 100644 --- a/public/app/plugins/datasource/cloudwatch/components/Dimensions.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/Dimensions.tsx @@ -28,7 +28,7 @@ export const Dimensions: FunctionComponent = ({ dimensions, loadValues, l if (!isEqual(completeDimensions, dimensions)) { onChange(completeDimensions); } - }, [data]); + }, [data, dimensions, onChange]); const excludeUsedKeys = (options: SelectableStrings) => { return options.filter(({ value }) => !Object.keys(data).includes(value!)); diff --git a/public/app/plugins/datasource/cloudwatch/components/MetricsQueryFieldsEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/MetricsQueryFieldsEditor.tsx index 15d701d8987..af08c98493b 100644 --- a/public/app/plugins/datasource/cloudwatch/components/MetricsQueryFieldsEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/MetricsQueryFieldsEditor.tsx @@ -44,15 +44,15 @@ export function MetricsQueryFieldsEditor({ Promise.all([datasource.metricFindQuery('regions()'), datasource.metricFindQuery('namespaces()')]).then( ([regions, namespaces]) => { - setState({ - ...state, + setState((prevState) => ({ + ...prevState, regions: [...regions, variableOptionGroup], namespaces: [...namespaces, variableOptionGroup], variableOptionGroup, - }); + })); } ); - }, []); + }, [datasource]); const loadMetricNames = async () => { const { namespace, region } = query; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/index.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/index.tsx index c48b2697f31..2d4eed59af8 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/index.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/index.tsx @@ -28,7 +28,7 @@ export const FiltersSettingsEditor: FunctionComponent = ({ value }) => { if (!value.settings?.filters?.length) { dispatch(addFilter()); } - }, []); + }, [dispatch, value.settings?.filters?.length]); return ( <> diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/index.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/index.tsx index 59aa2f1739c..5c12f51f1a2 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/index.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/index.tsx @@ -36,7 +36,7 @@ export const BucketScriptSettingsEditor: FunctionComponent = ({ value, pr if (!value.pipelineVariables?.length) { dispatch(addPipelineVariable()); } - }, []); + }, [dispatch, value.pipelineVariables?.length]); return ( <> diff --git a/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.tsx b/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.tsx index def2f5da0b1..634f2567463 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.tsx @@ -26,6 +26,8 @@ export const ConfigEditor = (props: Props) => { logLevelField: options.jsonData.logLevelField || '', }, }); + // We can't enforce the eslint rule here because we only want to run this once. + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/AggregationField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/AggregationField.tsx index 5639ce2b18c..990d29de0fb 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/AggregationField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/AggregationField.tsx @@ -30,7 +30,7 @@ const AggregationField: React.FC = ({ }, }); }, - [query] + [onQueryChange, query] ); const options = useMemo(() => [...aggregationOptions, variableOptionGroup], [ diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/DimensionFields.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/DimensionFields.tsx index 19216c3ca15..f36f772908c 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/DimensionFields.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/DimensionFields.tsx @@ -10,15 +10,18 @@ interface DimensionFieldsProps extends AzureQueryEditorFieldProps { } const DimensionFields: React.FC = ({ query, dimensionOptions, onQueryChange }) => { - const setDimensionFilters = (newFilters: AzureMetricDimension[]) => { - onQueryChange({ - ...query, - azureMonitor: { - ...query.azureMonitor, - dimensionFilters: newFilters, - }, - }); - }; + const setDimensionFilters = useCallback( + (newFilters: AzureMetricDimension[]) => { + onQueryChange({ + ...query, + azureMonitor: { + ...query.azureMonitor, + dimensionFilters: newFilters, + }, + }); + }, + [onQueryChange, query] + ); const addFilter = useCallback(() => { setDimensionFilters([ @@ -29,7 +32,7 @@ const DimensionFields: React.FC = ({ query, dimensionOptio filter: '', }, ]); - }, [query.azureMonitor.dimensionFilters]); + }, [query.azureMonitor.dimensionFilters, setDimensionFilters]); const removeFilter = (index: number) => { const newFilters = [...query.azureMonitor.dimensionFilters]; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/LegendFormatField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/LegendFormatField.tsx index 44e0bf641e7..7c7925760c0 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/LegendFormatField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/LegendFormatField.tsx @@ -23,7 +23,7 @@ const LegendFormatField: React.FC = ({ onQueryChange alias: value, }, }); - }, [query, value]); + }, [onQueryChange, query, value]); return ( diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNameField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNameField.tsx index 32b74b38ec8..4abf99b5087 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNameField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNameField.tsx @@ -31,13 +31,7 @@ const MetricName: React.FC = ({ setMetricNames(results.map(toOption)); }) .catch((err) => setError(ERROR_SOURCE, err)); - }, [ - subscriptionId, - query.azureMonitor.resourceGroup, - query.azureMonitor.metricDefinition, - query.azureMonitor.resourceName, - query.azureMonitor.metricNamespace, - ]); + }, [datasource, metricNames.length, query.azureMonitor, setError, subscriptionId]); const handleChange = useCallback( (change: SelectableValue) => { @@ -53,7 +47,7 @@ const MetricName: React.FC = ({ }, }); }, - [query] + [onQueryChange, query] ); const options = useMemo(() => [...metricNames, variableOptionGroup], [metricNames, variableOptionGroup]); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNamespaceField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNamespaceField.tsx index 7748a0fe322..1ed50f9e57e 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNamespaceField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNamespaceField.tsx @@ -40,12 +40,7 @@ const MetricNamespaceField: React.FC = ({ setMetricNamespaces(results.map(toOption)); }) .catch((err) => setError(ERROR_SOURCE, err)); - }, [ - subscriptionId, - query.azureMonitor.resourceGroup, - query.azureMonitor.metricDefinition, - query.azureMonitor.resourceName, - ]); + }, [datasource, metricNamespaces.length, onQueryChange, query, setError, subscriptionId]); const handleChange = useCallback( (change: SelectableValue) => { @@ -64,7 +59,7 @@ const MetricNamespaceField: React.FC = ({ }, }); }, - [query] + [onQueryChange, query] ); const options = useMemo(() => [...metricNamespaces, variableOptionGroup], [metricNamespaces, variableOptionGroup]); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx index 867e74b6340..c33b8354411 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx @@ -79,7 +79,7 @@ describe('Azure Monitor QueryEditor', () => { const mockDatasource = createMockDatasource(); const onChange = jest.fn(); const mockQuery = createMockQuery(); - mockDatasource.getMetricNames = jest.fn().mockResolvedValueOnce([ + mockDatasource.getMetricNames = jest.fn().mockResolvedValue([ { value: 'metric-a', text: 'Metric A', diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/NamespaceField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/NamespaceField.tsx index e79674c271c..ade068bd7e8 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/NamespaceField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/NamespaceField.tsx @@ -29,7 +29,7 @@ const NamespaceField: React.FC = ({ .getMetricDefinitions(subscriptionId, resourceGroup) .then((results) => setNamespaces(results.map(toOption))) .catch((err) => setError(ERROR_SOURCE, err)); - }, [subscriptionId, query.azureMonitor.resourceGroup]); + }, [datasource, namespaces.length, query.azureMonitor, setError, subscriptionId]); const handleChange = useCallback( (change: SelectableValue) => { @@ -51,7 +51,7 @@ const NamespaceField: React.FC = ({ }, }); }, - [query] + [onQueryChange, query] ); const options = useMemo(() => [...namespaces, variableOptionGroup], [namespaces, variableOptionGroup]); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceGroupsField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceGroupsField.tsx index 51a0130970d..05326119084 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceGroupsField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceGroupsField.tsx @@ -30,7 +30,7 @@ const ResourceGroupsField: React.FC = ({ setError(ERROR_SOURCE, undefined); }) .catch((err) => setError(ERROR_SOURCE, err)); - }, [subscriptionId]); + }, [datasource, resourceGroups.length, setError, subscriptionId]); const handleChange = useCallback( (change: SelectableValue) => { @@ -53,7 +53,7 @@ const ResourceGroupsField: React.FC = ({ }, }); }, - [query] + [onQueryChange, query] ); const options = useMemo(() => [...resourceGroups, variableOptionGroup], [resourceGroups, variableOptionGroup]); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceNameField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceNameField.tsx index ac176194b90..5f8ed3b6fb4 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceNameField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceNameField.tsx @@ -29,7 +29,7 @@ const ResourceNameField: React.FC = ({ .getResourceNames(subscriptionId, resourceGroup, metricDefinition) .then((results) => setResourceNames(results.map(toOption))) .catch((err) => setError(ERROR_SOURCE, err)); - }, [subscriptionId, query.azureMonitor.resourceGroup, query.azureMonitor.metricDefinition]); + }, [datasource, query.azureMonitor, resourceNames.length, setError, subscriptionId]); const handleChange = useCallback( (change: SelectableValue) => { @@ -51,7 +51,7 @@ const ResourceNameField: React.FC = ({ }, }); }, - [query] + [onQueryChange, query] ); const options = useMemo(() => [...resourceNames, variableOptionGroup], [resourceNames, variableOptionGroup]); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/TimeGrainField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/TimeGrainField.tsx index 894f9d0f9bd..213028a7965 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/TimeGrainField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/TimeGrainField.tsx @@ -31,7 +31,7 @@ const TimeGrainField: React.FC = ({ }, }); }, - [query] + [onQueryChange, query] ); const timeGrains = useMemo(() => { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/TopField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/TopField.tsx index 5b6c568711c..1d1a7b72f8a 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/TopField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/TopField.tsx @@ -23,7 +23,7 @@ const TopField: React.FC = ({ onQueryChange, query } top: value, }, }); - }, [query, value]); + }, [onQueryChange, query, value]); return ( diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/QueryTypeField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/QueryTypeField.tsx index 7c24270f343..e32435d25c2 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/QueryTypeField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/QueryTypeField.tsx @@ -26,7 +26,7 @@ const QueryTypeField: React.FC = ({ query, onQueryChange }) queryType: change.value, }); }, - [query] + [onQueryChange, query] ); return ( diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/SubscriptionField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/SubscriptionField.tsx index 4433e77b852..99e05c50084 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/SubscriptionField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/SubscriptionField.tsx @@ -54,7 +54,14 @@ const SubscriptionField: React.FC = ({ }); }) .catch((err) => setError(ERROR_SOURCE, err)); - }, []); + }, [ + datasource.azureLogAnalyticsDatasource?.logAnalyticsSubscriptionId, + datasource.azureLogAnalyticsDatasource?.subscriptionId, + datasource.azureMonitorDatasource, + onQueryChange, + query, + setError, + ]); const handleChange = useCallback( (change: SelectableValue) => { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/metrics.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/metrics.ts index 4984648ca64..d476b279f45 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/metrics.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/metrics.ts @@ -79,6 +79,9 @@ export function useMetricsMetadata( query.azureMonitor.resourceName, query.azureMonitor.metricNamespace, query.azureMonitor.metricName, + query, + datasource, + onQueryChange, ]); return metricMetadata; diff --git a/public/app/plugins/datasource/influxdb/components/useShadowedState.ts b/public/app/plugins/datasource/influxdb/components/useShadowedState.ts index bfe2f9e51fc..35490bc58a0 100644 --- a/public/app/plugins/datasource/influxdb/components/useShadowedState.ts +++ b/public/app/plugins/datasource/influxdb/components/useShadowedState.ts @@ -12,7 +12,7 @@ export function useShadowedState(outsideVal: T): [T, (newVal: T) => void] { if (isOutsideValChanged && currentVal !== outsideVal) { setCurrentVal(outsideVal); } - }, [outsideVal, currentVal]); + }, [outsideVal, currentVal, prevOutsideVal]); return [currentVal, setCurrentVal]; } diff --git a/public/app/plugins/datasource/prometheus/components/PromExploreQueryEditor.tsx b/public/app/plugins/datasource/prometheus/components/PromExploreQueryEditor.tsx index d3aa7e0005b..3287560173c 100644 --- a/public/app/plugins/datasource/prometheus/components/PromExploreQueryEditor.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromExploreQueryEditor.tsx @@ -18,7 +18,7 @@ export const PromExploreQueryEditor: FC = (props: Props) => { if (query.exemplar === undefined) { onChange({ ...query, exemplar: true }); } - }, [query]); + }, [onChange, query]); function onChangeQueryStep(value: string) { const { query, onChange } = props; diff --git a/public/app/plugins/datasource/prometheus/components/PromLink.tsx b/public/app/plugins/datasource/prometheus/components/PromLink.tsx index b88c2072909..ec77ba609e6 100644 --- a/public/app/plugins/datasource/prometheus/components/PromLink.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromLink.tsx @@ -51,7 +51,7 @@ const PromLink: FC = ({ panelData, query, datasource }) => { setHref(getExternalLink()); } - }, [panelData]); + }, [datasource, panelData, query]); return ( diff --git a/public/app/plugins/panel/annolist/AnnotationListItemTags.tsx b/public/app/plugins/panel/annolist/AnnotationListItemTags.tsx index d6c7bef336c..9d1c1f1a966 100644 --- a/public/app/plugins/panel/annolist/AnnotationListItemTags.tsx +++ b/public/app/plugins/panel/annolist/AnnotationListItemTags.tsx @@ -19,7 +19,7 @@ export const AnnotationListItemTags: FC = ({ tags, remove, onClick }) => e.stopPropagation(); onClick(tag, remove); }, - [remove] + [onClick, remove] ); if (!tags || !tags.length) { diff --git a/public/app/plugins/panel/dashlist/DashList.tsx b/public/app/plugins/panel/dashlist/DashList.tsx index 351d9c3caea..193d616f5b1 100644 --- a/public/app/plugins/panel/dashlist/DashList.tsx +++ b/public/app/plugins/panel/dashlist/DashList.tsx @@ -82,17 +82,7 @@ export function DashList(props: PanelProps) { fetchDashboards(props.options, props.replaceVariables).then((dashes) => { setDashboards(dashes); }); - }, [ - props.options.showSearch, - props.options.showStarred, - props.options.showRecentlyViewed, - props.options.maxItems, - props.options.query, - props.options.tags, - props.options.folderId, - props.replaceVariables, - props.renderCounter, - ]); + }, [props.options, props.replaceVariables, props.renderCounter]); const toggleDashboardStar = async (e: React.SyntheticEvent, dash: Dashboard) => { e.preventDefault(); diff --git a/public/app/plugins/panel/nodeGraph/NodeGraphPanel.tsx b/public/app/plugins/panel/nodeGraph/NodeGraphPanel.tsx index e6775ca2793..9ffefbdd820 100644 --- a/public/app/plugins/panel/nodeGraph/NodeGraphPanel.tsx +++ b/public/app/plugins/panel/nodeGraph/NodeGraphPanel.tsx @@ -5,6 +5,7 @@ import { NodeGraph } from '@grafana/ui'; import { useLinks } from '../../../features/explore/utils/links'; export const NodeGraphPanel: React.FunctionComponent> = ({ width, height, data }) => { + const getLinks = useLinks(data.timeRange); if (!data || !data.series.length) { return (
@@ -13,8 +14,6 @@ export const NodeGraphPanel: React.FunctionComponent> = ({ w ); } - const getLinks = useLinks(data.timeRange); - return (
diff --git a/public/app/plugins/panel/timeseries/FillBelowToEditor.tsx b/public/app/plugins/panel/timeseries/FillBelowToEditor.tsx index 3ed0982cbcb..feb7372fabc 100644 --- a/public/app/plugins/panel/timeseries/FillBelowToEditor.tsx +++ b/public/app/plugins/panel/timeseries/FillBelowToEditor.tsx @@ -33,7 +33,7 @@ export const FillBellowToEditor: React.FC> }; } return undefined; - }, names); + }, [names, value]); return (