-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Alert list frontend pagination #57142
Changes from all commits
4ac7212
54861c9
1fd5d96
bb92691
5a8fc38
cd53db7
e1da0a8
7129486
8ed289e
2224a25
b41a158
376da43
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/* | ||
* 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 { Store, createStore, applyMiddleware } from 'redux'; | ||
import { History } from 'history'; | ||
import { alertListReducer } from './reducer'; | ||
import { AlertListState } from '../../types'; | ||
import { alertMiddlewareFactory } from './middleware'; | ||
import { AppAction } from '../action'; | ||
import { coreMock } from 'src/core/public/mocks'; | ||
import { AlertResultList } from '../../../../../common/types'; | ||
import { isOnAlertPage } from './selectors'; | ||
import { createBrowserHistory } from 'history'; | ||
|
||
describe('alert list tests', () => { | ||
let store: Store<AlertListState, AppAction>; | ||
let coreStart: ReturnType<typeof coreMock.createStart>; | ||
let history: History<never>; | ||
beforeEach(() => { | ||
coreStart = coreMock.createStart(); | ||
history = createBrowserHistory(); | ||
const middleware = alertMiddlewareFactory(coreStart); | ||
store = createStore(alertListReducer, applyMiddleware(middleware)); | ||
}); | ||
describe('when the user navigates to the alert list page', () => { | ||
beforeEach(() => { | ||
coreStart.http.get.mockImplementation(async () => { | ||
const response: AlertResultList = { | ||
alerts: [ | ||
{ | ||
'@timestamp': new Date(1542341895000), | ||
agent: { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we want to have less silly data? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think so. You can find some sample values for these fields here https://github.com/elastic/kibana/blob/master/x-pack/plugins/endpoint/server/test_data/all_alerts_data.json. |
||
id: 'ced9c68e-b94a-4d66-bb4c-6106514f0a2f', | ||
version: '3.0.0', | ||
}, | ||
event: { | ||
action: 'open', | ||
}, | ||
file_classification: { | ||
malware_classification: { | ||
score: 3, | ||
}, | ||
}, | ||
host: { | ||
hostname: 'HD-c15-bc09190a', | ||
ip: '10.179.244.14', | ||
os: { | ||
name: 'Windows', | ||
}, | ||
}, | ||
thread: {}, | ||
}, | ||
], | ||
total: 1, | ||
request_page_size: 10, | ||
request_page_index: 0, | ||
result_from_index: 0, | ||
}; | ||
return response; | ||
}); | ||
|
||
// Simulates user navigating to the /alerts page | ||
store.dispatch({ | ||
type: 'userChangedUrl', | ||
payload: { | ||
...history.location, | ||
pathname: '/alerts', | ||
}, | ||
}); | ||
}); | ||
|
||
it("should recognize it's on the alert list page", () => { | ||
const actual = isOnAlertPage(store.getState()); | ||
expect(actual).toBe(true); | ||
}); | ||
|
||
it('should return alertListData', () => { | ||
const actualResponseLength = store.getState().alerts.length; | ||
expect(actualResponseLength).toEqual(1); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/* | ||
* 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 { Store, createStore, applyMiddleware } from 'redux'; | ||
import { History } from 'history'; | ||
import { alertListReducer } from './reducer'; | ||
import { AlertListState } from '../../types'; | ||
import { alertMiddlewareFactory } from './middleware'; | ||
import { AppAction } from '../action'; | ||
import { coreMock } from 'src/core/public/mocks'; | ||
import { createBrowserHistory } from 'history'; | ||
import { | ||
urlFromNewPageSizeParam, | ||
paginationDataFromUrl, | ||
urlFromNewPageIndexParam, | ||
} from './selectors'; | ||
|
||
describe('alert list pagination', () => { | ||
let store: Store<AlertListState, AppAction>; | ||
let coreStart: ReturnType<typeof coreMock.createStart>; | ||
let history: History<never>; | ||
beforeEach(() => { | ||
coreStart = coreMock.createStart(); | ||
history = createBrowserHistory(); | ||
const middleware = alertMiddlewareFactory(coreStart); | ||
store = createStore(alertListReducer, applyMiddleware(middleware)); | ||
}); | ||
describe('when the user navigates to the alert list page', () => { | ||
describe('when a new page size is passed', () => { | ||
beforeEach(() => { | ||
const urlPageSizeSelector = urlFromNewPageSizeParam(store.getState()); | ||
history.push(urlPageSizeSelector(1)); | ||
store.dispatch({ type: 'userChangedUrl', payload: history.location }); | ||
}); | ||
it('should modify the url correctly', () => { | ||
const actualPaginationQuery = paginationDataFromUrl(store.getState()); | ||
expect(actualPaginationQuery).toMatchInlineSnapshot(` | ||
Object { | ||
"page_size": "1", | ||
} | ||
`); | ||
}); | ||
|
||
describe('and then a new page index is passed', () => { | ||
beforeEach(() => { | ||
const urlPageIndexSelector = urlFromNewPageIndexParam(store.getState()); | ||
history.push(urlPageIndexSelector(1)); | ||
store.dispatch({ type: 'userChangedUrl', payload: history.location }); | ||
}); | ||
it('should modify the url in the correct order', () => { | ||
const actualPaginationQuery = paginationDataFromUrl(store.getState()); | ||
expect(actualPaginationQuery).toMatchInlineSnapshot(` | ||
Object { | ||
"page_index": "1", | ||
"page_size": "1", | ||
} | ||
`); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('when a new page index is passed', () => { | ||
beforeEach(() => { | ||
const urlPageIndexSelector = urlFromNewPageIndexParam(store.getState()); | ||
history.push(urlPageIndexSelector(1)); | ||
store.dispatch({ type: 'userChangedUrl', payload: history.location }); | ||
}); | ||
it('should modify the url correctly', () => { | ||
const actualPaginationQuery = paginationDataFromUrl(store.getState()); | ||
expect(actualPaginationQuery).toMatchInlineSnapshot(` | ||
Object { | ||
"page_index": "1", | ||
} | ||
`); | ||
}); | ||
|
||
describe('and then a new page size is passed', () => { | ||
beforeEach(() => { | ||
const urlPageSizeSelector = urlFromNewPageSizeParam(store.getState()); | ||
history.push(urlPageSizeSelector(1)); | ||
store.dispatch({ type: 'userChangedUrl', payload: history.location }); | ||
}); | ||
it('should modify the url correctly and reset index to `0`', () => { | ||
const actualPaginationQuery = paginationDataFromUrl(store.getState()); | ||
expect(actualPaginationQuery).toMatchInlineSnapshot(` | ||
Object { | ||
"page_index": "0", | ||
"page_size": "1", | ||
} | ||
`); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ const initialState = (): AlertListState => { | |
request_page_index: 0, | ||
result_from_index: 0, | ||
total: 0, | ||
location: undefined, | ||
}; | ||
}; | ||
|
||
|
@@ -27,6 +28,11 @@ export const alertListReducer: Reducer<AlertListState, AppAction> = ( | |
...state, | ||
...action.payload, | ||
}; | ||
} else if (action.type === 'userChangedUrl') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you are updating the Alerts store every time the URL changes - is that intentional? or did you forget to use Also, when the user leaves the alert list page, the store for it should be reset. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i suppose, only time the action is dispatched currently is if the app is already confirmed to be on the alerts page There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The action is dispatched anytime the URL changes - so navigating to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @paul-tavares We didn't want to force other teams to use this approach so we are storing the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I created an issue for clearing the state when navigating away https://github.com/elastic/endpoint-app-team/issues/183 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @peluja1012 I actually thought we had agreed to use this new approach instead of Not sure if storing it globally would facilitate things though - since currently we're building our reducers/middleware to NOT be aware of global state. Accessing the location information would be an issue thus why each concern needs to store it, no? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @paul-tavares Oh that's nice. I didn't realize we were all moving forward with this approach. It would certainly be easier the way the code is laid out now to have each concern store the location. I don't know if I like the fact that we'll need a copied and pasted reducer clause to update the location in each concern though. I would rather have location be global. We could refactor the code to allow for a global location piece of state or create a reducer helper to handle updating the location in each concern. Either way, I'd like to do this work in a separate PR. This PR is currently blocking work on Alert Details and Alerts Filtering, so I would like to get it merged as soon as we can. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agreed 👍 |
||
return { | ||
...state, | ||
location: action.payload, | ||
}; | ||
} | ||
|
||
return state; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had mentioned this in my prior comment and just wanted to again make everyone aware - this will dispatch the action potentially BEFORE the component for the matched route is rendered. I think that is ok, but just wanted everyone to be aware of that behaviour.