[Logs + Metrics UI] Remove eslint exceptions (#50979)

This removes the two eslint exceptions specific to the `infra` plugin introduced in #49244.

fixes #49563
This commit is contained in:
Felix Stürmer 2019-12-11 13:48:40 +01:00 committed by GitHub
parent 9fcc93457f
commit 0cd62cabbb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 269 additions and 231 deletions

View file

@ -170,13 +170,6 @@ module.exports = {
'react-hooks/rules-of-hooks': 'off',
},
},
{
files: ['x-pack/legacy/plugins/infra/**/*.{js,ts,tsx}'],
rules: {
'react-hooks/exhaustive-deps': 'off',
'react-hooks/rules-of-hooks': 'off',
},
},
{
files: ['x-pack/legacy/plugins/lens/**/*.{js,ts,tsx}'],
rules: {

View file

@ -37,7 +37,6 @@ export const useFormattedTime = (
const dateFormat = formatMap[format];
const formattedTime = useMemo(() => getFormattedTime(time, dateFormat, fallbackFormat), [
getFormattedTime,
time,
dateFormat,
fallbackFormat,

View file

@ -51,7 +51,7 @@ export const LogEntryActionsMenu: React.FunctionComponent<{
/>
</EuiContextMenuItem>,
],
[uptimeLink]
[apmLink, uptimeLink]
);
const hasMenuItems = useMemo(() => menuItems.length > 0, [menuItems]);

View file

@ -16,7 +16,7 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { debounce } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import euiStyled from '../../../../../common/eui_styled_components';
import { useVisibilityState } from '../../utils/use_visibility_state';
@ -47,8 +47,25 @@ export const LogHighlightsMenu: React.FC<LogHighlightsMenuProps> = ({
} = useVisibilityState(false);
// Input field state
const [highlightTerm, setHighlightTerm] = useState('');
const [highlightTerm, _setHighlightTerm] = useState('');
const debouncedOnChange = useMemo(() => debounce(onChange, 275), [onChange]);
const setHighlightTerm = useCallback<typeof _setHighlightTerm>(
valueOrUpdater =>
_setHighlightTerm(previousHighlightTerm => {
const newHighlightTerm =
typeof valueOrUpdater === 'function'
? valueOrUpdater(previousHighlightTerm)
: valueOrUpdater;
if (newHighlightTerm !== previousHighlightTerm) {
debouncedOnChange([newHighlightTerm]);
}
return newHighlightTerm;
}),
[debouncedOnChange]
);
const changeHighlightTerm = useCallback(
e => {
const value = e.target.value;
@ -57,9 +74,6 @@ export const LogHighlightsMenu: React.FC<LogHighlightsMenuProps> = ({
[setHighlightTerm]
);
const clearHighlightTerm = useCallback(() => setHighlightTerm(''), [setHighlightTerm]);
useEffect(() => {
debouncedOnChange([highlightTerm]);
}, [highlightTerm]);
const button = (
<EuiButtonEmpty color="text" size="xs" iconType="brush" onClick={togglePopover}>

View file

@ -63,7 +63,7 @@ export const useMeasuredCharacterDimensions = (scale: TextScale) => {
X
</MonospaceCharacterDimensionsProbe>
),
[scale]
[measureElement, scale]
);
return {

View file

@ -7,7 +7,7 @@
import { EuiComboBox } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useCallback, useState, useEffect } from 'react';
import React, { useCallback, useState } from 'react';
import { FieldType } from 'ui/index_patterns';
import { colorTransformer, MetricsExplorerColor } from '../../../common/color_palette';
import {
@ -31,24 +31,19 @@ interface SelectedOption {
export const MetricsExplorerMetrics = ({ options, onChange, fields, autoFocus = false }: Props) => {
const colors = Object.keys(MetricsExplorerColor) as MetricsExplorerColor[];
const [inputRef, setInputRef] = useState<HTMLInputElement | null>(null);
const [focusOnce, setFocusState] = useState<boolean>(false);
const [shouldFocus, setShouldFocus] = useState(autoFocus);
useEffect(() => {
if (inputRef && autoFocus && !focusOnce) {
inputRef.focus();
setFocusState(true);
}
}, [inputRef]);
// the EuiCombobox forwards the ref to an input element
const autoFocusInputElement = useCallback(
(inputElement: HTMLInputElement | null) => {
if (inputElement && shouldFocus) {
inputElement.focus();
setShouldFocus(false);
}
},
[shouldFocus]
);
// I tried to use useRef originally but the EUIComboBox component's type definition
// would only accept an actual input element or a callback function (with the same type).
// This effectivly does the same thing but is compatible with EuiComboBox.
const handleInputRef = (ref: HTMLInputElement) => {
if (ref) {
setInputRef(ref);
}
};
const handleChange = useCallback(
selectedOptions => {
onChange(
@ -59,7 +54,7 @@ export const MetricsExplorerMetrics = ({ options, onChange, fields, autoFocus =
}))
);
},
[options, onChange]
[onChange, options.aggregation, colors]
);
const comboOptions = fields
@ -86,7 +81,7 @@ export const MetricsExplorerMetrics = ({ options, onChange, fields, autoFocus =
selectedOptions={selectedOptions}
onChange={handleChange}
isClearable={true}
inputRef={handleInputRef}
inputRef={autoFocusInputElement}
/>
);
};

View file

@ -36,7 +36,7 @@ export const SavedViewCreateModal = ({ close, save, isInvalid }: Props) => {
const saveView = useCallback(() => {
save(viewName, includeTime);
}, [viewName, includeTime]);
}, [includeTime, save, viewName]);
return (
<EuiOverlayMask>

View file

@ -94,7 +94,7 @@ export const AddLogColumnButtonAndPopover: React.FunctionComponent<{
addLogColumn(selectedOption.columnConfiguration);
},
[addLogColumn, availableColumnOptions]
[addLogColumn, availableColumnOptions, closePopover]
);
return (

View file

@ -52,7 +52,7 @@ export const useSourceConfigurationFormState = (configuration?: SourceConfigurat
const resetForm = useCallback(() => {
indicesConfigurationFormState.resetForm();
logColumnsConfigurationFormState.resetForm();
}, [indicesConfigurationFormState.resetForm, logColumnsConfigurationFormState.formState]);
}, [indicesConfigurationFormState, logColumnsConfigurationFormState]);
const isFormDirty = useMemo(
() => indicesConfigurationFormState.isFormDirty || logColumnsConfigurationFormState.isFormDirty,

View file

@ -17,28 +17,33 @@ import {
} from '../../graphql/types';
import { findInventoryModel } from '../../../common/inventory_models';
interface Props {
interface WaffleInventorySwitcherProps {
nodeType: InfraNodeType;
changeNodeType: (nodeType: InfraNodeType) => void;
changeGroupBy: (groupBy: InfraSnapshotGroupbyInput[]) => void;
changeMetric: (metric: InfraSnapshotMetricInput) => void;
}
export const WaffleInventorySwitcher = (props: Props) => {
export const WaffleInventorySwitcher: React.FC<WaffleInventorySwitcherProps> = ({
changeNodeType,
changeGroupBy,
changeMetric,
nodeType,
}) => {
const [isOpen, setIsOpen] = useState(false);
const closePopover = useCallback(() => setIsOpen(false), []);
const openPopover = useCallback(() => setIsOpen(true), []);
const goToNodeType = useCallback(
(nodeType: InfraNodeType) => {
(targetNodeType: InfraNodeType) => {
closePopover();
props.changeNodeType(nodeType);
props.changeGroupBy([]);
const inventoryModel = findInventoryModel(nodeType);
props.changeMetric({
changeNodeType(targetNodeType);
changeGroupBy([]);
const inventoryModel = findInventoryModel(targetNodeType);
changeMetric({
type: inventoryModel.metrics.defaultSnapshot as InfraSnapshotMetricType,
});
},
[props.changeGroupBy, props.changeNodeType, props.changeMetric]
[closePopover, changeNodeType, changeGroupBy, changeMetric]
);
const goToHost = useCallback(() => goToNodeType('host' as InfraNodeType), [goToNodeType]);
const goToK8 = useCallback(() => goToNodeType('pod' as InfraNodeType), [goToNodeType]);
@ -68,10 +73,10 @@ export const WaffleInventorySwitcher = (props: Props) => {
],
},
],
[]
[goToDocker, goToHost, goToK8]
);
const selectedText = useMemo(() => {
switch (props.nodeType) {
switch (nodeType) {
case InfraNodeType.host:
return i18n.translate('xpack.infra.waffle.nodeTypeSwitcher.hostsLabel', {
defaultMessage: 'Hosts',
@ -81,7 +86,7 @@ export const WaffleInventorySwitcher = (props: Props) => {
case InfraNodeType.container:
return 'Docker';
}
}, [props.nodeType]);
}, [nodeType]);
return (
<EuiFilterGroup>

View file

@ -46,7 +46,7 @@ export const useLogAnalysisCapabilities = () => {
useEffect(() => {
fetchMlCapabilities();
}, []);
}, [fetchMlCapabilities]);
const isLoading = useMemo(() => fetchMlCapabilitiesRequest.state === 'pending', [
fetchMlCapabilitiesRequest.state,

View file

@ -125,23 +125,23 @@ export const useLogAnalysisModule = <JobType extends string>({
dispatchModuleStatus({ type: 'failedSetup' });
});
},
[cleanUpModule, setUpModule]
[cleanUpModule, dispatchModuleStatus, setUpModule]
);
const viewSetupForReconfiguration = useCallback(() => {
dispatchModuleStatus({ type: 'requestedJobConfigurationUpdate' });
}, []);
}, [dispatchModuleStatus]);
const viewSetupForUpdate = useCallback(() => {
dispatchModuleStatus({ type: 'requestedJobDefinitionUpdate' });
}, []);
}, [dispatchModuleStatus]);
const viewResults = useCallback(() => {
dispatchModuleStatus({ type: 'viewedResults' });
}, []);
}, [dispatchModuleStatus]);
const jobIds = useMemo(() => moduleDescriptor.getJobIds(spaceId, sourceId), [
moduleDescriptor.getJobIds,
moduleDescriptor,
spaceId,
sourceId,
]);

View file

@ -140,7 +140,7 @@ export const useAnalysisSetupState = <JobType extends string>({
? [...errors, ...index.errors]
: errors;
}, []);
}, [selectedIndexNames, validatedIndices, validateIndicesRequest.state]);
}, [isValidating, validateIndicesRequest.state, selectedIndexNames, validatedIndices]);
return {
cleanupAndSetup,

View file

@ -78,7 +78,7 @@ export const useLogEntryHighlights = (
} else {
setLogEntryHighlights([]);
}
}, [highlightTerms, startKey, endKey, filterQuery, sourceVersion]);
}, [endKey, filterQuery, highlightTerms, loadLogEntryHighlights, sourceVersion, startKey]);
const logEntryHighlightsById = useMemo(
() =>

View file

@ -74,7 +74,15 @@ export const useLogSummaryHighlights = (
} else {
setLogSummaryHighlights([]);
}
}, [highlightTerms, start, end, bucketSize, filterQuery, sourceVersion]);
}, [
bucketSize,
debouncedLoadSummaryHighlights,
end,
filterQuery,
highlightTerms,
sourceVersion,
start,
]);
return {
logSummaryHighlights,

View file

@ -53,7 +53,7 @@ export const useNextAndPrevious = ({
const initialTimeKey = getUniqueLogEntryKey(entries[initialIndex]);
setCurrentTimeKey(initialTimeKey);
}
}, [currentTimeKey, entries, setCurrentTimeKey]);
}, [currentTimeKey, entries, setCurrentTimeKey, visibleMidpoint]);
const indexOfCurrentTimeKey = useMemo(() => {
if (currentTimeKey && entries.length > 0) {

View file

@ -25,11 +25,11 @@ export const LogHighlightsPositionBridge = withLogPosition(
const { setJumpToTarget, setVisibleMidpoint } = useContext(LogHighlightsState.Context);
useEffect(() => {
setVisibleMidpoint(visibleMidpoint);
}, [visibleMidpoint]);
}, [setVisibleMidpoint, visibleMidpoint]);
useEffect(() => {
setJumpToTarget(() => jumpToTargetPosition);
}, [jumpToTargetPosition]);
}, [jumpToTargetPosition, setJumpToTarget]);
return null;
}
@ -41,7 +41,7 @@ export const LogHighlightsFilterQueryBridge = withLogFilter(
useEffect(() => {
setFilterQuery(serializedFilterQuery);
}, [serializedFilterQuery]);
}, [serializedFilterQuery, setFilterQuery]);
return null;
}

View file

@ -35,7 +35,7 @@ export const WithStreamItems: React.FunctionComponent<{
createLogEntryStreamItem(logEntry, logEntryHighlightsById[logEntry.gid] || [])
),
[logEntries.entries, logEntryHighlightsById]
[isAutoReloading, logEntries.entries, logEntries.isReloading, logEntryHighlightsById]
);
return children({

View file

@ -96,6 +96,9 @@ export function useMetricsExplorerData(
}
setLoading(false);
})();
// TODO: fix this dependency list while preserving the semantics
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [options, source, timerange, signal, afterKey]);
return { error, loading, data };
}

View file

@ -102,7 +102,7 @@ function useStateWithLocalStorage<State>(
const [state, setState] = useState<State>(parseJsonOrDefault<State>(storageState, defaultState));
useEffect(() => {
localStorage.setItem(key, JSON.stringify(state));
}, [state]);
}, [key, state]);
return [state, setState];
}

View file

@ -26,29 +26,32 @@ export const useSavedView = <ViewState>(defaultViewState: ViewState, viewType: s
>(viewType);
const { create, error: errorOnCreate, createdId } = useCreateSavedObject(viewType);
const { deleteObject, deletedId } = useDeleteSavedObject(viewType);
const deleteView = useCallback((id: string) => deleteObject(id), []);
const deleteView = useCallback((id: string) => deleteObject(id), [deleteObject]);
const [createError, setCreateError] = useState<string | null>(null);
useEffect(() => setCreateError(createError), [errorOnCreate, setCreateError]);
useEffect(() => setCreateError(errorOnCreate), [errorOnCreate]);
const saveView = useCallback((d: { [p: string]: any }) => {
const doSave = async () => {
const exists = await hasView(d.name);
if (exists) {
setCreateError(
i18n.translate('xpack.infra.savedView.errorOnCreate.duplicateViewName', {
defaultMessage: `A view with that name already exists.`,
})
);
return;
}
create(d);
};
setCreateError(null);
doSave();
}, []);
const saveView = useCallback(
(d: { [p: string]: any }) => {
const doSave = async () => {
const exists = await hasView(d.name);
if (exists) {
setCreateError(
i18n.translate('xpack.infra.savedView.errorOnCreate.duplicateViewName', {
defaultMessage: `A view with that name already exists.`,
})
);
return;
}
create(d);
};
setCreateError(null);
doSave();
},
[create, hasView]
);
const savedObjects = data ? data.savedObjects : [];
const savedObjects = useMemo(() => (data ? data.savedObjects : []), [data]);
const views = useMemo(() => {
const items: Array<SavedView<ViewState>> = [
{
@ -61,19 +64,17 @@ export const useSavedView = <ViewState>(defaultViewState: ViewState, viewType: s
},
];
if (data) {
data.savedObjects.forEach(
o =>
o.type === viewType &&
items.push({
...o.attributes,
id: o.id,
})
);
}
savedObjects.forEach(
o =>
o.type === viewType &&
items.push({
...o.attributes,
id: o.id,
})
);
return items;
}, [savedObjects, defaultViewState]);
}, [defaultViewState, savedObjects, viewType]);
return {
views,

View file

@ -57,6 +57,9 @@ export function useTrackMetric(
const trackUiMetric = getTrackerForApp(app);
const id = setTimeout(() => trackUiMetric(metricType, decoratedMetric), Math.max(delay, 0));
return () => clearTimeout(id);
// the dependencies are managed externally
// eslint-disable-next-line react-hooks/exhaustive-deps
}, effectDependencies);
}

View file

@ -24,6 +24,7 @@ import { MetricsExplorerPage } from './metrics_explorer';
import { SnapshotPage } from './snapshot';
import { SettingsPage } from '../shared/settings';
import { AppNavigation } from '../../components/navigation/app_navigation';
import { SourceLoadingPage } from '../../components/source_loading_page';
interface InfrastructurePageProps extends RouteComponentProps {
uiCapabilities: UICapabilities;
@ -95,11 +96,15 @@ export const InfrastructurePage = injectUICapabilities(
{({ configuration, createDerivedIndexPattern }) => (
<MetricsExplorerOptionsContainer.Provider>
<WithMetricsExplorerOptionsUrlState />
<MetricsExplorerPage
derivedIndexPattern={createDerivedIndexPattern('metrics')}
source={configuration}
{...props}
/>
{configuration ? (
<MetricsExplorerPage
derivedIndexPattern={createDerivedIndexPattern('metrics')}
source={configuration}
{...props}
/>
) : (
<SourceLoadingPage />
)}
</MetricsExplorerOptionsContainer.Provider>
)}
</WithSource>

View file

@ -11,22 +11,17 @@ import { IIndexPattern } from 'src/plugins/data/public';
import { DocumentTitle } from '../../../components/document_title';
import { MetricsExplorerCharts } from '../../../components/metrics_explorer/charts';
import { MetricsExplorerToolbar } from '../../../components/metrics_explorer/toolbar';
import { SourceLoadingPage } from '../../../components/source_loading_page';
import { SourceQuery } from '../../../../common/graphql/types';
import { NoData } from '../../../components/empty_states';
import { useMetricsExplorerState } from './use_metric_explorer_state';
import { useTrackPageview } from '../../../hooks/use_track_metric';
interface MetricsExplorerPageProps {
source: SourceQuery.Query['source']['configuration'] | undefined;
source: SourceQuery.Query['source']['configuration'];
derivedIndexPattern: IIndexPattern;
}
export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExplorerPageProps) => {
if (!source) {
return <SourceLoadingPage />;
}
const {
loading,
error,

View file

@ -59,7 +59,7 @@ export const useMetricsExplorerState = (
setAfterKey(null);
setTimeRange({ ...currentTimerange, from: start, to: end });
},
[currentTimerange]
[currentTimerange, setTimeRange]
);
const handleGroupByChange = useCallback(
@ -70,7 +70,7 @@ export const useMetricsExplorerState = (
groupBy: groupBy || void 0,
});
},
[options]
[options, setOptions]
);
const handleFilterQuerySubmit = useCallback(
@ -81,7 +81,7 @@ export const useMetricsExplorerState = (
filterQuery: query,
});
},
[options]
[options, setOptions]
);
const handleMetricsChange = useCallback(
@ -92,7 +92,7 @@ export const useMetricsExplorerState = (
metrics,
});
},
[options]
[options, setOptions]
);
const handleAggregationChange = useCallback(
@ -109,7 +109,7 @@ export const useMetricsExplorerState = (
}));
setOptions({ ...options, aggregation, metrics });
},
[options]
[options, setOptions]
);
const onViewStateChange = useCallback(
@ -124,7 +124,7 @@ export const useMetricsExplorerState = (
setOptions(vs.options);
}
},
[setChartOptions, setTimeRange, setTimeRange]
[setChartOptions, setOptions, setTimeRange]
);
return {

View file

@ -36,7 +36,7 @@ export const LogEntryRatePageContent = () => {
useEffect(() => {
fetchModuleDefinition();
fetchJobStatus();
}, []);
}, [fetchJobStatus, fetchModuleDefinition]);
if (!hasLogAnalysisCapabilites) {
return <LogEntryRateUnavailableContent />;

View file

@ -124,7 +124,7 @@ export const AnomaliesTable: React.FunctionComponent<{
setItemIdToExpandedRowMap(newItemIdToExpandedRowMap);
}
},
[results, setTimeRange, timeRange, itemIdToExpandedRowMap, setItemIdToExpandedRowMap]
[itemIdToExpandedRowMap, jobId, results, setTimeRange, timeRange]
);
const columns = [

View file

@ -54,7 +54,7 @@ export const AnalysisSetupIndicesForm: React.FunctionComponent<{
</div>
);
}),
[indices]
[handleCheckboxChange, indices]
);
return (

View file

@ -31,7 +31,7 @@ export const useLogEntryRateModule = ({
spaceId,
timestampField,
}),
[indexPattern]
[indexPattern, sourceId, spaceId, timestampField]
);
return useLogAnalysisModule({

View file

@ -8,7 +8,6 @@ import { fold } from 'fp-ts/lib/Either';
import { constant, identity } from 'fp-ts/lib/function';
import { pipe } from 'fp-ts/lib/pipeable';
import * as rt from 'io-ts';
import { useEffect } from 'react';
import { useUrlState } from '../../../utils/use_url_state';
@ -41,12 +40,9 @@ export const useLogAnalysisResultsUrlState = () => {
pipe(urlTimeRangeRT.decode(value), fold(constant(undefined), identity)),
encodeUrlState: urlTimeRangeRT.encode,
urlStateKey: TIME_RANGE_URL_STATE_KEY,
writeDefaultState: true,
});
useEffect(() => {
setTimeRange(timeRange);
}, []);
const [autoRefresh, setAutoRefresh] = useUrlState({
defaultState: {
isPaused: false,
@ -56,12 +52,9 @@ export const useLogAnalysisResultsUrlState = () => {
pipe(autoRefreshRT.decode(value), fold(constant(undefined), identity)),
encodeUrlState: autoRefreshRT.encode,
urlStateKey: AUTOREFRESH_URL_STATE_KEY,
writeDefaultState: true,
});
useEffect(() => {
setAutoRefresh(autoRefresh);
}, []);
return {
timeRange,
setTimeRange,

View file

@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { useCallback } from 'react';
import React, { useCallback, useMemo } from 'react';
import moment from 'moment';
import { i18n } from '@kbn/i18n';
import {
@ -42,15 +42,15 @@ export const ChartSectionVis = ({
seriesOverrides,
type,
}: VisSectionProps) => {
if (!metric || !id) {
return null;
}
const [dateFormat] = useKibanaUiSetting('dateFormat');
const valueFormatter = useCallback(getFormatter(formatter, formatterTemplate), [
formatter,
formatterTemplate,
]);
const dateFormatter = useCallback(niceTimeFormatter(getMaxMinTimestamp(metric)), [metric]);
const dateFormatter = useMemo(
() => (metric != null ? niceTimeFormatter(getMaxMinTimestamp(metric)) : undefined),
[metric]
);
const handleTimeChange = useCallback(
(from: number, to: number) => {
if (onChangeRangeTime) {
@ -73,7 +73,9 @@ export const ChartSectionVis = ({
),
};
if (!metric) {
if (!id) {
return null;
} else if (!metric) {
return (
<ErrorMessage
title={i18n.translate('xpack.infra.chartSection.missingMetricDataText', {
@ -84,9 +86,7 @@ export const ChartSectionVis = ({
})}
/>
);
}
if (metric.series.some(seriesHasLessThen2DataPoints)) {
} else if (metric.series.some(seriesHasLessThen2DataPoints)) {
return (
<ErrorMessage
title={i18n.translate('xpack.infra.chartSection.notEnoughDataPointsToRenderTitle', {

View file

@ -41,7 +41,7 @@ interface Props {
isAutoReloading: boolean;
refreshInterval: number;
sideNav: NavItem[];
metadata: InfraMetadata | null;
metadata: InfraMetadata;
addNavItem(item: NavItem): void;
setRefreshInterval(refreshInterval: number): void;
setAutoReload(isAutoReloading: boolean): void;
@ -49,10 +49,6 @@ interface Props {
setTimeRange(timeRange: MetricsTimeInput): void;
}
export const NodeDetailsPage = (props: Props) => {
if (!props.metadata) {
return null;
}
const { parsedTimeRange } = props;
const { metrics, loading, makeRequest, error } = useNodeDetails(
props.requiredMetrics,
@ -65,11 +61,11 @@ export const NodeDetailsPage = (props: Props) => {
const refetch = useCallback(() => {
makeRequest();
}, []);
}, [makeRequest]);
useEffect(() => {
makeRequest();
}, [parsedTimeRange]);
}, [makeRequest, parsedTimeRange]);
if (error) {
return <PageError error={error} name={props.name} />;

View file

@ -4,15 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiTitle } from '@elastic/eui';
import React, {
useContext,
Children,
isValidElement,
cloneElement,
FunctionComponent,
useMemo,
isValidElement,
useContext,
} from 'react';
import { EuiTitle } from '@elastic/eui';
import { SideNavContext, SubNavItem } from '../lib/side_nav_context';
import { LayoutProps } from '../types';
@ -31,35 +31,42 @@ export const Section: FunctionComponent<SectionProps> = ({
stopLiveStreaming,
}) => {
const { addNavItem } = useContext(SideNavContext);
const subNavItems: SubNavItem[] = [];
const childrenWithProps = useMemo(
() =>
Children.map(children, child => {
if (isValidElement(child)) {
const metric = (metrics && metrics.find(m => m.id === child.props.id)) || null;
if (metric) {
subNavItems.push({
id: child.props.id,
name: child.props.label,
onClick: () => {
const el = document.getElementById(child.props.id);
if (el) {
el.scrollIntoView();
}
},
});
}
return cloneElement(child, {
metrics,
onChangeRangeTime,
isLiveStreaming,
stopLiveStreaming,
});
}
return null;
}),
[children, metrics, onChangeRangeTime, isLiveStreaming, stopLiveStreaming]
const subNavItems = Children.toArray(children).reduce<SubNavItem[]>(
(accumulatedChildren, child) => {
if (!isValidElement(child)) {
return accumulatedChildren;
}
const metric = metrics?.find(m => m.id === child.props.id) ?? null;
if (metric === null) {
return accumulatedChildren;
}
return [
...accumulatedChildren,
{
id: child.props.id,
name: child.props.label,
onClick: () => {
const el = document.getElementById(child.props.id);
if (el) {
el.scrollIntoView();
}
},
},
];
},
[]
);
const childrenWithProps = Children.map(children, child =>
isValidElement(child)
? cloneElement(child, {
metrics,
onChangeRangeTime,
isLiveStreaming,
stopLiveStreaming,
})
: null
);
if (metrics && subNavItems.length) {

View file

@ -23,29 +23,25 @@ export const SubSection: FunctionComponent<SubSectionProps> = ({
isLiveStreaming,
stopLiveStreaming,
}) => {
if (!children || !metrics) {
const metric = useMemo(() => metrics?.find(m => m.id === id), [id, metrics]);
if (!children || !metric) {
return null;
}
const metric = metrics.find(m => m.id === id);
if (!metric) {
const childrenWithProps = Children.map(children, child => {
if (isValidElement(child)) {
return cloneElement(child, {
metric,
id,
onChangeRangeTime,
isLiveStreaming,
stopLiveStreaming,
});
}
return null;
}
const childrenWithProps = useMemo(
() =>
Children.map(children, child => {
if (isValidElement(child)) {
return cloneElement(child, {
metric,
id,
onChangeRangeTime,
isLiveStreaming,
stopLiveStreaming,
});
}
return null;
}),
[children, metric, id, onChangeRangeTime, isLiveStreaming, stopLiveStreaming]
);
});
return (
<div style={{ margin: '10px 0 16px 0' }} id={id}>
{label ? (

View file

@ -59,13 +59,10 @@ export const useMetricsTime = () => {
const [parsedTimeRange, setParsedTimeRange] = useState(parseRange(defaultRange));
const updateTimeRange = useCallback(
(range: MetricsTimeInput) => {
setTimeRange(range);
setParsedTimeRange(parseRange(range));
},
[setParsedTimeRange]
);
const updateTimeRange = useCallback((range: MetricsTimeInput) => {
setTimeRange(range);
setParsedTimeRange(parseRange(range));
}, []);
return {
timeRange,

View file

@ -112,26 +112,28 @@ export const MetricDetail = withMetricPageProviders(
})}
/>
<DetailPageContent data-test-subj="infraMetricsPage">
<NodeDetailsPage
name={name}
requiredMetrics={filteredRequiredMetrics}
sourceId={sourceId}
timeRange={timeRange}
parsedTimeRange={parsedTimeRange}
nodeType={nodeType}
nodeId={nodeId}
cloudId={cloudId}
metadataLoading={metadataLoading}
isAutoReloading={isAutoReloading}
refreshInterval={refreshInterval}
sideNav={sideNav}
metadata={metadata}
addNavItem={addNavItem}
setRefreshInterval={setRefreshInterval}
setAutoReload={setAutoReload}
triggerRefresh={triggerRefresh}
setTimeRange={setTimeRange}
/>
{metadata ? (
<NodeDetailsPage
name={name}
requiredMetrics={filteredRequiredMetrics}
sourceId={sourceId}
timeRange={timeRange}
parsedTimeRange={parsedTimeRange}
nodeType={nodeType}
nodeId={nodeId}
cloudId={cloudId}
metadataLoading={metadataLoading}
isAutoReloading={isAutoReloading}
refreshInterval={refreshInterval}
sideNav={sideNav}
metadata={metadata}
addNavItem={addNavItem}
setRefreshInterval={setRefreshInterval}
setAutoReload={setAutoReload}
triggerRefresh={triggerRefresh}
setTimeRange={setTimeRange}
/>
) : null}
</DetailPageContent>
</ColumnarPage>
)}

View file

@ -27,5 +27,8 @@ export const useCancellableEffect = (
effect(() => cancellationSignal.isCancelled);
return cancellationSignal.cancel;
// the dependencies are managed externally
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
};

View file

@ -28,10 +28,15 @@ import { useObservable } from './use_observable';
export const useKibanaUiSetting = (key: string, defaultValue?: any) => {
const uiSettingsClient = npSetup.core.uiSettings;
const uiSetting$ = useMemo(() => uiSettingsClient.get$(key, defaultValue), [uiSettingsClient]);
const uiSetting$ = useMemo(() => uiSettingsClient.get$(key, defaultValue), [
defaultValue,
key,
uiSettingsClient,
]);
const uiSetting = useObservable(uiSetting$);
const setUiSetting = useCallback((value: any) => uiSettingsClient.set(key, value), [
key,
uiSettingsClient,
]);

View file

@ -190,6 +190,8 @@ export const useTrackedPromise = <Arguments extends any[], Result>(
return newPendingPromise.promise;
},
// the dependencies are managed by the caller
// eslint-disable-next-line react-hooks/exhaustive-deps
dependencies
);

View file

@ -5,10 +5,10 @@
*/
import { Location } from 'history';
import { useMemo, useCallback } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { decode, encode, RisonValue } from 'rison-node';
import { QueryString } from 'ui/utils/query_string';
import { useHistory } from './history_context';
export const useUrlState = <State>({
@ -16,21 +16,26 @@ export const useUrlState = <State>({
decodeUrlState,
encodeUrlState,
urlStateKey,
writeDefaultState = false,
}: {
defaultState: State;
decodeUrlState: (value: RisonValue | undefined) => State | undefined;
encodeUrlState: (value: State) => RisonValue | undefined;
urlStateKey: string;
writeDefaultState?: boolean;
}) => {
const history = useHistory();
// history.location is mutable so we can't reliably use useMemo
const queryString = history?.location ? getQueryStringFromLocation(history.location) : '';
const urlStateString = useMemo(() => {
if (!history) {
if (!queryString) {
return;
}
return getParamFromQueryString(getQueryStringFromLocation(history.location), urlStateKey);
}, [history && history.location, urlStateKey]);
return getParamFromQueryString(queryString, urlStateKey);
}, [queryString, urlStateKey]);
const decodedState = useMemo(() => decodeUrlState(decodeRisonUrlState(urlStateString)), [
decodeUrlState,
@ -44,27 +49,38 @@ export const useUrlState = <State>({
const setState = useCallback(
(newState: State | undefined) => {
if (!history) {
if (!history || !history.location) {
return;
}
const location = history.location;
const currentLocation = history.location;
const newLocation = replaceQueryStringInLocation(
location,
currentLocation,
replaceStateKeyInQueryString(
urlStateKey,
typeof newState !== 'undefined' ? encodeUrlState(newState) : undefined
)(getQueryStringFromLocation(location))
)(getQueryStringFromLocation(currentLocation))
);
if (newLocation !== location) {
if (newLocation !== currentLocation) {
history.replace(newLocation);
}
},
[encodeUrlState, history, history && history.location, urlStateKey]
[encodeUrlState, history, urlStateKey]
);
const [shouldInitialize, setShouldInitialize] = useState(
writeDefaultState && typeof decodedState === 'undefined'
);
useEffect(() => {
if (shouldInitialize) {
setShouldInitialize(false);
setState(defaultState);
}
}, [shouldInitialize, setState, defaultState]);
return [state, setState] as [typeof state, typeof setState];
};

View file

@ -20,6 +20,6 @@ export const useVisibilityState = (initialState: boolean) => {
show,
toggle,
}),
[isVisible, show, hide]
[hide, isVisible, show, toggle]
);
};