[Infrastructure UI]: Implement Telemetry events for Hosts View (#149497)

## 📓 Summary

Re-opened to fix errors and conflicts on #149329 

Closes #148739 

Implement the following Telemetry custom events:
- **Host Entry Clicked**
- **Hosts View Query Submitted**

When triggered, these events will track to FullStory some details
relevant to the performed query or the selected host from the table.

These changes include a necessary refactor of the hosts' table hook in
order to trigger an event on each entry click without performance
issues.

## 🧪 Testing

As long as we don't have access to the staging environment of FullStory,
is difficult to test E2E if an event is tracked or not.

What we can firstly test is that our events are correctly collected by
the `core.analytics` API and shipped correctly to the FullStory
integration. To verify this, please follow these steps:

1. Add the following configuration to your `kibana.dev.yml` file:
```yml
# Enable Fullstory Telemetry
xpack.cloud.id: "elastic_kibana_dev"
xpack.cloud.full_story.enabled: true
# Staging FullStory OrgID
xpack.cloud.full_story.org_id: "1397FY"
```

2. Add debugging breakpoint into the [`sendToShipper()`
function](https://github.com/elastic/kibana/blob/main/packages/analytics/client/src/analytics_client/analytics_client.ts#L275)
in the analytics package.
3. Navigate to Hosts View and perform search queries or click on table
entries to check whether the events are correctly composed.

---------

Co-authored-by: Marco Antonio Ghiani <marcoantonio.ghiani@elastic.co>
This commit is contained in:
Marco Antonio Ghiani 2023-02-02 10:00:16 +01:00 committed by GitHub
parent 6dc77ce630
commit e1afb6584d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 339 additions and 275 deletions

View file

@ -18,7 +18,11 @@ const configSchema = schema.object({
schema.maybe(schema.string())
),
eventTypesAllowlist: schema.arrayOf(schema.string(), {
defaultValue: ['Loaded Kibana'],
defaultValue: [
'Loaded Kibana', // Sent once per page refresh (potentially, once per session)
'Hosts View Query Submitted', // Worst-case scenario 1 every 2 seconds
'Host Entry Clicked', // Worst-case scenario once per second - AT RISK,
],
}),
});

View file

@ -0,0 +1,45 @@
/*
* 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.
*/
const ONE_MINUTE = 60000;
const FIVE_MINUTES = ONE_MINUTE * 5;
const TEN_MINUTES = ONE_MINUTE * 10;
const THIRTY_MINUTES = ONE_MINUTE * 30;
const ONE_HOUR = ONE_MINUTE * 60;
const TWO_HOURS = ONE_HOUR * 2;
const EIGHT_HOURS = ONE_HOUR * 8;
const TWELVE_HOURS = ONE_HOUR * 12;
const ONE_DAY = ONE_HOUR * 24;
const TWO_DAYS = ONE_DAY * 2;
const SEVEN_DAYS = ONE_DAY * 7;
const FOURTEEN_DAYS = ONE_DAY * 14;
const THIRTY_DAYS = ONE_DAY * 30;
const SIXTY_DAYS = ONE_DAY * 60;
const NINETY_DAYS = ONE_DAY * 90;
const HALF_YEAR = ONE_DAY * 180;
const ONE_YEAR = ONE_DAY * 365;
export const telemetryTimeRangeFormatter = (ms: number): string => {
if (ms < ONE_MINUTE) return '1. Less than 1 minute';
if (ms >= ONE_MINUTE && ms < FIVE_MINUTES) return '2. 1-5 minutes';
if (ms >= FIVE_MINUTES && ms < TEN_MINUTES) return '3. 5-10 minutes';
if (ms >= TEN_MINUTES && ms < THIRTY_MINUTES) return '4. 10-30 minutes';
if (ms >= THIRTY_MINUTES && ms < ONE_HOUR) return '5. 30-60 minutes';
if (ms >= ONE_HOUR && ms < TWO_HOURS) return '6. 1-2 hours';
if (ms >= TWO_HOURS && ms < EIGHT_HOURS) return '7. 2-8 hours';
if (ms >= EIGHT_HOURS && ms < TWELVE_HOURS) return '8. 8-12 hours';
if (ms >= TWELVE_HOURS && ms < ONE_DAY) return '9. 12-24 hours';
if (ms >= ONE_DAY && ms < TWO_DAYS) return '10. 1-2 days';
if (ms >= TWO_DAYS && ms < SEVEN_DAYS) return '11. 2-7 days';
if (ms >= SEVEN_DAYS && ms < FOURTEEN_DAYS) return '12. 7-14 days';
if (ms >= FOURTEEN_DAYS && ms < THIRTY_DAYS) return '13. 14-30 days';
if (ms >= THIRTY_DAYS && ms < SIXTY_DAYS) return '14. 30-60 days';
if (ms >= SIXTY_DAYS && ms < NINETY_DAYS) return '15. 60-90 days';
if (ms >= NINETY_DAYS && ms < HALF_YEAR) return '16. 90-180 days';
if (ms >= HALF_YEAR && ms < ONE_YEAR) return '17. 180-365 days';
return '18. More than 1 year';
};

View file

@ -1,53 +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 React from 'react';
import { EuiToolTip } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui';
const cloudIcons: Record<string, string> = {
gcp: 'logoGCP',
aws: 'logoAWS',
azure: 'logoAzure',
unknownProvider: 'cloudSunny',
};
export const CloudProviderIconWithTitle = ({
provider,
title,
text,
}: {
provider?: string | null;
title?: React.ReactNode;
text: string;
}) => {
return (
<EuiFlexGroup
alignItems="center"
className="eui-textTruncate"
gutterSize="s"
responsive={false}
>
<EuiFlexItem grow={false}>
<EuiToolTip delay="long" content={provider ?? 'Unknown'}>
<EuiIcon
type={(provider && cloudIcons[provider]) || cloudIcons.unknownProvider}
size="m"
title={text}
/>
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem grow={false} className="eui-textTruncate">
{title ?? (
<EuiText size="relative" className="eui-textTruncate">
{text}
</EuiText>
)}
</EuiFlexItem>
</EuiFlexGroup>
);
};

View file

@ -5,11 +5,10 @@
* 2.0.
*/
import React, { useCallback, useEffect, useMemo } from 'react';
import React, { useCallback, useEffect } from 'react';
import { EuiInMemoryTable } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { isEqual } from 'lodash';
import { buildHostsTableColumns } from './hosts_table_columns';
import { NoData } from '../../../../components/empty_states';
import { InfraLoadingPanel } from '../../../../components/loading';
import { useHostsTable } from '../hooks/use_hosts_table';
@ -41,6 +40,8 @@ export const HostsTable = () => {
metrics: HOST_TABLE_METRICS,
});
const { columns, items } = useHostsTable(nodes, { time: unifiedSearchDateRange });
useEffect(() => {
if (hostViewState.loading !== loading || nodes.length !== hostViewState.totalHits) {
setHostViewState({
@ -58,7 +59,6 @@ export const HostsTable = () => {
setHostViewState,
]);
const items = useHostsTable(nodes);
const noData = items.length === 0;
const onTableChange = useCallback(
@ -79,11 +79,6 @@ export const HostsTable = () => {
[setProperties, properties.pagination, properties.sorting]
);
const hostsTableColumns = useMemo(
() => buildHostsTableColumns({ time: unifiedSearchDateRange }),
[unifiedSearchDateRange]
);
if (loading) {
return (
<InfraLoadingPanel
@ -125,7 +120,7 @@ export const HostsTable = () => {
'data-test-subj': 'hostsView-tableRow',
}}
items={items}
columns={hostsTableColumns}
columns={columns}
onTableChange={onTableChange}
/>
);

View file

@ -1,139 +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 { EuiBasicTableColumn } from '@elastic/eui';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiText } from '@elastic/eui';
import { encode } from '@kbn/rison';
import { TimeRange } from '@kbn/es-query';
import type { SnapshotMetricInput, SnapshotNodeMetric } from '../../../../../common/http_api';
import { createInventoryMetricFormatter } from '../../inventory_view/lib/create_inventory_metric_formatter';
import { CloudProviderIconWithTitle } from './cloud_provider_icon_with_title';
import { TruncateLinkWithTooltip } from './truncate_link_with_tooltip';
interface HostNodeRow extends HostMetrics {
os?: string | null;
servicesOnHost?: number | null;
title: { name: string; cloudProvider?: string | null };
name: string;
}
export interface HostMetrics {
cpuCores: SnapshotNodeMetric;
diskLatency: SnapshotNodeMetric;
rx: SnapshotNodeMetric;
tx: SnapshotNodeMetric;
memory: SnapshotNodeMetric;
memoryTotal: SnapshotNodeMetric;
}
const formatMetric = (type: SnapshotMetricInput['type'], value: number | undefined | null) =>
value || value === 0 ? createInventoryMetricFormatter({ type })(value) : 'N/A';
interface HostBuilderParams {
time: TimeRange;
}
export const buildHostsTableColumns = ({
time,
}: HostBuilderParams): Array<EuiBasicTableColumn<HostNodeRow>> => {
const hostLinkSearch = {
_a: encode({ time: { ...time, interval: '>=1m' } }),
};
return [
{
name: i18n.translate('xpack.infra.hostsViewPage.table.nameColumnHeader', {
defaultMessage: 'Name',
}),
field: 'title',
sortable: true,
truncateText: true,
render: (title: HostNodeRow['title']) => (
<CloudProviderIconWithTitle
provider={title?.cloudProvider}
text={title.name}
title={
<TruncateLinkWithTooltip
text={title.name}
linkProps={{
app: 'metrics',
pathname: `/detail/host/${title.name}`,
search: hostLinkSearch,
}}
/>
}
/>
),
},
{
name: i18n.translate('xpack.infra.hostsViewPage.table.operatingSystemColumnHeader', {
defaultMessage: 'Operating System',
}),
field: 'os',
sortable: true,
render: (os: string) => <EuiText size="s">{os}</EuiText>,
},
{
name: i18n.translate('xpack.infra.hostsViewPage.table.numberOfCpusColumnHeader', {
defaultMessage: '# of CPUs',
}),
field: 'cpuCores',
sortable: true,
render: (cpuCores: SnapshotNodeMetric) => (
<>{formatMetric('cpuCores', cpuCores?.value ?? cpuCores?.max)}</>
),
align: 'right',
},
{
name: i18n.translate('xpack.infra.hostsViewPage.table.diskLatencyColumnHeader', {
defaultMessage: 'Disk Latency (avg.)',
}),
field: 'diskLatency.avg',
sortable: true,
render: (avg: number) => <>{formatMetric('diskLatency', avg)}</>,
align: 'right',
},
{
name: i18n.translate('xpack.infra.hostsViewPage.table.averageTxColumnHeader', {
defaultMessage: 'TX (avg.)',
}),
field: 'tx.avg',
sortable: true,
render: (avg: number) => <>{formatMetric('tx', avg)}</>,
align: 'right',
},
{
name: i18n.translate('xpack.infra.hostsViewPage.table.averageRxColumnHeader', {
defaultMessage: 'RX (avg.)',
}),
field: 'rx.avg',
sortable: true,
render: (avg: number) => <>{formatMetric('rx', avg)}</>,
align: 'right',
},
{
name: i18n.translate('xpack.infra.hostsViewPage.table.averageMemoryTotalColumnHeader', {
defaultMessage: 'Memory total (avg.)',
}),
field: 'memoryTotal.avg',
sortable: true,
render: (avg: number) => <>{formatMetric('memoryTotal', avg)}</>,
align: 'right',
},
{
name: i18n.translate('xpack.infra.hostsViewPage.table.averageMemoryUsageColumnHeader', {
defaultMessage: 'Memory usage (avg.)',
}),
field: 'memory.avg',
sortable: true,
render: (avg: number) => <>{formatMetric('memory', avg)}</>,
align: 'right',
},
];
};

View file

@ -0,0 +1,62 @@
/*
* 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 { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, EuiToolTip, IconType } from '@elastic/eui';
import { TimeRange } from '@kbn/es-query';
import { useLinkProps } from '@kbn/observability-plugin/public';
import { encode } from '@kbn/rison';
import type { CloudProvider, HostNodeRow } from '../hooks/use_hosts_table';
const cloudIcons: Record<CloudProvider, IconType> = {
gcp: 'logoGCP',
aws: 'logoAWS',
azure: 'logoAzure',
unknownProvider: 'cloudSunny',
};
interface HostsTableEntryTitleProps {
onClick: () => void;
time: TimeRange;
title: HostNodeRow['title'];
}
export const HostsTableEntryTitle = ({ onClick, time, title }: HostsTableEntryTitleProps) => {
const { name, cloudProvider } = title;
const link = useLinkProps({
app: 'metrics',
pathname: `/detail/host/${name}`,
search: {
_a: encode({ time: { ...time, interval: '>=1m' } }),
},
});
const iconType = (cloudProvider && cloudIcons[cloudProvider]) || cloudIcons.unknownProvider;
const providerName = cloudProvider ?? 'Unknown';
return (
<EuiFlexGroup
alignItems="center"
className="eui-textTruncate"
gutterSize="s"
responsive={false}
>
<EuiFlexItem grow={false}>
<EuiToolTip delay="long" content={providerName}>
<EuiIcon type={iconType} size="m" title={name} />
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem grow={false} className="eui-textTruncate" onClick={onClick}>
<EuiToolTip delay="long" content={name}>
<EuiLink className="eui-displayBlock eui-textTruncate" {...link}>
{name}
</EuiLink>
</EuiToolTip>
</EuiFlexItem>
</EuiFlexGroup>
);
};

View file

@ -120,6 +120,6 @@ export const KPIChart = ({
const KPIChartStyled = styled(Chart)`
.echMetric {
border-radius: 5px;
border-radius: ${(p) => p.theme.eui.euiBorderRadius};
}
`;

View file

@ -1,36 +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 { EuiToolTip, EuiLink } from '@elastic/eui';
import React from 'react';
import { LinkDescriptor, useLinkProps } from '@kbn/observability-plugin/public';
interface Props {
text: string;
linkProps: LinkDescriptor;
}
export function TruncateLinkWithTooltip(props: Props) {
const { text, linkProps } = props;
const link = useLinkProps(linkProps);
return (
<div className="eui-displayBlock eui-fullWidth">
<EuiToolTip
className="eui-fullWidth"
delay="long"
content={text}
anchorClassName="eui-fullWidth"
>
<EuiLink className="eui-displayBlock eui-textTruncate" {...link}>
{text}
</EuiLink>
</EuiToolTip>
</div>
);
}

View file

@ -128,8 +128,10 @@ describe('useHostTable hook', () => {
},
},
];
const result = renderHook(() => useHostsTable(nodes));
const time = { from: 'now-15m', to: 'now', interval: '>=1m' };
expect(result.result.current).toStrictEqual(items);
const { result } = renderHook(() => useHostsTable(nodes, { time }));
expect(result.current.items).toStrictEqual(items);
});
});

View file

@ -1,31 +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 { useMemo } from 'react';
import type { SnapshotNode, SnapshotNodeMetric } from '../../../../../common/http_api';
import { HostMetrics } from '../components/hosts_table_columns';
type MappedMetrics = Record<keyof HostMetrics, SnapshotNodeMetric>;
export const useHostsTable = (nodes: SnapshotNode[]) => {
const items = useMemo(() => {
return nodes.map(({ metrics, path, name }) => ({
name,
os: path.at(-1)?.os ?? '-',
title: {
name,
cloudProvider: path.at(-1)?.cloudProvider ?? null,
},
...metrics.reduce((data, metric) => {
data[metric.name as keyof HostMetrics] = metric;
return data;
}, {} as MappedMetrics),
}));
}, [nodes]);
return items;
};

View file

@ -0,0 +1,196 @@
/*
* 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, { useCallback, useMemo } from 'react';
import { EuiBasicTableColumn, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { TimeRange } from '@kbn/es-query';
import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana';
import { createInventoryMetricFormatter } from '../../inventory_view/lib/create_inventory_metric_formatter';
import { HostsTableEntryTitle } from '../components/hosts_table_entry_title';
import type {
SnapshotNode,
SnapshotNodeMetric,
SnapshotMetricInput,
} from '../../../../../common/http_api';
/**
* Columns and items types
*/
export type CloudProvider = 'gcp' | 'aws' | 'azure' | 'unknownProvider';
type HostMetric = 'cpuCores' | 'diskLatency' | 'rx' | 'tx' | 'memory' | 'memoryTotal';
type HostMetrics = Record<HostMetric, SnapshotNodeMetric>;
export interface HostNodeRow extends HostMetrics {
os?: string | null;
servicesOnHost?: number | null;
title: { name: string; cloudProvider?: CloudProvider | null };
name: string;
}
// type MappedMetrics = Record<keyof HostNodeRow, SnapshotNodeMetric>;
interface HostTableParams {
time: TimeRange;
}
/**
* Helper functions
*/
const formatMetric = (type: SnapshotMetricInput['type'], value: number | undefined | null) => {
return value || value === 0 ? createInventoryMetricFormatter({ type })(value) : 'N/A';
};
const buildItemsList = (nodes: SnapshotNode[]) => {
return nodes.map(({ metrics, path, name }) => ({
name,
os: path.at(-1)?.os ?? '-',
title: {
name,
cloudProvider: path.at(-1)?.cloudProvider ?? null,
},
...metrics.reduce((data, metric) => {
data[metric.name as HostMetric] = metric;
return data;
}, {} as HostMetrics),
})) as HostNodeRow[];
};
/**
* Columns translations
*/
const titleLabel = i18n.translate('xpack.infra.hostsViewPage.table.nameColumnHeader', {
defaultMessage: 'Name',
});
const osLabel = i18n.translate('xpack.infra.hostsViewPage.table.operatingSystemColumnHeader', {
defaultMessage: 'Operating System',
});
const cpuCountLabel = i18n.translate('xpack.infra.hostsViewPage.table.numberOfCpusColumnHeader', {
defaultMessage: '# of CPUs',
});
const diskLatencyLabel = i18n.translate('xpack.infra.hostsViewPage.table.diskLatencyColumnHeader', {
defaultMessage: 'Disk Latency (avg.)',
});
const averageTXLabel = i18n.translate('xpack.infra.hostsViewPage.table.averageTxColumnHeader', {
defaultMessage: 'TX (avg.)',
});
const averageRXLabel = i18n.translate('xpack.infra.hostsViewPage.table.averageRxColumnHeader', {
defaultMessage: 'RX (avg.)',
});
const averageTotalMemoryLabel = i18n.translate(
'xpack.infra.hostsViewPage.table.averageMemoryTotalColumnHeader',
{
defaultMessage: 'Memory total (avg.)',
}
);
const averageMemoryUsageLabel = i18n.translate(
'xpack.infra.hostsViewPage.table.averageMemoryUsageColumnHeader',
{
defaultMessage: 'Memory usage (avg.)',
}
);
/**
* Build a table columns and items starting from the snapshot nodes.
*/
export const useHostsTable = (nodes: SnapshotNode[], { time }: HostTableParams) => {
const {
services: { telemetry },
} = useKibanaContextForPlugin();
const reportHostEntryClick = useCallback(
({ name, cloudProvider }: HostNodeRow['title']) => {
telemetry.reportHostEntryClicked({
hostname: name,
cloud_provider: cloudProvider,
});
},
[telemetry]
);
const items = useMemo(() => buildItemsList(nodes), [nodes]);
const columns: Array<EuiBasicTableColumn<HostNodeRow>> = useMemo(
() => [
{
name: titleLabel,
field: 'title',
sortable: true,
truncateText: true,
render: (title: HostNodeRow['title']) => (
<HostsTableEntryTitle
title={title}
time={time}
onClick={() => reportHostEntryClick(title)}
/>
),
},
{
name: osLabel,
field: 'os',
sortable: true,
render: (os: string) => <EuiText size="s">{os}</EuiText>,
},
{
name: cpuCountLabel,
field: 'cpuCores',
sortable: true,
render: (cpuCores: SnapshotNodeMetric) =>
formatMetric('cpuCores', cpuCores?.value ?? cpuCores?.max),
align: 'right',
},
{
name: diskLatencyLabel,
field: 'diskLatency.avg',
sortable: true,
render: (avg: number) => formatMetric('diskLatency', avg),
align: 'right',
},
{
name: averageTXLabel,
field: 'tx.avg',
sortable: true,
render: (avg: number) => formatMetric('tx', avg),
align: 'right',
},
{
name: averageRXLabel,
field: 'rx.avg',
sortable: true,
render: (avg: number) => formatMetric('rx', avg),
align: 'right',
},
{
name: averageTotalMemoryLabel,
field: 'memoryTotal.avg',
sortable: true,
render: (avg: number) => formatMetric('memoryTotal', avg),
align: 'right',
},
{
name: averageMemoryUsageLabel,
field: 'memory.avg',
sortable: true,
render: (avg: number) => formatMetric('memory', avg),
align: 'right',
},
],
[reportHostEntryClick, time]
);
return { columns, items };
};

View file

@ -11,10 +11,22 @@ import { buildEsQuery, Filter, Query, TimeRange } from '@kbn/es-query';
import type { SavedQuery } from '@kbn/data-plugin/public';
import { debounce } from 'lodash';
import deepEqual from 'fast-deep-equal';
import { telemetryTimeRangeFormatter } from '../../../../../common/formatters/telemetry_time_range';
import type { InfraClientStartDeps } from '../../../../types';
import { useMetricsDataViewContext } from './use_data_view';
import { useSyncKibanaTimeFilterTime } from '../../../../hooks/use_kibana_timefilter_time';
import { useHostsUrlState, INITIAL_DATE_RANGE } from './use_unified_search_url_state';
import { useHostsUrlState, INITIAL_DATE_RANGE, HostsState } from './use_unified_search_url_state';
const buildQuerySubmittedPayload = (hostState: HostsState) => {
const { panelFilters, filters, dateRangeTimestamp, query: queryObj } = hostState;
return {
control_filters: panelFilters.map((filter) => JSON.stringify(filter)),
filters: filters.map((filter) => JSON.stringify(filter)),
interval: telemetryTimeRangeFormatter(dateRangeTimestamp.to - dateRangeTimestamp.from),
query: queryObj.query,
};
};
export const useUnifiedSearch = () => {
const { state, dispatch, getTime, getDateRangeAsTimestamp } = useHostsUrlState();
@ -22,6 +34,7 @@ export const useUnifiedSearch = () => {
const { services } = useKibana<InfraClientStartDeps>();
const {
data: { query: queryManager },
telemetry,
} = services;
useSyncKibanaTimeFilterTime(INITIAL_DATE_RANGE, {
@ -62,6 +75,11 @@ export const useUnifiedSearch = () => {
};
});
// Track telemetry event on query/filter/date changes
useEffect(() => {
telemetry.reportHostsViewQuerySubmitted(buildQuerySubmittedPayload(state));
}, [state, telemetry]);
const onSubmit = useCallback(
(data?: {
query?: Query;

View file

@ -33,12 +33,12 @@ export interface HostsViewQuerySubmittedSchema {
export interface HostEntryClickedParams {
hostname: string;
cloud_provider?: string;
cloud_provider?: string | null;
}
export interface HostEntryClickedSchema {
hostname: SchemaValue<string>;
cloud_provider: SchemaValue<string | undefined>;
cloud_provider: SchemaValue<string | undefined | null>;
}
export interface ITelemetryClient {

View file

@ -80,6 +80,7 @@ export interface InfraClientStartDeps {
share: SharePluginStart;
storage: IStorageWrapper;
lens: LensPublicStart;
telemetry: ITelemetryClient;
}
export type InfraClientCoreSetup = CoreSetup<InfraClientStartDeps, InfraClientStartExports>;