mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Infrastructure UI] Add unified search to hosts table (#143850)
* Add unified search to hosts table * Add saved query support * Adjust error handling * Minor refactoring and unit tests * Revert changes to translations * CR fixes
This commit is contained in:
parent
2efad9d15a
commit
eff4ce0cd5
11 changed files with 437 additions and 159 deletions
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { InfraLoadingPanel } from '../../../../components/loading';
|
||||
import { useMetricsDataViewContext } from '../hooks/use_data_view';
|
||||
import { UnifiedSearchBar } from './unified_search_bar';
|
||||
import { HostsTable } from './hosts_table';
|
||||
|
||||
export const HostContainer = () => {
|
||||
const { metricsDataView, isDataViewLoading, hasFailedLoadingDataView } =
|
||||
useMetricsDataViewContext();
|
||||
|
||||
if (isDataViewLoading) {
|
||||
return (
|
||||
<InfraLoadingPanel
|
||||
height="100%"
|
||||
width="auto"
|
||||
text={i18n.translate('xpack.infra.waffle.loadingDataText', {
|
||||
defaultMessage: 'Loading data',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return hasFailedLoadingDataView || !metricsDataView ? null : (
|
||||
<>
|
||||
<UnifiedSearchBar dataView={metricsDataView} />
|
||||
<EuiSpacer />
|
||||
<HostsTable />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -7,16 +7,86 @@
|
|||
|
||||
import React from 'react';
|
||||
import { EuiInMemoryTable } from '@elastic/eui';
|
||||
import type { SnapshotNode } from '../../../../../common/http_api';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { HostsTableColumns } from './hosts_table_columns';
|
||||
import { NoData } from '../../../../components/empty_states';
|
||||
import { InfraLoadingPanel } from '../../../../components/loading';
|
||||
import { useHostTable } from '../hooks/use_host_table';
|
||||
import { useSnapshot } from '../../inventory_view/hooks/use_snaphot';
|
||||
import type { SnapshotMetricType } from '../../../../../common/inventory_models/types';
|
||||
import type { InfraTimerangeInput } from '../../../../../common/http_api';
|
||||
import { useUnifiedSearchContext } from '../hooks/use_unified_search';
|
||||
import { useSourceContext } from '../../../../containers/metrics_source';
|
||||
|
||||
interface Props {
|
||||
nodes: SnapshotNode[];
|
||||
}
|
||||
const HOST_METRICS: Array<{ type: SnapshotMetricType }> = [
|
||||
{ type: 'rx' },
|
||||
{ type: 'tx' },
|
||||
{ type: 'memory' },
|
||||
{ type: 'cpuCores' },
|
||||
{ type: 'memoryTotal' },
|
||||
];
|
||||
|
||||
export const HostsTable = () => {
|
||||
const { sourceId } = useSourceContext();
|
||||
const { esQuery, dateRangeTimestamp } = useUnifiedSearchContext();
|
||||
|
||||
const timeRange: InfraTimerangeInput = {
|
||||
from: dateRangeTimestamp.from,
|
||||
to: dateRangeTimestamp.to,
|
||||
interval: '1m',
|
||||
ignoreLookback: true,
|
||||
};
|
||||
|
||||
// Snapshot endpoint internally uses the indices stored in source.configuration.metricAlias.
|
||||
// For the Unified Search, we create a data view, which for now will be built off of source.configuration.metricAlias too
|
||||
// if we introduce data view selection, we'll have to change this hook and the endpoint to accept a new parameter for the indices
|
||||
const { loading, nodes, reload } = useSnapshot(
|
||||
esQuery && JSON.stringify(esQuery),
|
||||
HOST_METRICS,
|
||||
[],
|
||||
'host',
|
||||
sourceId,
|
||||
dateRangeTimestamp.to,
|
||||
'',
|
||||
'',
|
||||
true,
|
||||
timeRange
|
||||
);
|
||||
|
||||
export const HostsTable: React.FunctionComponent<Props> = ({ nodes }) => {
|
||||
const items = useHostTable(nodes);
|
||||
const noData = items.length === 0;
|
||||
|
||||
return <EuiInMemoryTable pagination sorting items={items} columns={HostsTableColumns} />;
|
||||
return (
|
||||
<>
|
||||
{loading ? (
|
||||
<InfraLoadingPanel
|
||||
height="100%"
|
||||
width="auto"
|
||||
text={i18n.translate('xpack.infra.waffle.loadingDataText', {
|
||||
defaultMessage: 'Loading data',
|
||||
})}
|
||||
/>
|
||||
) : noData ? (
|
||||
<div>
|
||||
<NoData
|
||||
titleText={i18n.translate('xpack.infra.waffle.noDataTitle', {
|
||||
defaultMessage: 'There is no data to display.',
|
||||
})}
|
||||
bodyText={i18n.translate('xpack.infra.waffle.noDataDescription', {
|
||||
defaultMessage: 'Try adjusting your time or filter.',
|
||||
})}
|
||||
refetchText={i18n.translate('xpack.infra.waffle.checkNewDataButtonLabel', {
|
||||
defaultMessage: 'Check for new data',
|
||||
})}
|
||||
onRefetch={() => {
|
||||
reload();
|
||||
}}
|
||||
testString="noMetricsDataPrompt"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<EuiInMemoryTable pagination sorting items={items} columns={HostsTableColumns} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import type { Filter, Query, TimeRange } from '@kbn/es-query';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import type { SavedQuery } from '@kbn/data-plugin/public';
|
||||
import type { InfraClientStartDeps } from '../../../../types';
|
||||
import { useUnifiedSearchContext } from '../hooks/use_unified_search';
|
||||
|
||||
interface Props {
|
||||
dataView: DataView;
|
||||
}
|
||||
|
||||
export const UnifiedSearchBar = ({ dataView }: Props) => {
|
||||
const {
|
||||
services: { unifiedSearch },
|
||||
} = useKibana<InfraClientStartDeps>();
|
||||
const {
|
||||
unifiedSearchDateRange,
|
||||
unifiedSearchQuery,
|
||||
submitFilterChange,
|
||||
saveQuery,
|
||||
clearSavedQUery,
|
||||
} = useUnifiedSearchContext();
|
||||
|
||||
const { SearchBar } = unifiedSearch.ui;
|
||||
|
||||
const onFilterChange = (filters: Filter[]) => {
|
||||
onQueryChange({ filters });
|
||||
};
|
||||
|
||||
const onQuerySubmit = (payload: { dateRange: TimeRange; query?: Query }) => {
|
||||
onQueryChange({ payload });
|
||||
};
|
||||
|
||||
const onClearSavedQuery = () => {
|
||||
clearSavedQUery();
|
||||
};
|
||||
|
||||
const onQuerySave = (savedQuery: SavedQuery) => {
|
||||
saveQuery(savedQuery);
|
||||
};
|
||||
|
||||
const onQueryChange = ({
|
||||
payload,
|
||||
filters,
|
||||
}: {
|
||||
payload?: { dateRange: TimeRange; query?: Query };
|
||||
filters?: Filter[];
|
||||
}) => {
|
||||
submitFilterChange(payload?.query, payload?.dateRange, filters);
|
||||
};
|
||||
|
||||
return (
|
||||
<SearchBar
|
||||
appName={'Infra Hosts'}
|
||||
indexPatterns={[dataView]}
|
||||
query={unifiedSearchQuery}
|
||||
dateRangeFrom={unifiedSearchDateRange.from}
|
||||
dateRangeTo={unifiedSearchDateRange.to}
|
||||
onQuerySubmit={onQuerySubmit}
|
||||
onSaved={onQuerySave}
|
||||
onSavedQueryUpdated={onQuerySave}
|
||||
onClearSavedQuery={onClearSavedQuery}
|
||||
showSaveQuery
|
||||
showQueryInput
|
||||
// @ts-expect-error onFiltersUpdated is a valid prop on SearchBar
|
||||
onFiltersUpdated={onFilterChange}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useDataView } from './use_data_view';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { KibanaReactContextValue, useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { coreMock, notificationServiceMock } from '@kbn/core/public/mocks';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { DataViewsServicePublic } from '@kbn/data-views-plugin/public/types';
|
||||
import { InfraClientStartDeps } from '../../../../types';
|
||||
import { CoreStart } from '@kbn/core/public';
|
||||
|
||||
jest.mock('@kbn/i18n');
|
||||
jest.mock('@kbn/kibana-react-plugin/public');
|
||||
|
||||
let dataViewMock: jest.Mocked<DataViewsServicePublic>;
|
||||
const useKibanaMock = useKibana as jest.MockedFunction<typeof useKibana>;
|
||||
const notificationMock = notificationServiceMock.createStartContract();
|
||||
const prop = { metricAlias: 'test' };
|
||||
|
||||
const mockUseKibana = () => {
|
||||
useKibanaMock.mockReturnValue({
|
||||
services: {
|
||||
...coreMock.createStart(),
|
||||
notifications: notificationMock,
|
||||
dataViews: dataViewMock,
|
||||
} as Partial<CoreStart> & Partial<InfraClientStartDeps>,
|
||||
} as unknown as KibanaReactContextValue<Partial<CoreStart> & Partial<InfraClientStartDeps>>);
|
||||
};
|
||||
|
||||
const mockDataView = {
|
||||
id: 'mock-id',
|
||||
title: 'mock-title',
|
||||
timeFieldName: 'mock-time-field-name',
|
||||
isPersisted: () => false,
|
||||
getName: () => 'mock-data-view',
|
||||
toSpec: () => ({}),
|
||||
} as jest.Mocked<DataView>;
|
||||
|
||||
describe('useHostTable hook', () => {
|
||||
beforeEach(() => {
|
||||
dataViewMock = {
|
||||
createAndSave: jest.fn(),
|
||||
find: jest.fn(),
|
||||
} as Partial<DataViewsServicePublic> as jest.Mocked<DataViewsServicePublic>;
|
||||
|
||||
mockUseKibana();
|
||||
});
|
||||
|
||||
it('should find an existing Data view', async () => {
|
||||
dataViewMock.find.mockReturnValue(Promise.resolve([mockDataView]));
|
||||
const { result, waitForNextUpdate } = renderHook(() => useDataView(prop));
|
||||
|
||||
await waitForNextUpdate();
|
||||
expect(result.current.isDataViewLoading).toEqual(false);
|
||||
expect(result.current.hasFailedLoadingDataView).toEqual(false);
|
||||
expect(result.current.metricsDataView).toEqual(mockDataView);
|
||||
});
|
||||
|
||||
it('should create a new Data view', async () => {
|
||||
dataViewMock.find.mockReturnValue(Promise.resolve([]));
|
||||
dataViewMock.createAndSave.mockReturnValue(Promise.resolve(mockDataView));
|
||||
const { result, waitForNextUpdate } = renderHook(() => useDataView(prop));
|
||||
|
||||
await waitForNextUpdate();
|
||||
expect(result.current.isDataViewLoading).toEqual(false);
|
||||
expect(result.current.hasFailedLoadingDataView).toEqual(false);
|
||||
expect(result.current.metricsDataView).toEqual(mockDataView);
|
||||
});
|
||||
|
||||
it('should display a toast when it fails to load the data view', async () => {
|
||||
dataViewMock.find.mockReturnValue(Promise.reject());
|
||||
const { result, waitForNextUpdate } = renderHook(() => useDataView(prop));
|
||||
|
||||
await waitForNextUpdate();
|
||||
expect(result.current.isDataViewLoading).toEqual(false);
|
||||
expect(result.current.hasFailedLoadingDataView).toEqual(true);
|
||||
expect(result.current.metricsDataView).toBeUndefined();
|
||||
expect(notificationMock.toasts.addDanger).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
|
@ -5,7 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useCallback, useState, useEffect } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useCallback, useState, useEffect, useMemo } from 'react';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import createContainer from 'constate';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
|
@ -15,7 +16,7 @@ import { useTrackedPromise } from '../../../../utils/use_tracked_promise';
|
|||
export const useDataView = ({ metricAlias }: { metricAlias: string }) => {
|
||||
const [metricsDataView, setMetricsDataView] = useState<DataView>();
|
||||
const {
|
||||
services: { dataViews },
|
||||
services: { dataViews, notifications },
|
||||
} = useKibana<InfraClientStartDeps>();
|
||||
|
||||
const [createDataViewRequest, createDataView] = useTrackedPromise(
|
||||
|
@ -33,7 +34,7 @@ export const useDataView = ({ metricAlias }: { metricAlias: string }) => {
|
|||
|
||||
const [getDataViewRequest, getDataView] = useTrackedPromise(
|
||||
{
|
||||
createPromise: (indexPattern: string): Promise<DataView[]> => {
|
||||
createPromise: (_indexPattern: string): Promise<DataView[]> => {
|
||||
return dataViews.find(metricAlias, 1);
|
||||
},
|
||||
onResolve: (response: DataView[]) => {
|
||||
|
@ -58,17 +59,36 @@ export const useDataView = ({ metricAlias }: { metricAlias: string }) => {
|
|||
}
|
||||
}, [metricAlias, createDataView, getDataView]);
|
||||
|
||||
const hasFailedFetchingDataView = getDataViewRequest.state === 'rejected';
|
||||
const hasFailedCreatingDataView = createDataViewRequest.state === 'rejected';
|
||||
const isDataViewLoading = useMemo(
|
||||
() => getDataViewRequest.state === 'pending' || createDataViewRequest.state === 'pending',
|
||||
[getDataViewRequest.state, createDataViewRequest.state]
|
||||
);
|
||||
|
||||
const hasFailedLoadingDataView = useMemo(
|
||||
() => getDataViewRequest.state === 'rejected' || createDataViewRequest.state === 'rejected',
|
||||
[getDataViewRequest.state, createDataViewRequest.state]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
loadDataView();
|
||||
}, [metricAlias, loadDataView]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasFailedLoadingDataView && notifications) {
|
||||
notifications.toasts.addDanger(
|
||||
i18n.translate('xpack.infra.hostsTable.errorOnCreateOrLoadDataview', {
|
||||
defaultMessage:
|
||||
'There was an error trying to load or create the Data View: {metricAlias}',
|
||||
values: { metricAlias },
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [hasFailedLoadingDataView, notifications, metricAlias]);
|
||||
|
||||
return {
|
||||
metricsDataView,
|
||||
hasFailedCreatingDataView,
|
||||
hasFailedFetchingDataView,
|
||||
isDataViewLoading,
|
||||
hasFailedLoadingDataView,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import createContainer from 'constate';
|
||||
import { useCallback, useReducer } from 'react';
|
||||
import { buildEsQuery, Filter, Query, TimeRange } from '@kbn/es-query';
|
||||
import DateMath from '@kbn/datemath';
|
||||
import type { SavedQuery } from '@kbn/data-plugin/public';
|
||||
import type { InfraClientStartDeps } from '../../../../types';
|
||||
import { useMetricsDataViewContext } from './use_data_view';
|
||||
import { useKibanaTimefilterTime } from '../../../../hooks/use_kibana_timefilter_time';
|
||||
|
||||
const DEFAULT_FROM_MINUTES_VALUE = 15;
|
||||
|
||||
export const useUnifiedSearch = () => {
|
||||
const [, forceUpdate] = useReducer((x: number) => x + 1, 0);
|
||||
|
||||
const { metricsDataView } = useMetricsDataViewContext();
|
||||
const { services } = useKibana<InfraClientStartDeps>();
|
||||
const {
|
||||
data: { query: queryManager },
|
||||
} = services;
|
||||
|
||||
const [getTime, setTime] = useKibanaTimefilterTime({
|
||||
from: `now-${DEFAULT_FROM_MINUTES_VALUE}m`,
|
||||
to: 'now',
|
||||
});
|
||||
const { queryString, filterManager } = queryManager;
|
||||
|
||||
const currentDate = new Date();
|
||||
const fromTS =
|
||||
DateMath.parse(getTime().from)?.valueOf() ??
|
||||
new Date(currentDate.getMinutes() - DEFAULT_FROM_MINUTES_VALUE).getTime();
|
||||
const toTS = DateMath.parse(getTime().to)?.valueOf() ?? currentDate.getTime();
|
||||
|
||||
const currentTimeRange = {
|
||||
from: fromTS,
|
||||
to: toTS,
|
||||
};
|
||||
|
||||
const submitFilterChange = useCallback(
|
||||
(query?: Query, dateRange?: TimeRange, filters?: Filter[]) => {
|
||||
if (filters) {
|
||||
filterManager.setFilters(filters);
|
||||
}
|
||||
|
||||
setTime({
|
||||
...getTime(),
|
||||
...dateRange,
|
||||
});
|
||||
|
||||
queryString.setQuery({ ...queryString.getQuery(), ...query });
|
||||
// Unified search holds the all state, we need to force the hook to rerender so that it can return the most recent values
|
||||
// This can be removed once we get the state from the URL
|
||||
forceUpdate();
|
||||
},
|
||||
[filterManager, queryString, getTime, setTime]
|
||||
);
|
||||
|
||||
const saveQuery = useCallback(
|
||||
(newSavedQuery: SavedQuery) => {
|
||||
const savedQueryFilters = newSavedQuery.attributes.filters ?? [];
|
||||
const globalFilters = filterManager.getGlobalFilters();
|
||||
filterManager.setFilters([...savedQueryFilters, ...globalFilters]);
|
||||
|
||||
// Unified search holds the all state, we need to force the hook to rerender so that it can return the most recent values
|
||||
// This can be removed once we get the state from the URL
|
||||
forceUpdate();
|
||||
},
|
||||
[filterManager]
|
||||
);
|
||||
|
||||
const clearSavedQUery = useCallback(() => {
|
||||
filterManager.setFilters(filterManager.getGlobalFilters());
|
||||
|
||||
// Unified search holds the all state, we need to force the hook to rerender so that it can return the most recent values
|
||||
// This can be removed once we get the state from the URL
|
||||
forceUpdate();
|
||||
}, [filterManager]);
|
||||
|
||||
const buildQuery = useCallback(() => {
|
||||
if (!metricsDataView) {
|
||||
return null;
|
||||
}
|
||||
return buildEsQuery(metricsDataView, queryString.getQuery(), filterManager.getFilters());
|
||||
}, [filterManager, metricsDataView, queryString]);
|
||||
|
||||
return {
|
||||
dateRangeTimestamp: currentTimeRange,
|
||||
esQuery: buildQuery(),
|
||||
submitFilterChange,
|
||||
saveQuery,
|
||||
clearSavedQUery,
|
||||
unifiedSearchQuery: queryString.getQuery() as Query,
|
||||
unifiedSearchDateRange: getTime(),
|
||||
unifiedSearchFilters: filterManager.getFilters(),
|
||||
};
|
||||
};
|
||||
|
||||
export const UnifiedSearch = createContainer(useUnifiedSearch);
|
||||
export const [UnifiedSearchProvider, useUnifiedSearchContext] = UnifiedSearch;
|
|
@ -1,120 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Query, TimeRange } from '@kbn/es-query';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { SearchBar } from '@kbn/unified-search-plugin/public';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { NoData } from '../../../components/empty_states';
|
||||
import { InfraLoadingPanel } from '../../../components/loading';
|
||||
import { useMetricsDataViewContext } from './hooks/use_data_view';
|
||||
import { HostsTable } from './components/hosts_table';
|
||||
import { useSourceContext } from '../../../containers/metrics_source';
|
||||
import { useSnapshot } from '../inventory_view/hooks/use_snaphot';
|
||||
import type { SnapshotMetricType } from '../../../../common/inventory_models/types';
|
||||
|
||||
export const HostsContent: React.FunctionComponent = () => {
|
||||
const { source, sourceId } = useSourceContext();
|
||||
const [dateRange, setDateRange] = useState<TimeRange>({ from: 'now-15m', to: 'now' });
|
||||
const [query, setQuery] = useState<Query>({ query: '', language: 'kuery' });
|
||||
const { metricsDataView, hasFailedCreatingDataView, hasFailedFetchingDataView } =
|
||||
useMetricsDataViewContext();
|
||||
// needed to refresh the lens table when filters havent changed
|
||||
|
||||
const onQuerySubmit = useCallback(
|
||||
(payload: { dateRange: TimeRange; query?: Query }) => {
|
||||
setDateRange(payload.dateRange);
|
||||
if (payload.query) {
|
||||
setQuery(payload.query);
|
||||
}
|
||||
},
|
||||
[setDateRange, setQuery]
|
||||
);
|
||||
|
||||
const hostMetrics: Array<{ type: SnapshotMetricType }> = [
|
||||
{ type: 'rx' },
|
||||
{ type: 'tx' },
|
||||
{ type: 'memory' },
|
||||
{ type: 'cpuCores' },
|
||||
{ type: 'memoryTotal' },
|
||||
];
|
||||
|
||||
const { loading, nodes, reload } = useSnapshot(
|
||||
'', // use the unified search query, supported type?
|
||||
hostMetrics,
|
||||
[],
|
||||
'host',
|
||||
sourceId,
|
||||
1666710279338, // currentTime. need to add support for TimeRange?
|
||||
'',
|
||||
'',
|
||||
true,
|
||||
{
|
||||
from: 1666710279338, // dynamic time range needs to be supported
|
||||
interval: '1m',
|
||||
lookbackSize: 5,
|
||||
to: 1666711479338,
|
||||
}
|
||||
);
|
||||
|
||||
const noData = !loading && nodes && nodes.length === 0;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{metricsDataView && !loading ? (
|
||||
noData ? (
|
||||
<NoData
|
||||
titleText={i18n.translate('xpack.infra.waffle.noDataTitle', {
|
||||
defaultMessage: 'There is no data to display.',
|
||||
})}
|
||||
bodyText={i18n.translate('xpack.infra.waffle.noDataDescription', {
|
||||
defaultMessage: 'Try adjusting your time or filter.',
|
||||
})}
|
||||
refetchText={i18n.translate('xpack.infra.waffle.checkNewDataButtonLabel', {
|
||||
defaultMessage: 'Check for new data',
|
||||
})}
|
||||
onRefetch={() => {
|
||||
reload();
|
||||
}}
|
||||
testString="noMetricsDataPrompt"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<SearchBar
|
||||
showFilterBar={false}
|
||||
showDatePicker={true}
|
||||
showAutoRefreshOnly={false}
|
||||
showSaveQuery={true}
|
||||
showQueryInput={true}
|
||||
query={query}
|
||||
dateRangeFrom={dateRange.from}
|
||||
dateRangeTo={dateRange.to}
|
||||
indexPatterns={[metricsDataView]}
|
||||
onQuerySubmit={onQuerySubmit}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<HostsTable nodes={nodes} />
|
||||
</>
|
||||
)
|
||||
) : hasFailedCreatingDataView || hasFailedFetchingDataView ? (
|
||||
<div>
|
||||
<div>There was an error trying to load or create the Data View:</div>
|
||||
{source?.configuration.metricAlias}
|
||||
</div>
|
||||
) : (
|
||||
<InfraLoadingPanel
|
||||
height="100vh"
|
||||
width="auto"
|
||||
text={i18n.translate('xpack.infra.waffle.loadingDataText', {
|
||||
defaultMessage: 'Loading data',
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -9,16 +9,16 @@ import { EuiErrorBoundary } from '@elastic/eui';
|
|||
import React from 'react';
|
||||
import { useTrackPageview } from '@kbn/observability-plugin/public';
|
||||
import { APP_WRAPPER_CLASS } from '@kbn/core/public';
|
||||
|
||||
import { SourceErrorPage } from '../../../components/source_error_page';
|
||||
import { SourceLoadingPage } from '../../../components/source_loading_page';
|
||||
import { useSourceContext } from '../../../containers/metrics_source';
|
||||
import { useMetricsBreadcrumbs } from '../../../hooks/use_metrics_breadcrumbs';
|
||||
import { MetricsPageTemplate } from '../page_template';
|
||||
import { hostsTitle } from '../../../translations';
|
||||
import { HostsContent } from './hosts_content';
|
||||
import { MetricsDataViewProvider } from './hooks/use_data_view';
|
||||
import { fullHeightContentStyles } from '../../../page_template.styles';
|
||||
import { UnifiedSearchProvider } from './hooks/use_unified_search';
|
||||
import { HostContainer } from './components/hosts_container';
|
||||
|
||||
export const HostsPage = () => {
|
||||
const {
|
||||
|
@ -56,7 +56,9 @@ export const HostsPage = () => {
|
|||
}}
|
||||
>
|
||||
<MetricsDataViewProvider metricAlias={source.configuration.metricAlias}>
|
||||
<HostsContent />
|
||||
<UnifiedSearchProvider>
|
||||
<HostContainer />
|
||||
</UnifiedSearchProvider>
|
||||
</MetricsDataViewProvider>
|
||||
</MetricsPageTemplate>
|
||||
</div>
|
||||
|
|
|
@ -15595,15 +15595,15 @@
|
|||
"xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel": "Voir les instructions de configuration",
|
||||
"xpack.infra.homePage.settingsTabTitle": "Paramètres",
|
||||
"xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder": "Rechercher des données d'infrastructure… (par exemple host.name:host-1)",
|
||||
"xpack.infra.hostsTable.nameColumnHeader": "Nom",
|
||||
"xpack.infra.hostsTable.operatingSystemColumnHeader": "Système d'exploitation",
|
||||
"xpack.infra.hostsTable.numberOfCpusColumnHeader": "Nombre de processeurs",
|
||||
"xpack.infra.hostsTable.diskLatencyColumnHeader": "",
|
||||
"xpack.infra.hostsTable.averageTxColumnHeader": "",
|
||||
"xpack.infra.hostsTable.averageRxColumnHeader": "",
|
||||
"xpack.infra.hostsTable.averageMemoryTotalColumnHeader": "Total de la mémoire (moy.)",
|
||||
"xpack.infra.hostsTable.servicesOnHostColumnHeader": "",
|
||||
"xpack.infra.hostsTable.averageMemoryUsageColumnHeader": "Utilisation de la mémoire (moy.)",
|
||||
"xpack.infra.hostsTable.averageRxColumnHeader": "",
|
||||
"xpack.infra.hostsTable.averageTxColumnHeader": "",
|
||||
"xpack.infra.hostsTable.diskLatencyColumnHeader": "",
|
||||
"xpack.infra.hostsTable.nameColumnHeader": "Nom",
|
||||
"xpack.infra.hostsTable.numberOfCpusColumnHeader": "Nombre de processeurs",
|
||||
"xpack.infra.hostsTable.operatingSystemColumnHeader": "Système d'exploitation",
|
||||
"xpack.infra.hostsTable.servicesOnHostColumnHeader": "",
|
||||
"xpack.infra.infra.nodeDetails.apmTabLabel": "APM",
|
||||
"xpack.infra.infra.nodeDetails.createAlertLink": "Créer une règle d'inventaire",
|
||||
"xpack.infra.infra.nodeDetails.openAsPage": "Ouvrir en tant que page",
|
||||
|
@ -33705,4 +33705,4 @@
|
|||
"xpack.painlessLab.title": "Painless Lab",
|
||||
"xpack.painlessLab.walkthroughButtonLabel": "Présentation"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15580,15 +15580,15 @@
|
|||
"xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel": "セットアップの手順を表示",
|
||||
"xpack.infra.homePage.settingsTabTitle": "設定",
|
||||
"xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder": "インフラストラクチャデータを検索…(例:host.name:host-1)",
|
||||
"xpack.infra.hostsTable.nameColumnHeader": "名前",
|
||||
"xpack.infra.hostsTable.operatingSystemColumnHeader": "オペレーティングシステム",
|
||||
"xpack.infra.hostsTable.numberOfCpusColumnHeader": "CPU数",
|
||||
"xpack.infra.hostsTable.diskLatencyColumnHeader": "",
|
||||
"xpack.infra.hostsTable.averageMemoryTotalColumnHeader": "メモリ合計 (平均) ",
|
||||
"xpack.infra.hostsTable.averageMemoryUsageColumnHeader": "メモリー使用状況(平均)",
|
||||
"xpack.infra.hostsTable.averageTxColumnHeader": "",
|
||||
"xpack.infra.hostsTable.averageRxColumnHeader": "",
|
||||
"xpack.infra.hostsTable.averageMemoryTotalColumnHeader": "メモリ合計 (平均) ",
|
||||
"xpack.infra.hostsTable.diskLatencyColumnHeader": "",
|
||||
"xpack.infra.hostsTable.nameColumnHeader": "名前",
|
||||
"xpack.infra.hostsTable.numberOfCpusColumnHeader": "CPU数",
|
||||
"xpack.infra.hostsTable.operatingSystemColumnHeader": "オペレーティングシステム",
|
||||
"xpack.infra.hostsTable.servicesOnHostColumnHeader": "",
|
||||
"xpack.infra.hostsTable.averageMemoryUsageColumnHeader": "メモリー使用状況(平均)",
|
||||
"xpack.infra.infra.nodeDetails.apmTabLabel": "APM",
|
||||
"xpack.infra.infra.nodeDetails.createAlertLink": "インベントリルールの作成",
|
||||
"xpack.infra.infra.nodeDetails.openAsPage": "ページとして開く",
|
||||
|
@ -33679,4 +33679,4 @@
|
|||
"xpack.painlessLab.title": "Painless Lab",
|
||||
"xpack.painlessLab.walkthroughButtonLabel": "実地検証"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15601,15 +15601,15 @@
|
|||
"xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel": "查看设置说明",
|
||||
"xpack.infra.homePage.settingsTabTitle": "设置",
|
||||
"xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder": "搜索基础设施数据……(例如 host.name:host-1)",
|
||||
"xpack.infra.hostsTable.nameColumnHeader": "名称",
|
||||
"xpack.infra.hostsTable.operatingSystemColumnHeader": "操作系统",
|
||||
"xpack.infra.hostsTable.numberOfCpusColumnHeader": "# 个 CPU",
|
||||
"xpack.infra.hostsTable.diskLatencyColumnHeader": "",
|
||||
"xpack.infra.hostsTable.averageTxColumnHeader": "",
|
||||
"xpack.infra.hostsTable.averageRxColumnHeader": "",
|
||||
"xpack.infra.hostsTable.averageMemoryTotalColumnHeader": "内存合计 (平均值)",
|
||||
"xpack.infra.hostsTable.servicesOnHostColumnHeader": "",
|
||||
"xpack.infra.hostsTable.averageMemoryUsageColumnHeader": "内存使用率(平均值)",
|
||||
"xpack.infra.hostsTable.averageRxColumnHeader": "",
|
||||
"xpack.infra.hostsTable.averageTxColumnHeader": "",
|
||||
"xpack.infra.hostsTable.diskLatencyColumnHeader": "",
|
||||
"xpack.infra.hostsTable.nameColumnHeader": "名称",
|
||||
"xpack.infra.hostsTable.numberOfCpusColumnHeader": "# 个 CPU",
|
||||
"xpack.infra.hostsTable.operatingSystemColumnHeader": "操作系统",
|
||||
"xpack.infra.hostsTable.servicesOnHostColumnHeader": "",
|
||||
"xpack.infra.infra.nodeDetails.apmTabLabel": "APM",
|
||||
"xpack.infra.infra.nodeDetails.createAlertLink": "创建库存规则",
|
||||
"xpack.infra.infra.nodeDetails.openAsPage": "以页面形式打开",
|
||||
|
@ -33716,4 +33716,4 @@
|
|||
"xpack.painlessLab.title": "Painless 实验室",
|
||||
"xpack.painlessLab.walkthroughButtonLabel": "指导"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue