[ML] Model management UI fixes and enhancements (#79072)

* [ML] link to edit pipeline

* [ML] view training data link

* [ML] format stats and configs

* [ML] refactor date_utils

* [ML] fix types

* [ML] change "View" icon and label

* [ML] revert label change
This commit is contained in:
Dima Arnautov 2020-10-01 17:54:56 +02:00 committed by GitHub
parent ee7672aaf0
commit 6caf6d5080
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 138 additions and 80 deletions

View file

@ -6,10 +6,11 @@
// utility functions for handling dates
// @ts-ignore
import { formatDate } from '@elastic/eui/lib/services/format';
import dateMath from '@elastic/datemath';
import { TimeRange } from '../../../../../../src/plugins/data/common';
import { formatDate } from '@elastic/eui';
import { TimeRange } from '../../../../../src/plugins/data/common';
import { TIME_FORMAT } from '../constants/time_format';
export function formatHumanReadableDate(ts: number) {
return formatDate(ts, 'MMMM Do YYYY');
}
@ -28,3 +29,7 @@ export function validateTimeRange(time?: TimeRange): boolean {
const momentDateTo = dateMath.parse(time.to);
return !!(momentDateFrom && momentDateFrom.isValid() && momentDateTo && momentDateTo.isValid());
}
export const timeFormatter = (value: number) => {
return formatDate(value, TIME_FORMAT);
};

View file

@ -15,7 +15,7 @@ import { EuiDescriptionList } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { Annotation } from '../../../../../common/types/annotations';
import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils';
import { formatHumanReadableDateTimeSeconds } from '../../../../../common/util/date_utils';
interface Props {
annotation: Annotation;
@ -61,7 +61,7 @@ export const AnnotationDescriptionList = ({ annotation, detectorDescription }: P
defaultMessage: 'Created by',
}
),
description: annotation.create_username,
description: annotation.create_username ?? '',
});
listItems.push({
title: i18n.translate(
@ -79,7 +79,7 @@ export const AnnotationDescriptionList = ({ annotation, detectorDescription }: P
defaultMessage: 'Modified by',
}
),
description: annotation.modified_username,
description: annotation.modified_username ?? '',
});
}
if (detectorDescription !== undefined) {
@ -94,19 +94,19 @@ export const AnnotationDescriptionList = ({ annotation, detectorDescription }: P
if (annotation.partition_field_name !== undefined) {
listItems.push({
title: annotation.partition_field_name,
description: annotation.partition_field_value,
description: annotation.partition_field_value ?? '',
});
}
if (annotation.over_field_name !== undefined) {
listItems.push({
title: annotation.over_field_name,
description: annotation.over_field_value,
description: annotation.over_field_value ?? '',
});
}
if (annotation.by_field_name !== undefined) {
listItems.push({
title: annotation.by_field_name,
description: annotation.by_field_value,
description: annotation.by_field_value ?? '',
});
}

View file

@ -31,8 +31,6 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services';
import { formatDate } from '@elastic/eui/lib/services/format';
import { addItemToRecentlyAccessed } from '../../../util/recently_accessed';
import { ml } from '../../../services/ml_api_service';
import { mlJobService } from '../../../services/job_service';
@ -42,7 +40,6 @@ import {
getLatestDataOrBucketTimestamp,
isTimeSeriesViewJob,
} from '../../../../../common/util/job_utils';
import { TIME_FORMAT } from '../../../../../common/constants/time_format';
import {
annotation$,
@ -56,6 +53,7 @@ import {
import { withKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { ML_APP_URL_GENERATOR, ML_PAGES } from '../../../../../common/constants/ml_url_generator';
import { PLUGIN_ID } from '../../../../../common/constants/app';
import { timeFormatter } from '../../../../../common/util/date_utils';
const CURRENT_SERIES = 'current_series';
/**
@ -377,10 +375,6 @@ class AnnotationsTableUI extends Component {
);
}
function renderDate(date) {
return formatDate(date, TIME_FORMAT);
}
const columns = [
{
field: 'annotation',
@ -397,7 +391,7 @@ class AnnotationsTableUI extends Component {
defaultMessage: 'From',
}),
dataType: 'date',
render: renderDate,
render: timeFormatter,
sortable: true,
},
{
@ -406,7 +400,7 @@ class AnnotationsTableUI extends Component {
defaultMessage: 'To',
}),
dataType: 'date',
render: renderDate,
render: timeFormatter,
sortable: true,
},
{
@ -415,7 +409,7 @@ class AnnotationsTableUI extends Component {
defaultMessage: 'Last modified date',
}),
dataType: 'date',
render: renderDate,
render: timeFormatter,
sortable: true,
},
{

View file

@ -16,7 +16,7 @@ import {
formatHumanReadableDate,
formatHumanReadableDateTime,
formatHumanReadableDateTimeSeconds,
} from '../../util/date_utils';
} from '../../../../common/util/date_utils';
import { DescriptionCell } from './description_cell';
import { DetectorCell } from './detector_cell';

View file

@ -26,7 +26,7 @@ import {
EuiTabbedContent,
EuiText,
} from '@elastic/eui';
import { formatHumanReadableDateTimeSeconds } from '../../util/date_utils';
import { formatHumanReadableDateTimeSeconds } from '../../../../common/util/date_utils';
import { EntityCell } from '../entity_cell';
import {

View file

@ -26,7 +26,7 @@ import { getFieldTypeFromMapping } from '../../services/mapping_service';
import { ml } from '../../services/ml_api_service';
import { mlJobService } from '../../services/job_service';
import { getUrlForRecord, openCustomUrlWindow } from '../../util/custom_url_utils';
import { formatHumanReadableDateTimeSeconds } from '../../util/date_utils';
import { formatHumanReadableDateTimeSeconds } from '../../../../common/util/date_utils';
import { getIndexPatternIdFromName } from '../../util/index_utils';
import { replaceStringTokens } from '../../util/string_utils';
import { ML_APP_URL_GENERATOR, ML_PAGES } from '../../../../common/constants/ml_url_generator';

View file

@ -37,7 +37,7 @@ import {
OUTLIER_SCORE,
TOP_CLASSES,
} from '../../data_frame_analytics/common/constants';
import { formatHumanReadableDateTimeSeconds } from '../../util/date_utils';
import { formatHumanReadableDateTimeSeconds } from '../../../../common/util/date_utils';
import { getNestedProperty } from '../../util/object_utils';
import { mlFieldFormatService } from '../../services/field_format_service';

View file

@ -7,14 +7,13 @@
import React, { FC } from 'react';
import { EuiSpacer, EuiInMemoryTable, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
// @ts-ignore
import { formatDate } from '@elastic/eui/lib/services/format';
import { i18n } from '@kbn/i18n';
import theme from '@elastic/eui/dist/eui_theme_light.json';
import { JobMessage } from '../../../../common/types/audit_message';
import { TIME_FORMAT } from '../../../../common/constants/time_format';
import { JobIcon } from '../job_message_icon';
import { timeFormatter } from '../../../../common/util/date_utils';
interface JobMessagesProps {
messages: JobMessage[];
@ -55,7 +54,7 @@ export const JobMessages: FC<JobMessagesProps> = ({ messages, loading, error, re
name: i18n.translate('xpack.ml.jobMessages.timeLabel', {
defaultMessage: 'Time',
}),
render: (timestamp: number) => formatDate(timestamp, TIME_FORMAT),
render: timeFormatter,
width: '120px',
sortable: true,
},

View file

@ -13,7 +13,6 @@ import {
EuiInMemoryTable,
EuiLoadingSpinner,
EuiBasicTableColumn,
formatDate,
} from '@elastic/eui';
import { checkPermission } from '../../capabilities/check_capabilities';
@ -21,12 +20,12 @@ import { EditModelSnapshotFlyout } from './edit_model_snapshot_flyout';
import { RevertModelSnapshotFlyout } from './revert_model_snapshot_flyout';
import { ml } from '../../services/ml_api_service';
import { JOB_STATE, DATAFEED_STATE } from '../../../../common/constants/states';
import { TIME_FORMAT } from '../../../../common/constants/time_format';
import { CloseJobConfirm } from './close_job_confirm';
import {
ModelSnapshot,
CombinedJobWithStats,
} from '../../../../common/types/anomaly_detection_jobs';
import { timeFormatter } from '../../../../common/util/date_utils';
interface Props {
job: CombinedJobWithStats;
@ -138,7 +137,7 @@ export const ModelSnapshotTable: FC<Props> = ({ job, refreshJobList }) => {
defaultMessage: 'Date created',
}),
dataType: 'date',
render: renderDate,
render: timeFormatter,
sortable: true,
},
{
@ -147,7 +146,7 @@ export const ModelSnapshotTable: FC<Props> = ({ job, refreshJobList }) => {
defaultMessage: 'Latest timestamp',
}),
dataType: 'date',
render: renderDate,
render: timeFormatter,
sortable: true,
},
{
@ -246,10 +245,6 @@ export const ModelSnapshotTable: FC<Props> = ({ job, refreshJobList }) => {
);
};
function renderDate(date: number) {
return formatDate(date, TIME_FORMAT);
}
async function getCombinedJobState(jobId: string) {
const jobs = await ml.jobs.jobs([jobId]);

View file

@ -32,7 +32,6 @@ import {
EuiHorizontalRule,
EuiSuperSelect,
EuiText,
formatDate,
} from '@elastic/eui';
import {
@ -47,8 +46,8 @@ import { LineChartPoint } from '../../../jobs/new_job/common/chart_loader';
import { EventRateChart } from '../../../jobs/new_job/pages/components/charts/event_rate_chart/event_rate_chart';
import { Anomaly } from '../../../jobs/new_job/common/results_loader/results_loader';
import { parseInterval } from '../../../../../common/util/parse_interval';
import { TIME_FORMAT } from '../../../../../common/constants/time_format';
import { CreateCalendar, CalendarEvent } from './create_calendar';
import { timeFormatter } from '../../../../../common/util/date_utils';
interface Props {
snapshot: ModelSnapshot;
@ -255,7 +254,7 @@ export const RevertModelSnapshotFlyout: FC<Props> = ({ snapshot, snapshots, job,
<FormattedMessage
id="xpack.ml.newJob.wizard.revertModelSnapshotFlyout.warningCallout.contents"
defaultMessage="All anomaly detection results after {date} will be deleted."
values={{ date: formatDate(currentSnapshot.latest_record_time_stamp, TIME_FORMAT) }}
values={{ date: timeFormatter(currentSnapshot.latest_record_time_stamp) }}
/>
</EuiCallOut>

View file

@ -11,7 +11,7 @@ import { EuiIcon, EuiLoadingSpinner, EuiTabbedContent } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { formatHumanReadableDateTimeSeconds } from '../../../../../util/date_utils';
import { formatHumanReadableDateTimeSeconds } from '../../../../../../../common/util/date_utils';
import { DataFrameAnalyticsListRow } from './common';
import { ExpandedRowDetailsPane, SectionConfig, SectionItem } from './expanded_row_details_pane';

View file

@ -20,16 +20,35 @@ import {
EuiHorizontalRule,
EuiFlexGroup,
EuiTextColor,
EuiButtonEmpty,
EuiBadge,
} from '@elastic/eui';
// @ts-ignore
import { formatDate } from '@elastic/eui/lib/services/format';
import { EuiDescriptionListProps } from '@elastic/eui/src/components/description_list/description_list';
import { ModelItemFull } from './models_list';
import { TIME_FORMAT } from '../../../../../../../common/constants/time_format';
import { useMlKibana } from '../../../../../contexts/kibana';
import { timeFormatter } from '../../../../../../../common/util/date_utils';
interface ExpandedRowProps {
item: ModelItemFull;
}
const formatterDictionary: Record<string, (value: any) => JSX.Element | string | undefined> = {
tags: (tags: string[]) => {
if (tags.length === 0) return;
return (
<div>
{tags.map((tag) => (
<EuiBadge key={tag} color="hollow">
{tag}
</EuiBadge>
))}
</div>
);
},
create_time: timeFormatter,
timestamp: timeFormatter,
};
export const ExpandedRow: FC<ExpandedRowProps> = ({ item }) => {
const {
inference_config: inferenceConfig,
@ -57,19 +76,45 @@ export const ExpandedRow: FC<ExpandedRowProps> = ({ item }) => {
license_level,
};
function formatToListItems(items: Record<string, any>) {
function formatToListItems(items: Record<string, any>): EuiDescriptionListProps['listItems'] {
return Object.entries(items)
.map(([title, value]) => {
if (title.includes('timestamp')) {
value = formatDate(value, TIME_FORMAT);
if (title in formatterDictionary) {
return {
title,
description: formatterDictionary[title](value),
};
}
return { title, description: typeof value === 'object' ? JSON.stringify(value) : value };
return {
title,
description:
typeof value === 'object' ? (
<EuiCodeBlock
language="json"
fontSize="s"
paddingSize="s"
overflowHeight={300}
isCopyable={false}
>
{JSON.stringify(value, null, 2)}
</EuiCodeBlock>
) : (
value
),
};
})
.filter(({ description }) => {
return description !== undefined;
});
}
const {
services: {
share,
application: { navigateToUrl },
},
} = useMlKibana();
const tabs = [
{
id: 'details',
@ -323,9 +368,35 @@ export const ExpandedRow: FC<ExpandedRowProps> = ({ item }) => {
return (
<EuiFlexItem key={pipelineName}>
<EuiPanel>
<EuiTitle size={'xs'}>
<h5>{pipelineName}</h5>
</EuiTitle>
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiTitle size={'xs'}>
<h5>{pipelineName}</h5>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
onClick={async () => {
const ingestPipelinesAppUrlGenerator = share.urlGenerators.getUrlGenerator(
'INGEST_PIPELINES_APP_URL_GENERATOR'
);
await navigateToUrl(
await ingestPipelinesAppUrlGenerator.createUrl({
page: 'pipeline_edit',
pipelineId: pipelineName,
absolute: true,
})
);
}}
>
<FormattedMessage
id="xpack.ml.inference.modelsList.expandedRow.editPipelineLabel"
defaultMessage="Edit"
/>
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
{description && <EuiText>{description}</EuiText>}
<EuiSpacer size={'m'} />
<EuiTitle size={'xxs'}>

View file

@ -19,15 +19,13 @@ import {
EuiBadge,
SearchFilterConfig,
} from '@elastic/eui';
// @ts-ignore
import { formatDate } from '@elastic/eui/lib/services/format';
import { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/basic_table';
import { EuiTableSelectionType } from '@elastic/eui/src/components/basic_table/table_types';
import { Action } from '@elastic/eui/src/components/basic_table/action_types';
import { StatsBar, ModelsBarStats } from '../../../../../components/stats_bar';
import { useInferenceApiService } from '../../../../../services/ml_api_service/inference';
import { ModelsTableToConfigMapping } from './index';
import { TIME_FORMAT } from '../../../../../../../common/constants/time_format';
import { DeleteModelsModal } from './delete_models_modal';
import { useMlKibana, useMlUrlGenerator, useNotifications } from '../../../../../contexts/kibana';
import { ExpandedRow } from './expanded_row';
@ -46,6 +44,7 @@ import { useTableSettings } from '../analytics_list/use_table_settings';
import { filterAnalyticsModels, AnalyticsSearchBar } from '../analytics_search_bar';
import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator';
import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
import { timeFormatter } from '../../../../../../../common/util/date_utils';
type Stats = Omit<TrainedModelStat, 'model_id'>;
@ -277,7 +276,7 @@ export const ModelsList: FC = () => {
description: i18n.translate('xpack.ml.inference.modelsList.viewTrainingDataActionLabel', {
defaultMessage: 'View training data',
}),
icon: 'list',
icon: 'visTable',
type: 'icon',
available: (item) => item.metadata?.analytics_config?.id,
onClick: async (item) => {
@ -290,6 +289,7 @@ export const ModelsList: FC = () => {
analysisType: getAnalysisType(
item.metadata?.analytics_config.analysis
) as DataFrameAnalysisConfigType,
defaultIsTraining: true,
},
});
@ -375,7 +375,7 @@ export const ModelsList: FC = () => {
defaultMessage: 'Created at',
}),
dataType: 'date',
render: (date: string) => formatDate(date, TIME_FORMAT),
render: timeFormatter,
sortable: true,
},
{

View file

@ -17,7 +17,7 @@ import d3 from 'd3';
import $ from 'jquery';
import moment from 'moment';
import { formatHumanReadableDateTime } from '../../util/date_utils';
import { formatHumanReadableDateTime } from '../../../../common/util/date_utils';
import { formatValue } from '../../formatters/format_value';
import { getSeverityColor, getSeverityWithLow } from '../../../../common/util/anomaly_utils';
import {

View file

@ -17,7 +17,7 @@ import $ from 'jquery';
import moment from 'moment';
import { i18n } from '@kbn/i18n';
import { formatHumanReadableDateTime } from '../../util/date_utils';
import { formatHumanReadableDateTime } from '../../../../common/util/date_utils';
import { formatValue } from '../../formatters/format_value';
import {
getSeverityColor,

View file

@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n';
import { Subject, Subscription } from 'rxjs';
import { TooltipValue } from '@elastic/charts';
import { htmlIdGenerator } from '@elastic/eui';
import { formatHumanReadableDateTime } from '../util/date_utils';
import { formatHumanReadableDateTime } from '../../../common/util/date_utils';
import { numTicksForDateFormat } from '../util/chart_utils';
import { getSeverityColor } from '../../../common/util/anomaly_utils';
import { mlEscape } from '../util/string_utils';

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { formatHumanReadableDateTime } from '../../../util/date_utils';
import { formatHumanReadableDateTime } from '../../../../../common/util/date_utils';
import { getDefaultChartsData } from '../../explorer_charts/explorer_charts_container_service';
import { EXPLORER_ACTION, VIEW_BY_JOB_LABEL } from '../../explorer_constants';

View file

@ -16,10 +16,9 @@ import {
EuiLink,
EuiLoadingSpinner,
} from '@elastic/eui';
import { formatDate, formatNumber } from '@elastic/eui/lib/services/format';
import { formatNumber } from '@elastic/eui/lib/services/format';
import { FORECAST_REQUEST_STATE } from '../../../../../../../common/constants/states';
import { TIME_FORMAT } from '../../../../../../../common/constants/time_format';
import { addItemToRecentlyAccessed } from '../../../../../util/recently_accessed';
import { mlForecastService } from '../../../../../services/forecast_service';
import { i18n } from '@kbn/i18n';
@ -34,6 +33,7 @@ import {
ML_PAGES,
} from '../../../../../../../common/constants/ml_url_generator';
import { PLUGIN_ID } from '../../../../../../../common/constants/app';
import { timeFormatter } from '../../../../../../../common/util/date_utils';
const MAX_FORECASTS = 500;
@ -218,7 +218,7 @@ export class ForecastsTableUI extends Component {
defaultMessage: 'Created',
}),
dataType: 'date',
render: (date) => formatDate(date, TIME_FORMAT),
render: timeFormatter,
textOnly: true,
sortable: true,
scope: 'row',
@ -229,7 +229,7 @@ export class ForecastsTableUI extends Component {
defaultMessage: 'From',
}),
dataType: 'date',
render: (date) => formatDate(date, TIME_FORMAT),
render: timeFormatter,
textOnly: true,
sortable: true,
},
@ -239,7 +239,7 @@ export class ForecastsTableUI extends Component {
defaultMessage: 'To',
}),
dataType: 'date',
render: (date) => formatDate(date, TIME_FORMAT),
render: timeFormatter,
textOnly: true,
sortable: true,
},
@ -277,7 +277,7 @@ export class ForecastsTableUI extends Component {
name: i18n.translate('xpack.ml.jobsList.jobDetails.forecastsTable.expiresLabel', {
defaultMessage: 'Expires',
}),
render: (date) => formatDate(date, TIME_FORMAT),
render: timeFormatter,
textOnly: true,
sortable: true,
},
@ -309,7 +309,7 @@ export class ForecastsTableUI extends Component {
{
defaultMessage: 'View forecast created at {createdDate}',
values: {
createdDate: formatDate(forecast.forecast_create_timestamp, TIME_FORMAT),
createdDate: timeFormatter(forecast.forecast_create_timestamp),
},
}
);

View file

@ -5,10 +5,9 @@
*/
import numeral from '@elastic/numeral';
import { formatDate } from '@elastic/eui/lib/services/format';
import { roundToDecimalPlace } from '../../../../formatters/round_to_decimal_place';
import { toLocaleString } from '../../../../util/string_utils';
import { TIME_FORMAT } from '../../../../../../common/constants/time_format';
import { timeFormatter } from '../../../../../../common/util/date_utils';
const DATA_FORMAT = '0.0 b';
@ -29,7 +28,7 @@ export function formatValues([key, value]) {
case 'latest_empty_bucket_timestamp':
case 'latest_sparse_bucket_timestamp':
case 'latest_bucket_timestamp':
value = formatDate(value, TIME_FORMAT);
value = timeFormatter(value);
break;
// data

View file

@ -5,12 +5,10 @@
*/
import React, { FC } from 'react';
// @ts-ignore
import { formatDate } from '@elastic/eui/lib/services/format';
import { EuiIcon } from '@elastic/eui';
import { RectAnnotation, LineAnnotation, AnnotationDomainTypes } from '@elastic/charts';
import { LineChartPoint } from '../../../../common/chart_loader';
import { TIME_FORMAT } from '../../../../../../../../common/constants/time_format';
import { timeFormatter } from '../../../../../../../../common/util/date_utils';
interface Props {
overlayKey: number;
@ -70,9 +68,7 @@ export const OverlayRange: FC<Props> = ({
<div style={{ textAlign: 'center' }}>
<EuiIcon type="arrowUp" />
</div>
<div style={{ fontWeight: 'normal', color: '#343741' }}>
{formatDate(start, TIME_FORMAT)}
</div>
<div style={{ fontWeight: 'normal', color: '#343741' }}>{timeFormatter(start)}</div>
</div>
</>
) : undefined

View file

@ -23,7 +23,7 @@ import {
getTaskStateBadge,
progressColumn,
} from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns';
import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils';
import { formatHumanReadableDateTimeSeconds } from '../../../../../common/util/date_utils';
import { ViewLink } from './actions';

View file

@ -20,7 +20,7 @@ import {
EuiToolTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils';
import { formatHumanReadableDateTimeSeconds } from '../../../../../common/util/date_utils';
import { ExplorerLink } from './actions';
import { getJobsFromGroup } from './utils';
import { GroupsDictionary, Group } from './anomaly_detection_panel';

View file

@ -15,7 +15,7 @@ import { isWebUrl } from '../util/url_utils';
import { ML_DATA_PREVIEW_COUNT } from '../../../common/util/job_utils';
import { TIME_FORMAT } from '../../../common/constants/time_format';
import { parseInterval } from '../../../common/util/parse_interval';
import { validateTimeRange } from '../util/date_utils';
import { validateTimeRange } from '../../../common/util/date_utils';
let jobs = [];
let datafeedIds = {};

View file

@ -13,7 +13,7 @@ import React from 'react';
import { EuiButtonIcon, EuiIcon, EuiInMemoryTable, EuiText, EuiToolTip } from '@elastic/eui';
import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils';
import { formatHumanReadableDateTimeSeconds } from '../../../../../common/util/date_utils';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';

View file

@ -33,7 +33,7 @@ import {
showMultiBucketAnomalyMarker,
showMultiBucketAnomalyTooltip,
} from '../../../util/chart_utils';
import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils';
import { formatHumanReadableDateTimeSeconds } from '../../../../../common/util/date_utils';
import { getTimeBucketsFromCache } from '../../../util/time_buckets';
import { mlTableService } from '../../../services/table_service';
import { ContextChartMask } from '../context_chart_mask';

View file

@ -20,4 +20,4 @@ export * from '../common/util/validators';
export * from './application/formatters/metric_change_description';
export * from './application/components/data_grid';
export * from './application/data_frame_analytics/common';
export * from './application/util/date_utils';
export * from '../common/util/date_utils';