-
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
Alert list frontend pagination #57142
Conversation
96b3d0c
to
4cd62dc
Compare
997c0c7
to
f6457a0
Compare
alerts: [ | ||
{ | ||
'@timestamp': new Date(0), | ||
agent: { |
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.
do we want to have less silly data?
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 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.
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
export function flattenedPromise<T>(): [Promise<T>, PromiseResolver<T>, PromiseRejector<T>] { |
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.
maybe a comment explaining what this is/why it's needed
export const routingMiddlewareFactory: MiddlewareFactory = (coreStart, history) => { | ||
const [promise, resolve] = flattenedPromise(); | ||
|
||
return { |
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.
@paul-tavares just to follow up with the comment @peluja1012 left on your pr, this is the new route action/middleware approach we're taking, would like to hear your thoughts
@@ -65,14 +67,14 @@ const AppRoot: React.FunctionComponent<RouterProps> = React.memo(({ basename, st | |||
); | |||
}} | |||
/> | |||
<Route path="/alerts" component={AlertIndex} /> | |||
<Route path="/alerts" render={() => <AlertIndex />} /> |
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.
switched for consistency purposes and to be able to pass props down if/when needed
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.
why?
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.
@kqualters-elastic needs to pass down props and we can keep consistency with the rest of the app
}); | ||
|
||
it('should return alertListData', () => { | ||
const actual = store.getState().alerts.length; |
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.
Good test, but the name actual
seems a little ambiguous.
next(action); | ||
const state = api.getState(); | ||
if (action.type === 'userChangedUrl' && isOnAlertPage(state)) { | ||
const response: AlertResultList = await coreStart.http.post(`/api/endpoint/alerts`, { |
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.
You can always do this later, but we're going to be removing POST support for this API. If you want to try changing it now, it probably will just require changing this to .get
and then changing the the line below to query: paginationDataFromUrl(state) as HttpFetchQuery),
instead of body: JSON.stringify(paginationDataFromUrl(state)),
... I'm fine with waiting until the next PR though, whatever you feel like.
@@ -0,0 +1,12 @@ | |||
/* |
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.
We should move this file to the view/alerts
folder, as hooks are associated with views. Also I think it might be better to rename the file to something like use_alerts_selector.ts
because we will probably add more hooks in the future and I don't think we should keep them all in one file.
|
||
it("should recognize it's on the alert list page", () => { | ||
const actual = isOnAlertPage(store.getState()); | ||
expect(actual).toBeTruthy(); |
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 think .toBe(true)
applies better to this case because we expect isOnAlertPage
to return either true
or false
.
import { AlertResultList } from '../../../../../common/types'; | ||
import { isOnAlertPage } from './selectors'; | ||
|
||
describe('alert list middleware and selectors', () => { |
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.
We should update this now that you split up the tests.
urlFromNewPageIndexParam, | ||
} from './selectors'; | ||
|
||
describe('alert list middleware and selectors', () => { |
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.
We should update this now that you split up the tests.
it('loads the Alert List Page', async () => { | ||
await testSubjects.existOrFail('alertListPage'); | ||
}); | ||
it('includes policy list table', async () => { |
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.
Let's update this description 😉
{ id: 'timestamp' }, | ||
{ id: 'archived' }, | ||
{ id: 'malware_score' }, | ||
{ id: 'Alert Type' }, |
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.
You can probably keep the id
's the same as they were before. To provide friendly names to the column headers you can use the EuiDataGridColumn
display
property. So you could do something like:
{
id: 'event_type',
display: i18n.translate(
'xpack.endpoint.application.endpoint.alerts.eventType',
{
defaultMessage: 'Event Type',
}
}
const state = api.getState(); | ||
if (action.type === 'userChangedUrl' && isOnAlertPage(state)) { | ||
const response: AlertResultList = await coreStart.http.get(`/api/endpoint/alerts`, { | ||
query: paginationDataFromUrl(state) as HttpFetchQuery, |
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.
why as
?
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.
@oatkiller Cause I told him to 🙈
*/ | ||
export const paginationDataFromUrl = (state: AlertListState): qs.ParsedUrlQuery => { | ||
// Removes the `?` from the beginning of query string if it exists | ||
return state.location ? qs.parse(state.location.search.slice(1)) : {}; |
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 think you need to call out / validate each value here. otherwise a malicious actor can give an unwitting nerd a url like somekibana.net/endpoint?badStuff=encodedHacks
and then the nerd clicks the url and a request goes to kibana w/ unwitting nerd's creds but with whatever params were provided by malicious actor @dplumlee
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.
@oatkiller We are doing pretty comprehensive validation on the backend FYI. So maybe not necessary? Always helps to have an extra layer, I s'pose.
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 was also wondering this. I think once we start to grab data from the URL which is open to manipulation, we should do some sort of manipulation in order to avoid front-end code injections. I'm ok if we defer that to another Issue, but we should come up with a common approach.
const RouteCapture = memo(({ children }) => { | ||
const location: EndpointAppLocation = useLocation(); | ||
const dispatch: (action: AppAction) => unknown = useDispatch(); | ||
dispatch({ type: 'userChangedUrl', payload: location }); |
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.
applyMiddleware(alertMiddlewareFactory(coreStart), appSagaFactory(coreStart)) | ||
applyMiddleware( | ||
substateMiddlewareFactory(s => s.alertList, alertMiddlewareFactory(coreStart).middleware), | ||
appSagaFactory(coreStart) |
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.
Just FYI. This is actually a bug @parkiino fixed at one point (should just be the sagaReduxMiddleware
variable that was created on line 52). That being said, its also going away since Candace refactored it to use Middleware and remove Sagas
|
||
const json = useSelector(selectors.alertListData); | ||
const [visibleColumns, setVisibleColumns] = useState(() => columns.map(({ id }) => id)); |
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.
Why use of local state and not redux store?
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 dont know if we had a particular reason for not putting it in store beyond we didn't consider it part of the feature and it was just a side effect already built into the eui DataGrid component
return { | ||
pageIndex, | ||
pageSize, | ||
pageSizeOptions: [10, 50, 100], |
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.
We should all sync up on the values to use for page size. @parkiino and I are using [10, 20, 50]
, which I think we discussed with Bonnie.
@@ -0,0 +1,22 @@ | |||
/* |
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.
Is this still used? should it be deleted?
84c66af
to
d50c1c9
Compare
@@ -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 comment
The 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 isOnAlertsList()
?
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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
The action is dispatched anytime the URL changes - so navigating to /management
or /policy
will trigger the action.
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.
@paul-tavares We didn't want to force other teams to use this approach so we are storing the location
in the alerts piece of the store. If all of us want to use this, we can store the location at the top level of the store. The idea is to update location
every time the url changes.
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 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 comment
The 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 usePageId()
when we talked through the revised implementation around using location
provided by react-router. At least I was planning on refactoring the Policy/Management pages to use userChangedUrl
instead once this merged in.
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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
agreed 👍
* Returns a boolean based on whether or not the user is on the alerts page | ||
*/ | ||
export const isOnAlertPage = (state: AlertListState): boolean => { | ||
return state.location ? state.location.pathname.endsWith('/alerts') : false; |
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.
(optional I would suggest you do a strict ===
as in state.location.pathname === '/alerts'
08600f4
to
376da43
Compare
💚 Build SucceededHistory
To update your PR or re-run it, just comment with: |
Summary
Connects the alert list frontend to the backend api to pass alert data and pagination control. Updates the frontend routing to use and modify the url query for navigation and REST api control
Checklist
Delete any items that are not applicable to this PR.
For maintainers