Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ src/platform/packages/shared/kbn-discover-utils @elastic/kibana-data-discovery
src/platform/packages/shared/kbn-doc-links @elastic/docs
src/platform/packages/shared/kbn-dom-drag-drop @elastic/kibana-visualizations @elastic/kibana-data-discovery
src/platform/packages/shared/kbn-ebt-tools @elastic/kibana-core
src/platform/packages/shared/kbn-elapsed-datetime-formatter @elastic/obs-ux-management-team
src/platform/packages/shared/kbn-elastic-agent-utils @elastic/obs-ux-logs-team
src/platform/packages/shared/kbn-encrypted-saved-objects-shared @elastic/kibana-security
src/platform/packages/shared/kbn-es @elastic/kibana-operations
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@
"@kbn/ebt-tools": "link:src/platform/packages/shared/kbn-ebt-tools",
"@kbn/ecs-data-quality-dashboard": "link:x-pack/solutions/security/packages/ecs-data-quality-dashboard",
"@kbn/ecs-data-quality-dashboard-plugin": "link:x-pack/solutions/security/plugins/ecs_data_quality_dashboard",
"@kbn/elapsed-datetime-formatter": "link:src/platform/packages/shared/kbn-elapsed-datetime-formatter",
"@kbn/elastic-agent-utils": "link:src/platform/packages/shared/kbn-elastic-agent-utils",
"@kbn/elastic-assistant": "link:x-pack/platform/packages/shared/kbn-elastic-assistant",
"@kbn/elastic-assistant-common": "link:x-pack/platform/packages/shared/kbn-elastic-assistant-common",
Expand Down
6 changes: 6 additions & 0 deletions packages/kbn-relative-time-display/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@kbn/relative-time-display",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")

SRCS = glob(
[
"**/*.ts",
"**/*.tsx",
],
exclude = [
"**/*.config.js",
"**/*.mock.*",
"**/*.test.*",
"**/*.stories.*",
"**/__snapshots__/**",
"**/integration_tests/**",
"**/mocks/**",
"**/scripts/**",
"**/storybook/**",
"**/test_fixtures/**",
"**/test_helpers/**",
],
)

SHARED_DEPS = [
"@npm//moment",
"//src/platform/packages/shared/kbn-i18n"
]

js_library(
name = "kbn-elapsed-datetime-formatter",
package_name = "@kbn/elapsed-datetime-formatter",
srcs = ["package.json"] + SRCS,
deps = SHARED_DEPS,
visibility = ["//visibility:public"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# @kbn/elapsed-datetime-formatter

This package is intended to show the elapsed time from a provided date in days or hours and minutes. It also provides the ability to show a tooltip with the original timestamp in a readable format.

## Examples

An alert was created or triggered 2 minutes before the current time, but only has a timestamp. Use this package to show the elapsed time of `2 minutes ago` in human-readable, translatable text.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

export * from './src/elapsed_time';
export * from './src/datetime';
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

module.exports = {
preset: '@kbn/test',
rootDir: '../../../../..',
roots: ['<rootDir>/src/platform/packages/shared/kbn-elapsed-datetime-formatter'],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "shared-common",
"id": "@kbn/elapsed-datetime-formatter",
"owner": "@elastic/obs-ux-management-team",
"group": "platform",
"visibility": "shared"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@kbn/elapsed-datetime-formatter",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0"
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/*
* 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.
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import moment from 'moment-timezone';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/*
* 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.
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import moment from 'moment-timezone';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import moment from 'moment';
import { getElapsedTimeText } from './elapsed_time';

jest.mock('@kbn/i18n', () => ({
i18n: {
translate: (_id: string, { defaultMessage, values }: any) => {
if (values) {
return defaultMessage
.replace(
'{day, plural, one {# day} other {# days}}',
`${values.day} day${values.day === 1 ? '' : 's'}`
)
.replace(
'{hour, plural, one {# hour} other {# hours}}',
`${values.hour} hour${values.hour === 1 ? '' : 's'}`
)
.replace(
'{minute, plural, one {# minute} other {# minutes}}',
`${values.minute} minute${values.minute === 1 ? '' : 's'}`
)
.replace(
'{hour, plural, one {# hour} other {# hours}}, {minute, plural, one {# minute} other {# minutes}}',
`${values.hour} hour${values.hour === 1 ? '' : 's'}, ${values.minute} minute${
values.minute === 1 ? '' : 's'
}`
);
}
return defaultMessage;
},
},
}));

describe('getElapsedTimeText', () => {
it('returns days if duration has days', () => {
const duration = moment.duration({ days: 2, hours: 0, minutes: 0 });
expect(getElapsedTimeText(duration)).toContain('2 days');
});

it('returns hours if duration has hours and no minutes', () => {
const duration = moment.duration({ days: 0, hours: 3, minutes: 0 });
expect(getElapsedTimeText(duration)).toContain('3 hours');
});

it('returns hours and minutes if duration has both', () => {
const duration = moment.duration({ days: 0, hours: 1, minutes: 15 });
expect(getElapsedTimeText(duration)).toContain('1 hour, 15 minutes');
});

it('returns minutes if duration has only minutes', () => {
const duration = moment.duration({ days: 0, hours: 0, minutes: 5 });
expect(getElapsedTimeText(duration)).toContain('5 minutes');
});

it('returns "a few seconds ago" if duration is less than a minute', () => {
const duration = moment.duration({ days: 0, hours: 0, minutes: 0, seconds: 30 });
expect(getElapsedTimeText(duration)).toBe('a few seconds ago');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiToolTip } from '@elastic/eui';
import moment from 'moment';
import { asAbsoluteDateTime, TimeUnit } from './datetime';

interface Props {
/**
* timestamp in milliseconds
*/
time: number;
/**
* Threshold in hours to switch from relative to absolute time display
* If not provided, it defaults to 2 hours
*/
elapsedDisplayThreshold?: number;
timeUnit?: TimeUnit;
}

export function getElapsedTimeText(duration: moment.Duration) {
const [day, hour, minute] = [duration.days(), duration.hours(), duration.minutes()];
// keeping days for flexibility, but it is not used in the current implementation. If day is detected, it will be displayed without hours or minutes
if (day > 0) {
return i18n.translate('xpack.observability.alertsTable.highFidelityDurationWithDays', {
defaultMessage: '{day, plural, one {# day} other {# days}} ago',
values: { day },
});
}
if (hour > 0 && minute === 0) {
return i18n.translate('xpack.observability.alertsTable.highFidelityDurationWithHours', {
defaultMessage: '{hour, plural, one {# hour} other {# hours}} ago',
values: { hour },
});
}
if (hour > 0) {
return i18n.translate(
'xpack.observability.alertsTable.highFidelityDurationWithHoursAndMinutes',
{
defaultMessage:
'{hour, plural, one {# hour} other {# hours}}, {minute, plural, one {# minute} other {# minutes}} ago',
values: { hour, minute },
}
);
}
if (minute > 0) {
return i18n.translate('xpack.observability.alertsTable.highFidelityDuration', {
defaultMessage: '{minute, plural, one {# minute} other {# minutes}} ago',
values: { minute },
});
}
return i18n.translate('xpack.observability.alertsTable.highFidelityDurationRecently', {
defaultMessage: 'a few seconds ago',
});
}

export function ElapsedTimestampTooltip({
time,
elapsedDisplayThreshold = 24,
timeUnit = 'milliseconds',
}: Props): JSX.Element {
const duration = moment.duration(new Date().getTime() - time);
const absoluteTimeLabel = asAbsoluteDateTime(time, timeUnit);

const timeDisplay =
duration.asHours() > elapsedDisplayThreshold ? absoluteTimeLabel : getElapsedTimeText(duration);

return (
<EuiToolTip content={absoluteTimeLabel}>
<>{timeDisplay}</>
</EuiToolTip>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"extends": "../../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node",
"react"
]
},
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"target/**/*"
],
"kbn_references": ["@kbn/i18n"]
}
2 changes: 2 additions & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,8 @@
"@kbn/ecs-data-quality-dashboard/*": ["x-pack/solutions/security/packages/ecs-data-quality-dashboard/*"],
"@kbn/ecs-data-quality-dashboard-plugin": ["x-pack/solutions/security/plugins/ecs_data_quality_dashboard"],
"@kbn/ecs-data-quality-dashboard-plugin/*": ["x-pack/solutions/security/plugins/ecs_data_quality_dashboard/*"],
"@kbn/elapsed-datetime-formatter": ["src/platform/packages/shared/kbn-elapsed-datetime-formatter"],
"@kbn/elapsed-datetime-formatter/*": ["src/platform/packages/shared/kbn-elapsed-datetime-formatter/*"],
"@kbn/elastic-agent-utils": ["src/platform/packages/shared/kbn-elastic-agent-utils"],
"@kbn/elastic-agent-utils/*": ["src/platform/packages/shared/kbn-elastic-agent-utils/*"],
"@kbn/elastic-assistant": ["x-pack/platform/packages/shared/kbn-elastic-assistant"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export {
getDurationFormatter,
asDuration,
asDynamicBytes,
asAbsoluteDateTime,
asInteger,
} from './utils/formatters';
export { getInspectResponse } from './utils/get_inspect_response';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
*/

import { i18n } from '@kbn/i18n';
import { TimeUnit } from '@kbn/elapsed-datetime-formatter';
import moment from 'moment';
import { memoize } from 'lodash';
import { NOT_AVAILABLE_LABEL } from '../../i18n';
import { asDecimalOrInteger, asInteger, asDecimal } from './formatters';
import { TimeUnit } from './datetime';
import { Maybe } from '../../typings';
import { isFiniteNumber } from '../is_finite_number';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@
*/

export * from './formatters';
export * from './datetime';
export * from './duration';
export * from './size';
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
import { isEmpty } from 'lodash';
import type { Alert } from '@kbn/alerting-types';
import type { JsonValue } from '@kbn/utility-types';
import { ElapsedTimestampTooltip } from '@kbn/elapsed-datetime-formatter';
import {
RELATED_ACTIONS_COL,
RELATED_ALERT_REASON,
Expand Down Expand Up @@ -93,7 +94,11 @@ export const AlertsTableCellValue: GetObservabilityAlertsTableProp<'renderCellVa
<TimestampTooltip time={new Date(value ?? '').getTime()} timeUnit="milliseconds" />
),
[ALERT_START]: (value) => (
<TimestampTooltip time={new Date(value ?? '').getTime()} timeUnit="milliseconds" />
<ElapsedTimestampTooltip
time={new Date(value ?? '').getTime()}
timeUnit="milliseconds"
elapsedDisplayThreshold={2}
/>
),
[ALERT_RULE_EXECUTION_TIMESTAMP]: (value) => (
<TimestampTooltip time={new Date(value ?? '').getTime()} timeUnit="milliseconds" />
Expand Down
Loading