forked from elastic/kibana
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ML] Data frame analytics: Hook unit tests. (elastic#43199)
- moves use_create_analytics_form.ts inside a use_create_analytics_form directory to be able to split up the large file - moves code related to the plain reducer function from ``use_create_analytics_form.tsto its own filereducer.ts` - adds unit tests for use_create_analytics_form.ts and reducer.ts. - Changes the button and modal header text from 'Create data frame analytics job' to 'Create outlier detection job'.
- Loading branch information
Showing
7 changed files
with
338 additions
and
181 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
.../data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
export { useCreateAnalyticsForm, CreateAnalyticsFormProps } from './use_create_analytics_form'; |
62 changes: 62 additions & 0 deletions
62
...rame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/* | ||
* 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 { getInitialState, reducer, ACTION } from './reducer'; | ||
|
||
describe('useCreateAnalyticsForm', () => { | ||
test('reducer(): provide a minimum required valid job config, then reset.', () => { | ||
const initialState = getInitialState(); | ||
expect(initialState.isValid).toBe(false); | ||
|
||
const updatedState = reducer(initialState, { | ||
type: ACTION.SET_FORM_STATE, | ||
payload: { | ||
destinationIndex: 'the-destination-index', | ||
jobId: 'the-analytics-job-id', | ||
sourceIndex: 'the-source-index', | ||
}, | ||
}); | ||
expect(updatedState.isValid).toBe(true); | ||
|
||
const resettedState = reducer(updatedState, { | ||
type: ACTION.RESET_FORM, | ||
}); | ||
expect(resettedState).toEqual(initialState); | ||
}); | ||
|
||
test('reducer(): open/close the modal', () => { | ||
const initialState = getInitialState(); | ||
expect(initialState.isModalVisible).toBe(false); | ||
|
||
const openModalState = reducer(initialState, { | ||
type: ACTION.OPEN_MODAL, | ||
}); | ||
expect(openModalState.isModalVisible).toBe(true); | ||
|
||
const closedModalState = reducer(openModalState, { | ||
type: ACTION.CLOSE_MODAL, | ||
}); | ||
expect(closedModalState.isModalVisible).toBe(false); | ||
}); | ||
|
||
test('reducer(): add/reset request messages', () => { | ||
const initialState = getInitialState(); | ||
expect(initialState.requestMessages).toHaveLength(0); | ||
|
||
const requestMessageState = reducer(initialState, { | ||
type: ACTION.ADD_REQUEST_MESSAGE, | ||
requestMessage: { | ||
message: 'the-message', | ||
}, | ||
}); | ||
expect(requestMessageState.requestMessages).toHaveLength(1); | ||
|
||
const resetMessageState = reducer(requestMessageState, { | ||
type: ACTION.RESET_REQUEST_MESSAGES, | ||
}); | ||
expect(resetMessageState.requestMessages).toHaveLength(0); | ||
}); | ||
}); |
179 changes: 179 additions & 0 deletions
179
...ata_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
/* | ||
* 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 { isValidIndexName } from '../../../../../../common/util/es_utils'; | ||
import { checkPermission } from '../../../../../privilege/check_privilege'; | ||
|
||
import { isAnalyticsIdValid, DataFrameAnalyticsId } from '../../../../common'; | ||
|
||
export type EsIndexName = string; | ||
export type IndexPatternTitle = string; | ||
|
||
export interface RequestMessage { | ||
error?: string; | ||
message: string; | ||
} | ||
|
||
export interface State { | ||
createIndexPattern: boolean; | ||
destinationIndex: EsIndexName; | ||
destinationIndexNameExists: boolean; | ||
destinationIndexNameEmpty: boolean; | ||
destinationIndexNameValid: boolean; | ||
destinationIndexPatternTitleExists: boolean; | ||
disabled: boolean; | ||
indexNames: EsIndexName[]; | ||
indexPatternTitles: IndexPatternTitle[]; | ||
indexPatternsWithNumericFields: IndexPatternTitle[]; | ||
isJobCreated: boolean; | ||
isJobStarted: boolean; | ||
isModalButtonDisabled: boolean; | ||
isModalVisible: boolean; | ||
isValid: boolean; | ||
jobId: DataFrameAnalyticsId; | ||
jobIds: DataFrameAnalyticsId[]; | ||
jobIdExists: boolean; | ||
jobIdEmpty: boolean; | ||
jobIdValid: boolean; | ||
requestMessages: RequestMessage[]; | ||
sourceIndex: EsIndexName; | ||
sourceIndexNameExists: boolean; | ||
sourceIndexNameEmpty: boolean; | ||
sourceIndexNameValid: boolean; | ||
} | ||
|
||
export const getInitialState = (): State => ({ | ||
createIndexPattern: false, | ||
destinationIndex: '', | ||
destinationIndexNameExists: false, | ||
destinationIndexNameEmpty: true, | ||
destinationIndexNameValid: false, | ||
destinationIndexPatternTitleExists: false, | ||
disabled: | ||
!checkPermission('canCreateDataFrameAnalytics') || | ||
!checkPermission('canStartStopDataFrameAnalytics'), | ||
indexNames: [], | ||
indexPatternTitles: [], | ||
indexPatternsWithNumericFields: [], | ||
isJobCreated: false, | ||
isJobStarted: false, | ||
isModalVisible: false, | ||
isModalButtonDisabled: false, | ||
isValid: false, | ||
jobId: '', | ||
jobIds: [], | ||
jobIdExists: false, | ||
jobIdEmpty: true, | ||
jobIdValid: false, | ||
requestMessages: [], | ||
sourceIndex: '', | ||
sourceIndexNameExists: false, | ||
sourceIndexNameEmpty: true, | ||
sourceIndexNameValid: false, | ||
}); | ||
|
||
const validate = (state: State): State => { | ||
state.isValid = | ||
!state.jobIdEmpty && | ||
state.jobIdValid && | ||
!state.jobIdExists && | ||
!state.sourceIndexNameEmpty && | ||
state.sourceIndexNameValid && | ||
!state.destinationIndexNameEmpty && | ||
state.destinationIndexNameValid && | ||
(!state.destinationIndexPatternTitleExists || !state.createIndexPattern); | ||
|
||
return state; | ||
}; | ||
|
||
export enum ACTION { | ||
ADD_REQUEST_MESSAGE = 'add_request_message', | ||
RESET_REQUEST_MESSAGES = 'reset_request_messages', | ||
CLOSE_MODAL = 'close_modal', | ||
OPEN_MODAL = 'open_modal', | ||
RESET_FORM = 'reset_form', | ||
SET_FORM_STATE = 'set_form_state', | ||
} | ||
|
||
export type Action = | ||
| { type: ACTION.ADD_REQUEST_MESSAGE; requestMessage: RequestMessage } | ||
| { type: ACTION.RESET_REQUEST_MESSAGES } | ||
| { type: ACTION.CLOSE_MODAL } | ||
| { type: ACTION.OPEN_MODAL } | ||
| { type: ACTION.RESET_FORM } | ||
| { type: ACTION.SET_FORM_STATE; payload: Partial<State> }; | ||
|
||
export function reducer(state: State, action: Action): State { | ||
switch (action.type) { | ||
case ACTION.ADD_REQUEST_MESSAGE: | ||
state.requestMessages.push(action.requestMessage); | ||
return state; | ||
|
||
case ACTION.RESET_REQUEST_MESSAGES: | ||
return { ...state, requestMessages: [] }; | ||
|
||
case ACTION.CLOSE_MODAL: | ||
return { ...state, isModalVisible: false }; | ||
|
||
case ACTION.OPEN_MODAL: | ||
return { ...state, isModalVisible: true }; | ||
|
||
case ACTION.RESET_FORM: | ||
return getInitialState(); | ||
|
||
case ACTION.SET_FORM_STATE: | ||
const newState = { ...state, ...action.payload }; | ||
|
||
// update state attributes which are derived from other state attributes. | ||
if (action.payload.destinationIndex !== undefined) { | ||
newState.destinationIndexNameExists = newState.indexNames.some( | ||
name => newState.destinationIndex === name | ||
); | ||
newState.destinationIndexNameEmpty = newState.destinationIndex === ''; | ||
newState.destinationIndexNameValid = isValidIndexName(newState.destinationIndex); | ||
newState.destinationIndexPatternTitleExists = newState.indexPatternTitles.some( | ||
name => newState.destinationIndex === name | ||
); | ||
} | ||
|
||
if (action.payload.indexNames !== undefined) { | ||
newState.destinationIndexNameExists = newState.indexNames.some( | ||
name => newState.destinationIndex === name | ||
); | ||
newState.sourceIndexNameExists = newState.indexNames.some( | ||
name => newState.sourceIndex === name | ||
); | ||
} | ||
|
||
if (action.payload.indexPatternTitles !== undefined) { | ||
newState.destinationIndexPatternTitleExists = newState.indexPatternTitles.some( | ||
name => newState.destinationIndex === name | ||
); | ||
} | ||
|
||
if (action.payload.jobId !== undefined) { | ||
newState.jobIdExists = newState.jobIds.some(id => newState.jobId === id); | ||
newState.jobIdEmpty = newState.jobId === ''; | ||
newState.jobIdValid = isAnalyticsIdValid(newState.jobId); | ||
} | ||
|
||
if (action.payload.jobIds !== undefined) { | ||
newState.jobIdExists = newState.jobIds.some(id => newState.jobId === id); | ||
} | ||
|
||
if (action.payload.sourceIndex !== undefined) { | ||
newState.sourceIndexNameExists = newState.indexNames.some( | ||
name => newState.sourceIndex === name | ||
); | ||
newState.sourceIndexNameEmpty = newState.sourceIndex === ''; | ||
newState.sourceIndexNameValid = isValidIndexName(newState.sourceIndex); | ||
} | ||
|
||
return validate(newState); | ||
} | ||
|
||
return state; | ||
} |
77 changes: 77 additions & 0 deletions
77
...s/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/* | ||
* 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 from 'react'; | ||
import { mountHook } from 'test_utils/enzyme_helpers'; | ||
|
||
import { KibanaContext } from '../../../../../contexts/kibana'; | ||
import { kibanaContextValueMock } from '../../../../../contexts/kibana/__mocks__/kibana_context_value'; | ||
|
||
import { getErrorMessage, useCreateAnalyticsForm } from './use_create_analytics_form'; | ||
|
||
const getMountedHook = () => | ||
mountHook( | ||
() => useCreateAnalyticsForm(), | ||
({ children }) => ( | ||
<KibanaContext.Provider value={kibanaContextValueMock}>{children}</KibanaContext.Provider> | ||
) | ||
); | ||
|
||
describe('getErrorMessage()', () => { | ||
test('verify error message response formats', () => { | ||
const errorMessage = getErrorMessage(new Error('the-error-message')); | ||
expect(errorMessage).toBe('the-error-message'); | ||
|
||
const customError1 = { customErrorMessage: 'the-error-message' }; | ||
const errorMessageMessage1 = getErrorMessage(customError1); | ||
expect(errorMessageMessage1).toBe('{"customErrorMessage":"the-error-message"}'); | ||
|
||
const customError2 = { message: 'the-error-message' }; | ||
const errorMessageMessage2 = getErrorMessage(customError2); | ||
expect(errorMessageMessage2).toBe('the-error-message'); | ||
}); | ||
}); | ||
|
||
describe('useCreateAnalyticsForm()', () => { | ||
test('initialization', () => { | ||
const { getLastHookValue } = getMountedHook(); | ||
const { state, actions } = getLastHookValue(); | ||
|
||
expect(state.isModalVisible).toBe(false); | ||
expect(typeof actions.closeModal).toBe('function'); | ||
expect(typeof actions.createAnalyticsJob).toBe('function'); | ||
expect(typeof actions.openModal).toBe('function'); | ||
expect(typeof actions.startAnalyticsJob).toBe('function'); | ||
expect(typeof actions.setFormState).toBe('function'); | ||
}); | ||
|
||
test('open/close modal', () => { | ||
const { act, getLastHookValue } = getMountedHook(); | ||
const { state, actions } = getLastHookValue(); | ||
|
||
expect(state.isModalVisible).toBe(false); | ||
|
||
act(() => { | ||
// this should be actions.openModal(), but that doesn't work yet because act() doesn't support async yet. | ||
// we need to wait for an update to React 16.9 | ||
actions.setFormState({ isModalVisible: true }); | ||
}); | ||
const { state: stateModalOpen } = getLastHookValue(); | ||
expect(stateModalOpen.isModalVisible).toBe(true); | ||
|
||
act(() => { | ||
// this should be actions.closeModal(), but that doesn't work yet because act() doesn't support async yet. | ||
// we need to wait for an update to React 16.9 | ||
actions.setFormState({ isModalVisible: false }); | ||
}); | ||
const { state: stateModalClosed } = getLastHookValue(); | ||
expect(stateModalClosed.isModalVisible).toBe(false); | ||
}); | ||
|
||
// TODO | ||
// add tests for createAnalyticsJob() and startAnalyticsJob() | ||
// once React 16.9 with support for async act() is available. | ||
}); |
Oops, something went wrong.