Skip to content

Commit

Permalink
[Logs UI] Use the Super date picker in the log stream (#54280)
Browse files Browse the repository at this point in the history
  • Loading branch information
Alejandro Fernández committed Mar 19, 2020
1 parent ee24743 commit 7d5ba1c
Show file tree
Hide file tree
Showing 74 changed files with 2,354 additions and 2,724 deletions.
72 changes: 39 additions & 33 deletions x-pack/plugins/infra/common/http_api/log_entries/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ export const LOG_ENTRIES_PATH = '/api/log_entries/entries';
export const logEntriesBaseRequestRT = rt.intersection([
rt.type({
sourceId: rt.string,
startDate: rt.number,
endDate: rt.number,
startTimestamp: rt.number,
endTimestamp: rt.number,
}),
rt.partial({
query: rt.string,
query: rt.union([rt.string, rt.null]),
size: rt.number,
}),
]);
Expand All @@ -31,7 +31,7 @@ export const logEntriesAfterRequestRT = rt.intersection([
rt.type({ after: rt.union([logEntriesCursorRT, rt.literal('first')]) }),
]);

export const logEntriesCenteredRT = rt.intersection([
export const logEntriesCenteredRequestRT = rt.intersection([
logEntriesBaseRequestRT,
rt.type({ center: logEntriesCursorRT }),
]);
Expand All @@ -40,54 +40,60 @@ export const logEntriesRequestRT = rt.union([
logEntriesBaseRequestRT,
logEntriesBeforeRequestRT,
logEntriesAfterRequestRT,
logEntriesCenteredRT,
logEntriesCenteredRequestRT,
]);

export type LogEntriesBaseRequest = rt.TypeOf<typeof logEntriesBaseRequestRT>;
export type LogEntriesBeforeRequest = rt.TypeOf<typeof logEntriesBeforeRequestRT>;
export type LogEntriesAfterRequest = rt.TypeOf<typeof logEntriesAfterRequestRT>;
export type LogEntriesCenteredRequest = rt.TypeOf<typeof logEntriesCenteredRequestRT>;
export type LogEntriesRequest = rt.TypeOf<typeof logEntriesRequestRT>;

// JSON value
const valueRT = rt.union([rt.string, rt.number, rt.boolean, rt.object, rt.null, rt.undefined]);
export const logMessageConstantPartRT = rt.type({
constant: rt.string,
});
export const logMessageFieldPartRT = rt.type({
field: rt.string,
value: rt.unknown,
highlights: rt.array(rt.string),
});

export const logMessagePartRT = rt.union([
rt.type({
constant: rt.string,
}),
rt.type({
field: rt.string,
value: valueRT,
highlights: rt.array(rt.string),
}),
]);
export const logMessagePartRT = rt.union([logMessageConstantPartRT, logMessageFieldPartRT]);

export const logColumnRT = rt.union([
rt.type({ columnId: rt.string, timestamp: rt.number }),
rt.type({
columnId: rt.string,
field: rt.string,
value: rt.union([rt.string, rt.undefined]),
highlights: rt.array(rt.string),
}),
rt.type({
columnId: rt.string,
message: rt.array(logMessagePartRT),
}),
]);
export const logTimestampColumnRT = rt.type({ columnId: rt.string, timestamp: rt.number });
export const logFieldColumnRT = rt.type({
columnId: rt.string,
field: rt.string,
value: rt.unknown,
highlights: rt.array(rt.string),
});
export const logMessageColumnRT = rt.type({
columnId: rt.string,
message: rt.array(logMessagePartRT),
});

export const logColumnRT = rt.union([logTimestampColumnRT, logFieldColumnRT, logMessageColumnRT]);

export const logEntryRT = rt.type({
id: rt.string,
cursor: logEntriesCursorRT,
columns: rt.array(logColumnRT),
});

export type LogMessagepart = rt.TypeOf<typeof logMessagePartRT>;
export type LogMessageConstantPart = rt.TypeOf<typeof logMessageConstantPartRT>;
export type LogMessageFieldPart = rt.TypeOf<typeof logMessageFieldPartRT>;
export type LogMessagePart = rt.TypeOf<typeof logMessagePartRT>;
export type LogTimestampColumn = rt.TypeOf<typeof logTimestampColumnRT>;
export type LogFieldColumn = rt.TypeOf<typeof logFieldColumnRT>;
export type LogMessageColumn = rt.TypeOf<typeof logMessageColumnRT>;
export type LogColumn = rt.TypeOf<typeof logColumnRT>;
export type LogEntry = rt.TypeOf<typeof logEntryRT>;

export const logEntriesResponseRT = rt.type({
data: rt.type({
entries: rt.array(logEntryRT),
topCursor: logEntriesCursorRT,
bottomCursor: logEntriesCursorRT,
topCursor: rt.union([logEntriesCursorRT, rt.null]),
bottomCursor: rt.union([logEntriesCursorRT, rt.null]),
}),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
logEntriesBaseRequestRT,
logEntriesBeforeRequestRT,
logEntriesAfterRequestRT,
logEntriesCenteredRT,
logEntriesCenteredRequestRT,
logEntryRT,
} from './entries';
import { logEntriesCursorRT } from './common';
Expand All @@ -36,7 +36,7 @@ export const logEntriesHighlightsAfterRequestRT = rt.intersection([
]);

export const logEntriesHighlightsCenteredRequestRT = rt.intersection([
logEntriesCenteredRT,
logEntriesCenteredRequestRT,
highlightsRT,
]);

Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/infra/common/http_api/log_entries/summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export const LOG_ENTRIES_SUMMARY_PATH = '/api/log_entries/summary';

export const logEntriesSummaryRequestRT = rt.type({
sourceId: rt.string,
startDate: rt.number,
endDate: rt.number,
startTimestamp: rt.number,
endTimestamp: rt.number,
bucketSize: rt.number,
query: rt.union([rt.string, rt.undefined, rt.null]),
});
Expand Down
73 changes: 73 additions & 0 deletions x-pack/plugins/infra/public/components/logging/log_datepicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { useCallback } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiSuperDatePicker, EuiButtonEmpty } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';

interface LogDatepickerProps {
startDateExpression: string;
endDateExpression: string;
isStreaming: boolean;
onUpdateDateRange?: (range: { startDateExpression: string; endDateExpression: string }) => void;
onStartStreaming?: () => void;
onStopStreaming?: () => void;
}

export const LogDatepicker: React.FC<LogDatepickerProps> = ({
startDateExpression,
endDateExpression,
isStreaming,
onUpdateDateRange,
onStartStreaming,
onStopStreaming,
}) => {
const handleTimeChange = useCallback(
({ start, end, isInvalid }) => {
if (onUpdateDateRange && !isInvalid) {
onUpdateDateRange({ startDateExpression: start, endDateExpression: end });
}
},
[onUpdateDateRange]
);

return (
<EuiFlexGroup gutterSize="s">
<EuiFlexItem>
<EuiSuperDatePicker
start={startDateExpression}
end={endDateExpression}
onTimeChange={handleTimeChange}
showUpdateButton={false}
// @ts-ignore: EuiSuperDatePicker doesn't expose the `isDisabled` prop, although it exists.
isDisabled={isStreaming}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{isStreaming ? (
<EuiButtonEmpty
color="primary"
iconType="pause"
iconSide="left"
onClick={onStopStreaming}
>
<FormattedMessage
id="xpack.infra.logs.stopStreamingButtonLabel"
defaultMessage="Stop streaming"
/>
</EuiButtonEmpty>
) : (
<EuiButtonEmpty iconType="play" iconSide="left" onClick={onStartStreaming}>
<FormattedMessage
id="xpack.infra.logs.startStreamingButtonLabel"
defaultMessage="Stream live"
/>
</EuiButtonEmpty>
)}
</EuiFlexItem>
</EuiFlexGroup>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import { max } from 'lodash';
import * as React from 'react';

import { euiStyled } from '../../../../../observability/public';
import { SummaryBucket } from './types';
import { LogEntriesSummaryBucket } from '../../../../common/http_api';

interface DensityChartProps {
buckets: SummaryBucket[];
buckets: LogEntriesSummaryBucket[];
end: number;
start: number;
width: number;
Expand All @@ -38,36 +38,36 @@ export const DensityChart: React.FC<DensityChartProps> = ({
const xMax = max(buckets.map(bucket => bucket.entriesCount)) || 0;
const xScale = scaleLinear()
.domain([0, xMax])
.range([0, width * (2 / 3)]);
.range([0, width]);

const path = area<SummaryBucket>()
const path = area<LogEntriesSummaryBucket>()
.x0(xScale(0))
.x1(bucket => xScale(bucket.entriesCount))
.y(bucket => yScale((bucket.start + bucket.end) / 2))
.y0(bucket => yScale(bucket.start))
.y1(bucket => yScale(bucket.end))
.curve(curveMonotoneY);
const pathData = path(buckets);

const highestPathCoord = String(pathData)
.replace(/[^.0-9,]/g, ' ')
.split(/[ ,]/)
.reduce((result, num) => (Number(num) > result ? Number(num) : result), 0);
const firstBucket = buckets[0];
const lastBucket = buckets[buckets.length - 1];
const pathBuckets = [
// Make sure the graph starts at the count of the first point
{ start, end: start, entriesCount: firstBucket.entriesCount },
...buckets,
// Make sure the line ends at the height of the last point
{ start: lastBucket.end, end: lastBucket.end, entriesCount: lastBucket.entriesCount },
// If the last point is not at the end of the minimap, make sure it doesn't extend indefinitely and goes to 0
{ start: end, end, entriesCount: 0 },
];
const pathData = path(pathBuckets);

return (
<g transform={`translate(${width / 3}, 0)`}>
<DensityChartNegativeBackground
transform={`translate(${-width / 3}, 0)`}
width={width / 2}
height={highestPathCoord}
/>
<DensityChartPositiveBackground width={width * (2 / 3)} height={highestPathCoord} />
<g>
<DensityChartPositiveBackground width={width} height={height} />
<PositiveAreaPath d={pathData || ''} />
</g>
);
};

const DensityChartNegativeBackground = euiStyled.rect`
fill: ${props => props.theme.eui.euiColorEmptyShade};
`;

const DensityChartPositiveBackground = euiStyled.rect`
fill: ${props =>
props.theme.darkMode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface HighlightedIntervalProps {
getPositionOfTime: (time: number) => number;
start: number;
end: number;
targetWidth: number;
width: number;
target: number | null;
}
Expand All @@ -22,6 +23,7 @@ export const HighlightedInterval: React.FC<HighlightedIntervalProps> = ({
end,
getPositionOfTime,
start,
targetWidth,
width,
target,
}) => {
Expand All @@ -35,14 +37,14 @@ export const HighlightedInterval: React.FC<HighlightedIntervalProps> = ({
<HighlightTargetMarker
className={className}
x1={0}
x2={width / 3}
x2={targetWidth}
y1={yTarget}
y2={yTarget}
/>
)}
<HighlightPolygon
className={className}
points={` ${width / 3},${yStart} ${width},${yStart} ${width},${yEnd} ${width / 3},${yEnd}`}
points={` ${targetWidth},${yStart} ${width},${yStart} ${width},${yEnd} ${targetWidth},${yEnd}`}
/>
</>
);
Expand Down
Loading

0 comments on commit 7d5ba1c

Please sign in to comment.