Skip to content

Commit

Permalink
Explorer empty bucket (#990)
Browse files Browse the repository at this point in the history
* Empty bucket addition proof of concept

Signed-off-by: Paul Sebastian <paulstn@amazon.com>

* Use the page's interval period for empty buckets

Signed-off-by: Paul Sebastian <paulstn@amazon.com>

* redid empty bucket loop design to be more intuitive

Signed-off-by: Paul Sebastian <paulstn@amazon.com>

* Added dynamic start and end datetime info to new count distribution

Signed-off-by: Paul Sebastian <paulstn@amazon.com>

* use interval period to align count distribution rebucketing

Signed-off-by: Paul Sebastian <paulstn@amazon.com>

* count distribution snapshot
Signed-off-by: Paul Sebastian <paulstn@amazon.com>

* moved empty bucket adding functionality out to a util file and generalized

Signed-off-by: Paul Sebastian <paulstn@amazon.com>

* util func tests

Signed-off-by: Paul Sebastian <paulstn@amazon.com>

* added stricter types for input and return in util func

Signed-off-by: Paul Sebastian <paulstn@amazon.com>

* needed to update util test

Signed-off-by: Paul Sebastian <paulstn@amazon.com>

---------

Signed-off-by: Paul Sebastian <paulstn@amazon.com>
  • Loading branch information
paulstn authored Sep 13, 2023
1 parent f989783 commit 188cee4
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 133 deletions.
20 changes: 20 additions & 0 deletions common/types/explorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,3 +381,23 @@ export interface VisSpecificMetaData {
x_coordinate: string;
y_coordinate: string;
}

export type MOMENT_UNIT_OF_TIME =
| 'years'
| 'y'
| 'quarters'
| 'Q'
| 'months'
| 'M'
| 'weeks'
| 'w'
| 'days'
| 'd'
| 'hours'
| 'h'
| 'minutes'
| 'm'
| 'seconds'
| 's'
| 'milliseconds'
| 'ms';
7 changes: 6 additions & 1 deletion public/components/event_analytics/explorer/explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,12 @@ export const Explorer = ({
startTime={appLogEvents ? startTime : dateRange[0]}
endTime={appLogEvents ? endTime : dateRange[1]}
/>
<CountDistribution countDistribution={countDistribution} />
<CountDistribution
countDistribution={countDistribution}
selectedInterval={selectedIntervalRef.current?.value}
startTime={appLogEvents ? startTime : dateRange[0]}
endTime={appLogEvents ? endTime : dateRange[1]}
/>
<EuiHorizontalRule margin="xs" />
<LogPatterns
selectedIntervalUnit={selectedIntervalRef.current}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,134 +44,7 @@ exports[`Count distribution component Renders count distribution component with
},
}
}
>
<Plt
data={
Array [
Object {
"name": Object {
"name": "count()",
"type": "integer",
},
"orientation": "v",
"type": "bar",
"x": Array [
"2021-05-01 00:00:00",
"2021-06-01 00:00:00",
"2021-07-01 00:00:00",
],
"y": Array [
2549,
9337,
1173,
],
},
]
}
layout={
Object {
"colorway": Array [
"#8C55A3",
],
"height": 220,
"margin": Object {
"b": 15,
"l": 60,
"pad": 0,
"r": 10,
"t": 30,
},
"showlegend": true,
}
}
>
<PlotlyComponent
config={
Object {
"displayModeBar": false,
}
}
data={
Array [
Object {
"name": Object {
"name": "count()",
"type": "integer",
},
"orientation": "v",
"type": "bar",
"x": Array [
"2021-05-01 00:00:00",
"2021-06-01 00:00:00",
"2021-07-01 00:00:00",
],
"y": Array [
2549,
9337,
1173,
],
},
]
}
debug={false}
divId="explorerPlotComponent"
layout={
Object {
"autosize": true,
"barmode": "stack",
"colorway": Array [
"#8C55A3",
],
"height": 220,
"hovermode": "closest",
"legend": Object {
"orientation": "h",
"traceorder": "normal",
},
"margin": Object {
"b": 15,
"l": 60,
"pad": 0,
"r": 10,
"t": 30,
},
"showlegend": true,
"xaxis": Object {
"automargin": true,
"rangemode": "normal",
"showgrid": true,
"zeroline": false,
},
"yaxis": Object {
"rangemode": "normal",
"showgrid": true,
"title": Object {
"text": "Count",
},
"zeroline": false,
},
}
}
style={
Object {
"height": "100%",
"width": "100%",
}
}
useResizeHandler={true}
>
<div
id="explorerPlotComponent"
style={
Object {
"height": "100%",
"width": "100%",
}
}
/>
</PlotlyComponent>
</Plt>
</Component>
/>
`;

exports[`Count distribution component Renders empty count distribution component 1`] = `<Component />`;
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@
import React from 'react';
import { BarOrientation, LONG_CHART_COLOR } from '../../../../../../common/constants/shared';
import { Plt } from '../../../../visualizations/plotly/plot';
import { fillTimeDataWithEmpty } from '../../../utils/utils';

export const CountDistribution = ({ countDistribution }: any) => {
export const CountDistribution = ({
countDistribution,
selectedInterval,
startTime,
endTime,
}: any) => {
if (
!countDistribution ||
!countDistribution.data ||
!countDistribution.metadata ||
!countDistribution.metadata.fields
!countDistribution.metadata.fields ||
!selectedInterval
)
return null;

Expand All @@ -31,9 +38,31 @@ export const CountDistribution = ({ countDistribution }: any) => {
},
];

// fill the final data with the exact right amount of empty buckets
function fillWithEmpty(processedData: any) {
// original x and y fields
const xVals = processedData[0].x;
const yVals = processedData[0].y;

const { buckets, values } = fillTimeDataWithEmpty(
xVals,
yVals,
selectedInterval.replace(/^auto_/, ''),
startTime,
endTime
);

// replace old x and y values with new
processedData[0].x = buckets;
processedData[0].y = values;

// // at the end, return the new object
return processedData;
}

return (
<Plt
data={finalData}
data={fillWithEmpty(finalData)}
layout={{
showlegend: true,
margin: {
Expand Down
66 changes: 66 additions & 0 deletions public/components/event_analytics/utils/__tests__/utils.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
isValidTraceId,
rangeNumDocs,
getHeaders,
fillTimeDataWithEmpty,
} from '../utils';

describe('Utils event analytics helper functions', () => {
Expand Down Expand Up @@ -80,4 +81,69 @@ describe('Utils event analytics helper functions', () => {
).toBeTruthy();
expect(getHeaders([], ['', 'Time', '_source'], undefined)).toBeTruthy();
});

it('validates fillTimeDataWithEmpty function', () => {
expect(
fillTimeDataWithEmpty(
['2023-07-01 00:00:00', '2023-08-01 00:00:00', '2023-09-01 00:00:00'],
[54, 802, 292],
'M',
'2023-01-01T08:00:00.000Z',
'2023-09-12T21:36:31.389Z'
)
).toEqual({
buckets: [
'2023-01-01 00:00:00',
'2023-02-01 00:00:00',
'2023-03-01 00:00:00',
'2023-04-01 00:00:00',
'2023-05-01 00:00:00',
'2023-06-01 00:00:00',
'2023-07-01 00:00:00',
'2023-08-01 00:00:00',
'2023-09-01 00:00:00',
],
values: [0, 0, 0, 0, 0, 0, 54, 802, 292],
});
expect(
fillTimeDataWithEmpty(
[
'2023-09-11 07:00:00',
'2023-09-11 09:00:00',
'2023-09-11 10:00:00',
'2023-09-11 11:00:00',
'2023-09-11 12:00:00',
'2023-09-11 13:00:00',
'2023-09-11 14:00:00',
'2023-09-11 15:00:00',
],
[1, 1, 5, 4, 2, 3, 3, 1],
'h',
'2023-09-11T00:00:00.000',
'2023-09-11T17:00:00.000'
)
).toEqual({
buckets: [
'2023-09-11 00:00:00',
'2023-09-11 01:00:00',
'2023-09-11 02:00:00',
'2023-09-11 03:00:00',
'2023-09-11 04:00:00',
'2023-09-11 05:00:00',
'2023-09-11 06:00:00',
'2023-09-11 07:00:00',
'2023-09-11 08:00:00',
'2023-09-11 09:00:00',
'2023-09-11 10:00:00',
'2023-09-11 11:00:00',
'2023-09-11 12:00:00',
'2023-09-11 13:00:00',
'2023-09-11 14:00:00',
'2023-09-11 15:00:00',
'2023-09-11 16:00:00',
'2023-09-11 17:00:00',
],
values: [0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 5, 4, 2, 3, 3, 1, 0, 0],
});
});
});
58 changes: 57 additions & 1 deletion public/components/event_analytics/utils/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
/* eslint-disable no-bitwise */
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

/* eslint-disable no-bitwise */

import { uniqueId, isEmpty } from 'lodash';
import moment from 'moment';
import React from 'react';
import { EuiText } from '@elastic/eui';
import datemath from '@elastic/datemath';
import { HttpStart } from '../../../../../../src/core/public';
import {
CUSTOM_LABEL,
TIME_INTERVAL_OPTIONS,
GROUPBY,
AGGREGATIONS,
BREAKDOWNS,
DATE_PICKER_FORMAT,
} from '../../../../common/constants/explorer';
import { PPL_DATE_FORMAT, PPL_INDEX_REGEX } from '../../../../common/constants/shared';
import {
Expand All @@ -23,6 +26,7 @@ import {
IExplorerFields,
IField,
IQuery,
MOMENT_UNIT_OF_TIME,
} from '../../../../common/types/explorer';
import PPLService from '../../../services/requests/ppl';
import { DocViewRow, IDocType } from '../explorer/events_views';
Expand Down Expand Up @@ -459,3 +463,55 @@ export const getContentTabTitle = (tabID: string, tabTitle: string) => {
</>
);
};

/**
* Used to fill in missing empty data where x is an array of time values and there are only x
* values when y is non-zero.
* @param xVals all x values being used
* @param yVals all y values being used
* @param intervalPeriod Moment unitOfTime used to dictate how long each interval is
* @param startTime starting time of x values
* @param endTime ending time of x values
* @returns an object with buckets and values where the buckets are all of the new x values and
* values are the corresponding values which include y values that are 0 for empty data
*/
export const fillTimeDataWithEmpty = (
xVals: string[],
yVals: number[],
intervalPeriod: MOMENT_UNIT_OF_TIME,
startTime: string,
endTime: string
): { buckets: string[]; values: number[] } => {
// parses out datetime for start and end, then reformats
const startDate = datemath
.parse(startTime)
?.startOf(intervalPeriod === 'w' ? 'isoWeek' : intervalPeriod);
const endDate = datemath
.parse(endTime)
?.startOf(intervalPeriod === 'w' ? 'isoWeek' : intervalPeriod);

// find the number of buckets
// below essentially does ((end - start) / interval_period) + 1
const numBuckets = endDate.diff(startDate, intervalPeriod) + 1;

// populate buckets as x values in the graph
const buckets = [startDate.format(DATE_PICKER_FORMAT)];
const currentDate = startDate;
for (let i = 1; i < numBuckets; i++) {
const nextBucket = currentDate.add(1, intervalPeriod);
buckets.push(nextBucket.format(DATE_PICKER_FORMAT));
}

// create y values, use old y values if they exist
const values: number[] = [];
buckets.forEach((bucket) => {
const bucketIndex = xVals.findIndex((x: string) => x === bucket);
if (bucketIndex !== -1) {
values.push(yVals[bucketIndex]);
} else {
values.push(0);
}
});

return { buckets, values };
};

0 comments on commit 188cee4

Please sign in to comment.