Skip to content

Commit

Permalink
build query parameters using data_end_time
Browse files Browse the repository at this point in the history
This PR addresses a data non-population issue observed in HC detectors. When setting the time horizon in the anomaly overview to the past hour, two boxes appeared in the heatmap. However, clicking on both resulted in no data being populated. Extending the time horizon to three hours increased the number of boxes to six, but similarly, clicking on these boxes also resulted in no data appearing.

The root cause of the issue is a mismatch in time references: the time displayed in the HC heatmap cells is calculated based on the anomaly plot time, which corresponds to data_end_time. However, when querying data within the HC heatmap cell's time range, data_start_time was used instead.

This PR updates sorting and querying fields from `DATA_START_TIME` to `DATA_END_TIME` to align with the data displayed in HC heatmap cells and ensure accuracy in temporal data analysis.

Testing done:
1. reproduced the issue and verified the fix.
2. added unit tests.
3. Confirmed that single stream detector result views remain functional post-changes.

Signed-off-by: Kaituo Li <[email protected]>
  • Loading branch information
kaituo committed Apr 29, 2024
1 parent 48acb93 commit 4f7385f
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 2 deletions.
1 change: 1 addition & 0 deletions public/pages/DetectorResults/containers/AnomalyResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ export function AnomalyResults(props: AnomalyResultsProps) {
endDate: adjustedCurrentTime.valueOf(),
} as DateRange;

// build result search query params relative to data end time
const params = buildParamsForGetAnomalyResultsWithDateRange(
featureDataPointsRange.startDate,
featureDataPointsRange.endDate
Expand Down
57 changes: 57 additions & 0 deletions public/pages/utils/__tests__/anomalyResultUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
getFeatureMissingDataAnnotations,
getFeatureDataPointsForDetector,
parsePureAnomalies,
buildParamsForGetAnomalyResultsWithDateRange,
} from '../anomalyResultUtils';
import { getRandomDetector } from '../../../redux/reducers/__tests__/utils';
import {
Expand All @@ -22,11 +23,16 @@ import {
AnomalyData,
} from '../../../models/interfaces';
import { ANOMALY_RESULT_SUMMARY, PARSED_ANOMALIES } from './constants';
import { MAX_ANOMALIES } from '../../../utils/constants';
import { SORT_DIRECTION, AD_DOC_FIELDS } from '../../../../server/utils/constants';

describe('anomalyResultUtils', () => {
let randomDetector_20_min: Detector;
let randomDetector_20_sec: Detector;
let feature_id = 'deny_max';
const startTime = 1609459200000; // January 1, 2021
const endTime = 1609545600000; // January 2, 2021

beforeAll(() => {
randomDetector_20_min = {
...getRandomDetector(true),
Expand Down Expand Up @@ -569,6 +575,57 @@ describe('anomalyResultUtils', () => {
)
).toEqual([]);
});
test('should correctly build parameters with default options', () => {
const expected = {
from: 0,
size: MAX_ANOMALIES,
sortDirection: SORT_DIRECTION.DESC,
sortField: AD_DOC_FIELDS.DATA_END_TIME,
startTime: startTime,
endTime: endTime,
fieldName: AD_DOC_FIELDS.DATA_END_TIME,
anomalyThreshold: -1,
entityList: undefined, // Default as an empty array stringified
};

const result = buildParamsForGetAnomalyResultsWithDateRange(startTime, endTime);
expect(result).toEqual(expected);
});

test('should correctly handle `anomalyOnly` and non-empty `entityList`', () => {
const entities = [{ id: '1', name: 'Entity1' }, { id: '2', name: 'Entity2' }];
const expected = {
from: 0,
size: MAX_ANOMALIES,
sortDirection: SORT_DIRECTION.DESC,
sortField: AD_DOC_FIELDS.DATA_END_TIME,
startTime: startTime,
endTime: endTime,
fieldName: AD_DOC_FIELDS.DATA_END_TIME,
anomalyThreshold: 0, // because anomalyOnly is true
entityList: JSON.stringify(entities),
};

const result = buildParamsForGetAnomalyResultsWithDateRange(startTime, endTime, true, entities);
expect(result).toEqual(expected);
});

test('should handle undefined `entityList` as an empty array JSON string', () => {
const expected = {
from: 0,
size: MAX_ANOMALIES,
sortDirection: SORT_DIRECTION.DESC,
sortField: AD_DOC_FIELDS.DATA_END_TIME,
startTime: startTime,
endTime: endTime,
fieldName: AD_DOC_FIELDS.DATA_END_TIME,
anomalyThreshold: -1, // default as anomalyOnly is false
entityList: undefined, // Default for undefined entityList
};

const result = buildParamsForGetAnomalyResultsWithDateRange(startTime, endTime, false, undefined);
expect(result).toEqual(expected);
});
});

describe('parsePureAnomalies()', () => {
Expand Down
36 changes: 34 additions & 2 deletions public/pages/utils/anomalyResultUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,38 @@ export const getLiveAnomalyResults = (
);
};

/**
* Builds search query parameters for retrieving anomaly results within a specified date range.
*
* This function constructs a parameter object for querying an anomaly detection system, filtering results
* by a given start and end time. It supports filtering anomalies based on a threshold and can limit results to
* specific entities if provided.
*
* In the context of anomaly results, the startTime and endTime parameters are used to compare against the data_end_time.
* Using data_end_time instead of data_start_time is crucial because, within HC heatmap cells, the startTime and
* endTime are derived from each cell's start and end times, which are determined based on the plotTime—coinciding
* with the data_end_time. This alignment ensures that the temporal data within each heatmap cell accurately
* reflects the intervals intended for analysis.
*
* @param startTime - The epoch time (in milliseconds) marking the start of the date range for the query.
* @param endTime - The epoch time (in milliseconds) marking the end of the date range for the query.
* @param anomalyOnly - Optional. If true, the query will return only results where anomalies are detected
* (anomaly threshold is set to 0). If false or omitted, it will include all results
* (anomaly threshold is set to -1). Default is `false`.
* @param entityList - Optional. An array of entities to filter the results. If omitted, results are not filtered
* by entities. Default is `undefined`.
*
* @returns An object containing the necessary parameters for the anomaly results search query. This object includes:
* - `from`: The starting index for fetching results (always set to 0).
* - `size`: The maximum number of anomalies to return (`MAX_ANOMALIES`).
* - `sortDirection`: The sorting order of results, set to descending (`SORT_DIRECTION.DESC`).
* - `sortField`: The field used to sort the data, set to data end time (`AD_DOC_FIELDS.DATA_END_TIME`).
* - `startTime`: Passed start time for the search range.
* - `endTime`: Passed end time for the search range.
* - `fieldName`: Field used to query the data, set to data end time (`AD_DOC_FIELDS.DATA_END_TIME`).
* - `anomalyThreshold`: The minimum score threshold for anomalies, dependent on `anomalyOnly` parameter.
* - `entityList`: A JSON string representing the list of entities to filter the results by.
*/
export const buildParamsForGetAnomalyResultsWithDateRange = (
startTime: number,
endTime: number,
Expand All @@ -128,10 +160,10 @@ export const buildParamsForGetAnomalyResultsWithDateRange = (
from: 0,
size: MAX_ANOMALIES,
sortDirection: SORT_DIRECTION.DESC,
sortField: AD_DOC_FIELDS.DATA_START_TIME,
sortField: AD_DOC_FIELDS.DATA_END_TIME,
startTime: startTime,
endTime: endTime,
fieldName: AD_DOC_FIELDS.DATA_START_TIME,
fieldName: AD_DOC_FIELDS.DATA_END_TIME,
anomalyThreshold: anomalyOnly ? 0 : -1,
entityList: JSON.stringify(entityList),
};
Expand Down

0 comments on commit 4f7385f

Please sign in to comment.