diff --git a/public/pages/DetectorsList/List/__tests__/List.test.tsx b/public/pages/DetectorsList/List/__tests__/List.test.tsx
index 00b78ffc..ed19a546 100644
--- a/public/pages/DetectorsList/List/__tests__/List.test.tsx
+++ b/public/pages/DetectorsList/List/__tests__/List.test.tsx
@@ -137,6 +137,7 @@ describe(' spec', () => {
name: `detector_name_${index}`,
indices: [`index_${index}`],
curState: DETECTOR_STATE.DISABLED,
+ featureAttributes: [`feature_${index}`],
totalAnomalies: index,
lastActiveAnomaly: moment('2020-04-15T09:00:00')
.add(index, 'minutes')
@@ -160,12 +161,7 @@ describe(' spec', () => {
},
});
- const {
- getByText,
- getAllByTestId,
- queryByText,
- getAllByText,
- } = renderWithRouter({
+ const { getByText, getAllByTestId, queryByText } = renderWithRouter({
...initialDetectorsState,
requesting: true,
});
@@ -191,13 +187,14 @@ describe(' spec', () => {
expect(queryByText('index_0')).toBeNull();
// Sort by detector state (enum sorting)
+ // NOTE: this is assuming DETECTOR_STATE.RUNNING is higher alphabetically ('running')
+ // than DETECTOR_STATE.DISABLED ('stopped')
userEvent.click(getAllByTestId('tableHeaderSortButton')[2]);
await wait();
- getAllByText(DETECTOR_STATE.DISABLED);
- expect(queryByText(DETECTOR_STATE.RUNNING)).toBeNull();
+ expect(queryByText(DETECTOR_STATE.RUNNING)).not.toBeNull();
userEvent.click(getAllByTestId('tableHeaderSortButton')[2]);
await wait();
- expect(queryByText(DETECTOR_STATE.RUNNING)).not.toBeNull();
+ expect(queryByText(DETECTOR_STATE.RUNNING)).toBeNull();
// Sort by totalAnomalies (numeric sorting)
userEvent.click(getAllByTestId('tableHeaderSortButton')[3]);
@@ -216,22 +213,22 @@ describe(' spec', () => {
// Sort by last anomaly occurrence (date sorting)
userEvent.click(getAllByTestId('tableHeaderSortButton')[4]);
await wait();
- getByText('04/15/2020 9:00 am');
- expect(queryByText('04/15/2020 9:30 am')).toBeNull();
+ getByText('04/15/2020 9:00 AM');
+ expect(queryByText('04/15/2020 9:30 AM')).toBeNull();
userEvent.click(getAllByTestId('tableHeaderSortButton')[4]);
await wait();
- getByText('04/15/2020 9:30 am');
- expect(queryByText('04/15/2020 9:00 am')).toBeNull();
+ getByText('04/15/2020 9:30 AM');
+ expect(queryByText('04/15/2020 9:00 AM')).toBeNull();
// Sort by last updated (date sorting)
userEvent.click(getAllByTestId('tableHeaderSortButton')[5]);
await wait();
- getByText('04/15/2020 7:00 am');
- expect(queryByText('04/15/2020 7:30 am')).toBeNull();
+ getByText('04/15/2020 7:00 AM');
+ expect(queryByText('04/15/2020 7:30 AM')).toBeNull();
userEvent.click(getAllByTestId('tableHeaderSortButton')[5]);
await wait();
- getByText('04/15/2020 7:30 am');
- expect(queryByText('04/15/2020 7:00 am')).toBeNull();
+ getByText('04/15/2020 7:30 AM');
+ expect(queryByText('04/15/2020 7:00 AM')).toBeNull();
});
test('should be able to search', async () => {
const randomDetectors = new Array(40).fill(null).map((_, index) => {
@@ -328,15 +325,15 @@ describe(' spec', () => {
getByText(randomDetectors[0].indices[0]);
getByText(randomDetectors[0].curState);
getByText(randomDetectors[0].totalAnomalies.toString());
- getByText('10/19/2019 9:00 am');
- getByText('10/19/2019 7:00 am');
+ getByText('10/19/2019 9:00 AM');
+ getByText('10/19/2019 7:00 AM');
//Test3 Detector
getByText(randomDetectors[2].name);
getByText(randomDetectors[2].indices[0]);
getByText(randomDetectors[2].curState);
getByText(randomDetectors[2].totalAnomalies.toString());
- getByText('10/19/2019 9:30 am');
- getByText('10/19/2019 7:30 am');
+ getByText('10/19/2019 9:30 AM');
+ getByText('10/19/2019 7:30 AM');
});
});
});
diff --git a/public/pages/DetectorsList/utils/tableUtils.tsx b/public/pages/DetectorsList/utils/tableUtils.tsx
index ee94c57c..eef3cd69 100644
--- a/public/pages/DetectorsList/utils/tableUtils.tsx
+++ b/public/pages/DetectorsList/utils/tableUtils.tsx
@@ -18,7 +18,7 @@ import { EuiIcon, EuiLink, EuiToolTip, EuiHealth } from '@elastic/eui';
import moment from 'moment';
import get from 'lodash/get';
import React from 'react';
-import { Detector } from '../../../../server/models/types';
+import { Detector } from '../../../models/interfaces';
import { PLUGIN_NAME, DETECTOR_STATE } from '../../../utils/constants';
import { stateToColorMap } from '../../utils/constants';
import { darkModeEnabled } from '../../../utils/kibanaUtils';
@@ -29,7 +29,7 @@ const hintColor = darkModeEnabled() ? '#98A2B3' : '#535966';
const renderTime = (time: number) => {
const momentTime = moment(time);
if (time && momentTime.isValid())
- return momentTime.format('MM/DD/YYYY h:mm a');
+ return momentTime.format('MM/DD/YYYY h:mm A');
return DEFAULT_EMPTY_DATA;
};
@@ -37,7 +37,7 @@ const renderIndices = (indices: string[]) => {
return get(indices, '0', DEFAULT_EMPTY_DATA);
};
-const renderState = (state: DETECTOR_STATE) => {
+const renderState = (state: DETECTOR_STATE, detector: Detector) => {
return (
//@ts-ignore
{state}
@@ -64,6 +64,7 @@ export const staticColumn = [
truncateText: true,
textOnly: true,
align: 'left',
+ width: '15%',
render: (name: string, detector: Detector) => (
{name}
@@ -89,6 +90,7 @@ export const staticColumn = [
truncateText: true,
textOnly: true,
align: 'left',
+ width: '15%',
render: renderIndices,
},
{
@@ -153,6 +155,7 @@ export const staticColumn = [
dataType: 'date',
truncateText: false,
align: 'left',
+ width: '16%',
render: renderTime,
},
{
@@ -174,6 +177,7 @@ export const staticColumn = [
dataType: 'date',
truncateText: false,
align: 'left',
+ width: '16%',
render: renderTime,
},
];
diff --git a/public/pages/utils/constants.ts b/public/pages/utils/constants.ts
index 47d9aed9..389be975 100644
--- a/public/pages/utils/constants.ts
+++ b/public/pages/utils/constants.ts
@@ -20,6 +20,7 @@ export enum DETECTOR_STATE_COLOR {
DISABLED = 'subdued',
INIT = '#0000cc',
RUNNING = 'success',
+ FEATURE_REQUIRED = 'subdued',
INIT_FAILURE = 'danger',
UNEXPECTED_FAILURE = 'danger',
}
@@ -28,6 +29,7 @@ export const stateToColorMap = new Map()
.set(DETECTOR_STATE.DISABLED, DETECTOR_STATE_COLOR.DISABLED)
.set(DETECTOR_STATE.INIT, DETECTOR_STATE_COLOR.INIT)
.set(DETECTOR_STATE.RUNNING, DETECTOR_STATE_COLOR.RUNNING)
+ .set(DETECTOR_STATE.FEATURE_REQUIRED, DETECTOR_STATE_COLOR.FEATURE_REQUIRED)
.set(DETECTOR_STATE.INIT_FAILURE, DETECTOR_STATE_COLOR.INIT_FAILURE)
.set(
DETECTOR_STATE.UNEXPECTED_FAILURE,
diff --git a/public/utils/constants.ts b/public/utils/constants.ts
index ded4fb68..2110daba 100644
--- a/public/utils/constants.ts
+++ b/public/utils/constants.ts
@@ -51,6 +51,7 @@ export enum DETECTOR_STATE {
DISABLED = 'Stopped',
INIT = 'Initializing',
RUNNING = 'Running',
+ FEATURE_REQUIRED = 'Feature required',
INIT_FAILURE = 'Initialization failure',
UNEXPECTED_FAILURE = 'Unexpected failure',
}
diff --git a/server/routes/ad.ts b/server/routes/ad.ts
index b66135cb..927b78ad 100644
--- a/server/routes/ad.ts
+++ b/server/routes/ad.ts
@@ -356,7 +356,6 @@ const getDetectors = async (
description: get(detector, '_source.description', ''),
indices: get(detector, '_source.indices', []),
lastUpdateTime: get(detector, '_source.last_update_time', 0),
- // TODO: get the state of the detector once possible (enabled/disabled for now)
...convertDetectorKeysToCamelCase(get(detector, '_source', {})),
},
}),
@@ -445,8 +444,8 @@ const getDetectors = async (
detectorState.state = DETECTOR_STATE[detectorState.state];
});
- // check if there was any failures
- detectorStates.forEach(detectorState => {
+ // check if there was any failures / detectors that are unable to start
+ detectorStates.forEach((detectorState, i) => {
/*
If the error starts with 'Stopped detector', then an EndRunException was thrown.
All EndRunExceptions are related to initialization failures except for the
@@ -461,6 +460,16 @@ const getDetectors = async (
? DETECTOR_STATE.UNEXPECTED_FAILURE
: DETECTOR_STATE.INIT_FAILURE;
}
+
+ /*
+ If a detector has no features, set to a feature required state
+ */
+ if (
+ detectorState.state === DETECTOR_STATE.DISABLED &&
+ finalDetectors[i].featureAttributes.length === 0
+ ) {
+ detectorState.state = DETECTOR_STATE.FEATURE_REQUIRED;
+ }
});
// update the final detectors to include the detector state