-
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
Add a flyout to alert list. #57926
Add a flyout to alert list. #57926
Conversation
0a15730
to
82c17c4
Compare
state: AlertListState | ||
) => (alertID: string) => string = state => { | ||
return (alertID: string) => { | ||
const urlPaginationData = { ...queryParams(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.
Why is the the variable name urlPaginationData
?
@@ -72,8 +94,38 @@ export const urlFromNewPageIndexParam: ( | |||
state: AlertListState | |||
) => (newPageIndex: number) => string = state => { | |||
return newPageIndex => { | |||
const urlPaginationData = paginationDataFromUrl(state); | |||
const urlPaginationData: AlertIndexQueryParams = { ...queryParams(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.
Do we need to spread ...queryParams(state)
?
@@ -7,9 +7,9 @@ import { KibanaRequest } from 'kibana/server'; | |||
import { EndpointAppConstants } from '../../../common/types'; | |||
import { EndpointAppContext, AlertRequestParams, JSONish } from '../../types'; | |||
|
|||
export const buildAlertListESQuery = async ( | |||
export const buildAlertListESQuery: ( |
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.
This fn used no await
so I removed async
. Might look into adding lint to enforce this.
must: [ | ||
{ | ||
match: { | ||
'event.kind': 'alert', |
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.
filter by alerts
{ | ||
'@timestamp': { | ||
order: 'desc', | ||
expect(query).toMatchInlineSnapshot(` |
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.
update the test to accommodate for the new filter
{ | ||
'@timestamp': { | ||
order: 'desc', | ||
expect(query).toMatchInlineSnapshot(` |
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.
update the test to accommodate for the new filter
</EuiPageBody> | ||
</EuiPage> | ||
<> | ||
{hasSelectedAlert && ( |
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.
If the url has a selected alert in the query string, show this.
@@ -71,7 +71,7 @@ export interface EndpointResultList { | |||
} | |||
|
|||
export interface AlertData { | |||
'@timestamp': Date; | |||
'@timestamp': string; |
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.
@alexk307 worth a discussion but: https://github.com/elastic/endpoint-app-team/issues/187
*/ | ||
export const urlWithSelectedAlert: ( | ||
state: AlertListState | ||
) => (alertID: string) => string = 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.
why do you need to nest these functions? Why not just have one function
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.
LGTM, just some questions
|
||
const handleAlertClick = useCallback( | ||
(event: React.MouseEvent<HTMLElement>) => { | ||
if (event.target instanceof HTMLElement) { |
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.
question (bc i'm still a noob at types): do we have to do this because of typescript?
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.
this actually isn't necessary. The event.target
could be any DOM node that is a descendant of event.currentTarget
aka the thing that the event listener is on. Any DOM node isn't necessarily an HTMLElement. DOM nodes don't all support alertId
.
In this case, I could just refer to event.currentTarget
. The (synthetic) event handler is registered to an html element (the one w/ the data-* attribute) and so referring to event.currentTarget
will work w/o the guard.
const handleAlertClick = useMemo(() => {
return (event: React.MouseEvent<HTMLElement>) => {
const alertId: string | undefined = event.currentTarget.dataset.alertId;
if (alertId !== undefined) {
history.push(urlWithSelectedAlert(alertId));
}
};
}, [history, urlWithSelectedAlert]);
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.
okie good to know
}; | ||
} else { | ||
return {}; | ||
export const uiQueryParams: ( |
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 will do something similar for Policy list PR that I have pending to add pagination to the URL.
In my PR I was trying to NOT hold on to the URL information passed through from React-Router, but rather (in the reducer) do the needed processing on it that Policy needs and save the the output to the store. Is that OK? or should I follow the same approach as here?
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 I understand you here, but let me know if I'm off base.
When modeling redux state, I try to record a normalized (like database normalization: https://en.wikipedia.org/wiki/Database_normalization) representation of the relevant side effects. If some calculation is done, I do it in a selector. The selector forms the read-only model for the state. Any value that can be calculated as a result of the reduced-side effects (the state) is provided by the selector. The selector (via reselect) also handles caching / invalidating the calculated views of state.
More thoughts
Redux as a pattern separates the writing/mutation/updating parts of a model from the viewing/calculating/presenting parts of the same model.
Reducer/state
- Keeps a normalized record of side effects
- Keeps data that is required by selector, or data that is required in order to update state in the future (e.g. a counter)
- Keeps data in a format that is easily updated when new side effects are introduced
- Doesn't calculate things from state. A reducer could be run 10x before the view runs a selector
- Fields of state are analogous to private instance members in a class
Selector
- Like a read-only model for the state
- Calculates 'views' on the state. Think about the 'presenter' model.
- Selectors are functions that are called at the time they are needed, do some calculation (maybe) and then memoize it (usually.)
- Selectors are like 'getters' in a class
All that having been said, I don't think the approach we have in this PR is the final goal for URL handling. I think its too prone to copy-paste errors and there is too much boilerplate. We should hit the drawing board.
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.
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 i've changed my query string stuff a lot. I think it's closer to what you have now. We should be able to come up w/ a common implementation in a PR or so I think. Let me know what you think of this one.
const value = query[key]; | ||
if (typeof value === 'string') { | ||
data[key] = value; | ||
} else if (Array.isArray(value)) { |
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.
😱 forgot about this. I will add awareness of it to my PR
@@ -84,6 +99,30 @@ export const AlertIndex = memo(() => { | |||
); | |||
|
|||
const [visibleColumns, setVisibleColumns] = useState(() => columns.map(({ id }) => id)); | |||
const formatter = new Intl.DateTimeFormat(i18n.getLocale(), { |
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.
use <FormattedDate>
and/or <FormattedTime>
from @kbn/i18n/react
instead of a local formatter?
Also - Just FYI: in Policy list we discussed displaying date/time in relative format if within the last 1h and formatted otherwise. I have that in policy_list
now, but if common requirement across all of our pages, then we should move/adjust/improve it 😄
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 to <FormattedDate />
, its equivalent
if (event.target instanceof HTMLElement) { | ||
const alertId: string | undefined = event.target.dataset.alertId; | ||
if (alertId !== undefined) { | ||
history.push(urlWithSelectedAlert(alertId)); |
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 did not realize that history.push
handled URL that started with ?
. I was using useLocation()
to grab the current pathname
in mine. I will refactor to remove it.
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.
history will interpret urls as relative to the current one.
// ran this on this page via console
history.pushState(null, '', '?cool=true')
// url is now: 'https://github.com/elastic/kibana/pull/57926/files?cool=true'
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 i switched my implementation to use LocationDescriptionObject because its easier
defaultMessage: 'Malicious File', | ||
} | ||
return ( | ||
<EuiLink data-alert-id={'TODO'} onClick={handleAlertClick}> |
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.
should the URL be set on this item? (allowing user's to do control click to new window or copy URL to clipboard)?
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.
Thanks for the tip. After realizing that EuiLink
returns a button, I didn't even think about trying to use href
'. If href
works, that'll simplify things a lot.
/** | ||
* Returns a url like the current one, but with a new alert id. | ||
*/ | ||
export const urlWithSelectedAlert: ( |
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.
Should these be named something like urlSearchParams*
to be more reflective of what it is returning?
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 view this as returning a relative URL. It is a string, beginning with ?
and so it fits the technical def: https://tools.ietf.org/html/rfc1808#section-5
The intent is that these will be used with history.pushState
or the href
attribute of an a
tag. These accept relative URLs and treat them as absolute after combining them with the base URL of the page.
Maybe I could explain the above in a comment. With all that being said, do you still think I should go for a diff name?
c0bb87b
to
7dc1ecf
Compare
|
||
/** | ||
* Returns a function that takes in a new page index and returns a new query param string | ||
*/ | ||
export const urlFromNewPageIndexParam: ( | ||
state: AlertListState | ||
) => (newPageIndex: number) => string = state => { | ||
) => (newPageIndex: number) => string = createSelector(uiQueryParams, paramData => { |
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.
@alexk307 I missed your earlier comment, so replying here.
Selectors have a common interface, they take the state of the app (what is returned by the top level reducer) and return whatever they want. The return value should be a primitive, or treated as immutable as its used to invalidate views and things.
These selectors want to take a second param. In this case, the page number for the link they want. But since selectors always take exactly one argument, we use a thunk instead.
Helpers that take a selector, e.g. useSelector
, createStructuredSelector
, createSelector
, etc, all still work fine, since we are still a standard (odd) selector. Its not a pattern i like to use too often, but we've got a whole bunch here (and in Resolver.)
@@ -63,6 +64,10 @@ export interface GlobalState { | |||
readonly policyList: PolicyListState; | |||
} | |||
|
|||
/** | |||
* A better type for createStructuredSelector. This doesn't support the options object. | |||
* TODO |
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.
TODO
substateMiddlewareFactory( | ||
globalState => globalState.alertList, | ||
alertMiddlewareFactory(coreStart) | ||
disableMiddleware |
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 put a comment in here saying why you need the ability to disable the middleware?
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.
durp
Pinging @elastic/endpoint-response (Team:Endpoint Response) |
request_page_index: 0, | ||
result_from_index: 0, | ||
}; | ||
const response: AlertResultList = mockAlertResultList(); | ||
return response; | ||
}); | ||
|
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.
whitespace
…react router also, use `href` instead of a button
a160717
to
9b0791d
Compare
disable test i broke. lets rethink this
9b0791d
to
626e2fb
Compare
[history, urlFromNewPageIndexParam] | ||
newPageIndex => { | ||
return history.push( | ||
urlFromQueryParams({ |
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 doing something closer to what you have.
@dplumlee @peluja1012 thoughts?
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.
this works for me. maybe we should also use the same querystring package that @paul-tavares is using (query-string
) just to keep it consistent. I think it's in kibana already and it's a bit better than the one we're using
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 looks good but closing the flyout doesn't seem to be working when only selected_alert
is present. The user gets redirected to /app/endpoint/
:
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 goofed this up. I'm working on another slight improvement, but I feel like I might just be getting in the way of stuff @paul-tavares is doing. Maybe worth syncing
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.
Fixed
@paul-tavares @peluja1012 @kqualters-elastic @dplumlee @parkiino Changed the way query strings are done, fixed tests and stuff. Comments and things might be sloppy, @dplumlee and @parkiino will fix that :) |
); | ||
|
||
const [visibleColumns, setVisibleColumns] = useState(() => columns.map(({ id }) => id)); | ||
|
||
const handleFlyoutClose = useCallback(() => { | ||
const { selected_alert, ...paramsWithoutSelectedAlert } = queryParams; |
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.
are we doing url params in snake case? is that the convention? i forget...
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.
…_improve-advanced-settings-save * commit '02efb01c481f9f24d8d707f06dfc68b2fb805001': (43 commits) [Endpoint] Add a flyout to alert list. (elastic#57926) Make sure index pattern has fields before parsing (elastic#58242) Sanitize workpad before sending to API (elastic#57704) [ML] Transform: Support multi-line JSON notation in advanced editor (elastic#58015) [Endpoint] Refactor Management List Tests (elastic#58148) [kbn/optimizer] include bootstrap cache key in optimizer cache key (elastic#58176) Do not refresh color scale on each lookup (elastic#57792) Updating to @elastic/[email protected] (elastic#54662) Trigger context (elastic#57870) [ML] Transforms: Adds clone feature to transforms list. (elastic#57837) [ML] New Platform server shim: update fields service routes (elastic#58060) [Endpoint] EMT-184: change endpoints to metadata up and down the code base. (elastic#58038) document difference between log record formats (elastic#57798) Expose elasticsearch config schema (elastic#57655) [ui/agg_response/tabify] update types for search/expressions/build_tabular_inspector_data.ts (elastic#58130) [SIEM] Cleans Cypress tests code (elastic#58134) fix: 🐛 make dev server Storybook builds work again (elastic#58188) Prevent core savedObjects plugin from being overridden (elastic#58193) Expose serverBasePath on client-side (elastic#58070) Fix legend sizing on area charts (elastic#58083) ...
…-out-of-legacy * 'master' of github.com:elastic/kibana: [SIEM] [Case] Enable case by default. Snake to camel on UI (elastic#57936) [File upload] Update remaining File Upload dependencies for NP migration (elastic#58128) Use EuiTokens for ES field types (elastic#57911) Added UI support for the default action group for Alert Type Model (elastic#57603) force savedObject API consumers to define SO type explicitly (elastic#58022) Update dependency @elastic/charts to ^17.1.1 (elastic#57634) [Endpoint] Add a flyout to alert list. (elastic#57926) Make sure index pattern has fields before parsing (elastic#58242) Sanitize workpad before sending to API (elastic#57704) [ML] Transform: Support multi-line JSON notation in advanced editor (elastic#58015) [Endpoint] Refactor Management List Tests (elastic#58148) [kbn/optimizer] include bootstrap cache key in optimizer cache key (elastic#58176) Do not refresh color scale on each lookup (elastic#57792) Updating to @elastic/[email protected] (elastic#54662) Trigger context (elastic#57870) [ML] Transforms: Adds clone feature to transforms list. (elastic#57837) [ML] New Platform server shim: update fields service routes (elastic#58060) [Endpoint] EMT-184: change endpoints to metadata up and down the code base. (elastic#58038)
💚 Build SucceededHistory
To update your PR or re-run it, just comment with: |
Summary
Add a button to alert list that adds
selected_alert
query string to url. if qs is present, flyout shows up. No data loading logic added yet.Related to https://github.com/elastic/endpoint-app-team/issues/92
Issues
The tests for this PR emit warnings. These don't effect the test. I believe they are being caused by
useEffect
calls inEuiDataGrid
that set state right after the first render, causing a subsequent render in many cases, but that's just a guess. I . haven't been able to work out a solution with #eui yet.https://github.com/elastic/endpoint-app-team/issues/205
This uses our custom type for
createStructuredSelector
, but the type isn't completely compatible w/ the implementation:https://github.com/elastic/endpoint-app-team/issues/204
And here are a bunch of other issues created during the development of this PR:
Checklist
Delete any items that are not applicable to this PR.
For maintainers