[Stack Monitoring] Migrate logs-related components to TypeScript (#203536)

## Summary
A recent [bug](https://github.com/elastic/kibana/issues/199902) that
affected some of the pages in Stack Monitoring was caused by changes
related to the locators of the logs-related apps.

The issue wasn't caught by type checks as the affected area in the
monitoring plugin was written in JavaScript.

The goal of this PR is to migrate the logs-related components to
TypeScript.

### Testing
The stateful environment deployed by this PR includes logs and metrics
for stack monitoring. Please make sure to select a larger time range
(e.g. last 14 days).
This commit is contained in:
Giorgos Bamparopoulos 2024-12-13 08:53:25 +00:00 committed by GitHub
parent e061b4c352
commit 46a1535f03
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 71 additions and 29 deletions

View file

@ -14,6 +14,7 @@ export interface ExternalConfig {
renderReactApp: boolean;
staleStatusThresholdSeconds: number;
isCcsEnabled: boolean;
logsIndices: string;
}
export const ExternalConfigContext = createContext({} as ExternalConfig);

View file

@ -29,7 +29,7 @@ const sharePlugin = {
},
},
},
};
} as unknown as ReturnType<typeof sharePluginMock.createStartContract>;
const logs = {
enabled: true,

View file

@ -8,16 +8,42 @@
import React, { PureComponent, useContext } from 'react';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import { upperFirst } from 'lodash';
import { Legacy } from '../../legacy_shims';
import { EuiBasicTable, EuiTitle, EuiSpacer, EuiText, EuiCallOut, EuiLink } from '@elastic/eui';
import { formatDateTimeLocal } from '../../../common/formatting';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { Reason } from './reason';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { SharePluginStart } from '@kbn/share-plugin/public';
import { Reason, type IReason } from './reason';
import { formatDateTimeLocal } from '../../../common/formatting';
import { Legacy } from '../../legacy_shims';
import { ExternalConfigContext } from '../../application/contexts/external_config_context';
import { MonitoringStartServices } from '../../types';
const getFormattedDateTimeLocal = (timestamp) => {
interface LogsProps {
logs: {
logs?: Array<{
timestamp: string;
component: string;
level: string;
type: string;
node: string;
message: string;
}>;
enabled: boolean;
limit: number;
reason?: IReason;
};
nodeId?: string;
indexUuid?: string;
clusterUuid?: string;
}
interface LogsContentProps extends LogsProps {
sharePlugin: SharePluginStart;
logsIndices: string;
}
const getFormattedDateTimeLocal = (timestamp: number | Date) => {
const timezone = Legacy.shims.uiSettings?.get('dateFormat:tz');
return formatDateTimeLocal(timestamp, timezone);
};
@ -51,7 +77,7 @@ const columns = [
field: 'timestamp',
name: columnTimestampTitle,
width: '12%',
render: (timestamp) => getFormattedDateTimeLocal(timestamp),
render: (timestamp: number | Date) => getFormattedDateTimeLocal(timestamp),
},
{
field: 'level',
@ -62,7 +88,7 @@ const columns = [
field: 'type',
name: columnTypeTitle,
width: '10%',
render: (type) => upperFirst(type),
render: (type: string) => upperFirst(type),
},
{
field: 'message',
@ -81,7 +107,7 @@ const clusterColumns = [
field: 'timestamp',
name: columnTimestampTitle,
width: '12%',
render: (timestamp) => getFormattedDateTimeLocal(timestamp),
render: (timestamp: number | Date) => getFormattedDateTimeLocal(timestamp),
},
{
field: 'level',
@ -92,7 +118,7 @@ const clusterColumns = [
field: 'type',
name: columnTypeTitle,
width: '10%',
render: (type) => upperFirst(type),
render: (type: string) => upperFirst(type),
},
{
field: 'message',
@ -111,7 +137,13 @@ const clusterColumns = [
},
];
function getDiscoverLink(clusterUuid, nodeId, indexUuid, sharePlugin, logsIndices) {
function getDiscoverLink(
clusterUuid?: string,
nodeId?: string,
indexUuid?: string,
sharePlugin?: SharePluginStart,
logsIndices?: string
) {
const params = [];
if (clusterUuid) {
params.push(`elasticsearch.cluster.uuid:${clusterUuid}`);
@ -124,13 +156,9 @@ function getDiscoverLink(clusterUuid, nodeId, indexUuid, sharePlugin, logsIndice
}
const filter = params.join(' and ');
const discoverLocator = sharePlugin.url.locators.get('DISCOVER_APP_LOCATOR');
const discoverLocator = sharePlugin?.url.locators.get('DISCOVER_APP_LOCATOR');
if (!discoverLocator) {
return;
}
const base = discoverLocator.getRedirectUrl({
const base = discoverLocator?.getRedirectUrl({
dataViewSpec: {
id: logsIndices,
title: logsIndices,
@ -144,14 +172,15 @@ function getDiscoverLink(clusterUuid, nodeId, indexUuid, sharePlugin, logsIndice
return base;
}
export const Logs = (props) => {
export const Logs = (props: LogsProps) => {
const {
services: { share },
} = useKibana();
} = useKibana<MonitoringStartServices>();
const externalConfig = useContext(ExternalConfigContext);
return <LogsContent sharePlugin={share} logsIndices={externalConfig.logsIndices} {...props} />;
};
export class LogsContent extends PureComponent {
export class LogsContent extends PureComponent<LogsContentProps> {
renderLogs() {
const {
logs: { enabled, logs },

View file

@ -12,7 +12,19 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { Legacy } from '../../legacy_shims';
import { Monospace } from '../metricbeat_migration/instruction_steps/components/monospace/monospace';
export const Reason = ({ reason }) => {
export interface IReason {
indexPatternExists?: boolean;
indexPatternInTimeRangeExists?: boolean;
typeExists?: boolean;
typeExistsAtAnyTime?: boolean;
usingStructuredLogs?: boolean;
clusterExists?: boolean;
nodeExists?: boolean | null;
indexExists?: boolean;
correctIndexName?: boolean;
}
export const Reason = ({ reason }: { reason?: IReason }) => {
const filebeatUrl = Legacy.shims.docLinks.links.filebeat.installation;
const elasticsearchUrl = Legacy.shims.docLinks.links.filebeat.elasticsearchModule;
const troubleshootUrl = Legacy.shims.docLinks.links.monitoring.troubleshootKibana;
@ -36,7 +48,7 @@ export const Reason = ({ reason }) => {
/>
);
if (false === reason.indexPatternExists) {
if (false === reason?.indexPatternExists) {
title = i18n.translate('xpack.monitoring.logs.reason.noIndexPatternTitle', {
defaultMessage: 'No log data found',
});
@ -56,8 +68,8 @@ export const Reason = ({ reason }) => {
/>
);
} else if (
false === reason.indexPatternInTimeRangeExists ||
(false === reason.typeExists && reason.typeExistsAtAnyTime)
false === reason?.indexPatternInTimeRangeExists ||
(false === reason?.typeExists && reason.typeExistsAtAnyTime)
) {
title = i18n.translate('xpack.monitoring.logs.reason.noIndexPatternInTimePeriodTitle', {
defaultMessage: 'No logs for the selected time',
@ -68,7 +80,7 @@ export const Reason = ({ reason }) => {
defaultMessage="Use the time filter to adjust your timeframe."
/>
);
} else if (false === reason.typeExists) {
} else if (false === reason?.typeExists) {
title = i18n.translate('xpack.monitoring.logs.reason.noTypeTitle', {
defaultMessage: 'No logs for Elasticsearch',
});
@ -87,7 +99,7 @@ export const Reason = ({ reason }) => {
}}
/>
);
} else if (false === reason.usingStructuredLogs) {
} else if (false === reason?.usingStructuredLogs) {
title = i18n.translate('xpack.monitoring.logs.reason.notUsingStructuredLogsTitle', {
defaultMessage: 'No structured logs found',
});
@ -107,7 +119,7 @@ export const Reason = ({ reason }) => {
}}
/>
);
} else if (false === reason.clusterExists) {
} else if (false === reason?.clusterExists) {
title = i18n.translate('xpack.monitoring.logs.reason.noClusterTitle', {
defaultMessage: 'No logs for this cluster',
});
@ -126,7 +138,7 @@ export const Reason = ({ reason }) => {
}}
/>
);
} else if (false === reason.nodeExists) {
} else if (false === reason?.nodeExists) {
title = i18n.translate('xpack.monitoring.logs.reason.noNodeTitle', {
defaultMessage: 'No logs for this Elasticsearch node',
});
@ -145,7 +157,7 @@ export const Reason = ({ reason }) => {
}}
/>
);
} else if (false === reason.indexExists) {
} else if (false === reason?.indexExists) {
title = i18n.translate('xpack.monitoring.logs.reason.noIndexTitle', {
defaultMessage: 'No logs for this index',
});
@ -164,7 +176,7 @@ export const Reason = ({ reason }) => {
}}
/>
);
} else if (false === reason.correctIndexName) {
} else if (false === reason?.correctIndexName) {
title = i18n.translate('xpack.monitoring.logs.reason.correctIndexNameTitle', {
defaultMessage: 'Corrupted filebeat index',
});