-
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
[App Search] Engine Overview server route & Logic file #83353
Conversation
8d53954
to
b774722
Compare
totalClicks: number; | ||
totalQueries: number; | ||
} | ||
interface EngineOverviewValues extends EngineOverviewApiData { |
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.
FYI - this drops/cleans up a good amount of values from ent-search:
-
apiKey
&apiUrl
- this wasn't being used anywhere, not actually sure where it was coming from (maybe leftover from old logic).apiKey
should just be grabbed directly fromEngineLogic.values.engine.apiKey
in any case. -
accountKey
- no longer applies in a SMAS world -
role
- also not used anywhere AFAICT, should just be pulled directly fromAppLogic.values.myRole
-
pollingCancelTokenSource
- per a discussion w/ Byron, we will no longer be using/storing axios cancel tokens -
showDocumentCreationModal
&creationMode
- I'm going to be spiking out something in a few PRs here where DocumentCreationModal handles its own show/hide state and creation mode state instead of parent views doing it in multiple places. -
activeApiLog
- Same as the above, I believe ApiLogTables should be in charge of managing the state of its details modal, not the parent. I'm removing it for now on that premise, but if for any reason my belief is incorrect we can come back in later and add it
import { HttpLogic } from '../../../shared/http'; | ||
import { EngineLogic } from '../engine'; | ||
|
||
const POLLING_DURATION = 5000; |
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.
Per a convo in the front-end channel, I think we're just gonna set per-view POLLING_DURATION
timing instead of trying to share a single const. We can come back later and look at this if this changes for any reason
jest.mock('../../../shared/http', () => ({ | ||
HttpLogic: { values: mockHttpValues }, | ||
})); | ||
const { http } = mockHttpValues; | ||
|
||
jest.mock('../../../shared/flash_messages', () => ({ | ||
flashAPIErrors: jest.fn(), | ||
})); | ||
import { flashAPIErrors } from '../../../shared/flash_messages'; | ||
|
||
jest.mock('../engine', () => ({ |
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 would really like to figure out someday how to DRY out all these mocks to a reusable helper, especially for the shared ones, but I'm not sure we'll ever be able to because of the relative ../
paths.
Maybe someday we can ask the Kibana team if absolute webpack alias's are possible for xpack plugins - we can but dream!
}), | ||
events: ({ values }) => ({ | ||
beforeUnmount() { | ||
if (values.timeoutId !== null) clearTimeout(values.timeoutId); |
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 saw a post about nullish operators/assignment on Twitter recently and almost kinda wanted to try it out here:
if (values.timeoutId !== null) clearTimeout(values.timeoutId); | |
values.timeoutId ?? clearTimeout(values.timeoutId); |
But it felt kinda weird to read and Typescript didn't like it. haha
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 wonder why TS didn't like it?
One interesting thing...
In the former, if values.timeoutId
is undefined
, clearTimeout
would be called.
In the later, if values.timeoutId
is undefined
, clearTimeout
would NOT be called.
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.
Looking at the code now, I realize that is irrelevant, because timeoutId would never be null
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.
TS probably isn't yet fully updated for nullish operators is my guess.
timeoutId would never be null
I think you meant undefined
here? If so, correct, a redux value can never be undefined
or the entire app just crashes
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's often a large disconnect between my brain and my fingers 😬
@@ -60,4 +60,19 @@ export function registerEnginesRoutes({ | |||
})(context, request, response); | |||
} | |||
); | |||
router.get( | |||
{ | |||
path: '/api/app_search/engines/{name}/overview', |
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 mentioned this before, I'll mention it again to see what you think.
I don't particularly like renaming routes. If this forwards to /overview_metrics
in Enterprise Search, I think it should also be /overview_metrics
in the Kibana API.
It just simplifies things, IMO. One less thing to think about. If you see /overview_metrics
throwing an error, it's simpler to just go look for the /overview_metrics
API endpoint in the backend to debug, rather than first checking the server mapping to see what the real API endpoint is on the backend to debug.
That's my totally subjective opinion. Feel free to disregard and keep it as is. I don't think it ultimately will make a big difference one way or the other.
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.
That's a super fair point - what I was thinking originally when looking at this endpoint / view was:
- What guarantees that our Overview page will only ever show metrics in the future? Why not show/pass other non-metric information?
- If we ever do at that point, we would (likely?) change the name of the endpoint, and a generic
/overview
(to match the page) is more future-proof
That being said, I'm hoping we take a second pass at all our API endpoints and where possible convert internal ones to standard API endpoints in some distant future - at that point it would be great to look at names as well.
}, | ||
onPollingSuccess: (engineMetrics) => { | ||
const timeoutId = window.setTimeout(actions.pollForOverviewMetrics, POLLING_DURATION); | ||
actions.setTimeoutId(timeoutId); |
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.
Don't change this, this is non-blocking feedback. Just wanted to point out a best practice from Kea and Redux.
https://kea.js.org/docs/guide/concepts#listeners
If you read the reducers section above, you'll remember that it's an anti-pattern to only have setThis and setThat actions that only update this or that.
Often times when you need to call 2 or more actions like this on success to set resulting data, it's a good indication that you're following this anti-pattern.
There really is only 1 "action" happening here. Meaning, if you think of actions as "events" like Kea suggestion, the "event" that is happening here is that polling has completed. Reducers should react to that "event" and set data accordingly.
I could be mis-reading this, just thought I'd take a moment to point this out.
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.
Ahh super interesting! I went back and forth a bit on whether I should refactor/clean this up as well actually but ended up leaving it as-is because it didn't bother me too much.
If you read the reducers section above, you'll remember that it's an anti-pattern to only have setThis and setThat actions that only update this or that.
This confuses me a bit, we do this in a number of our Kea files and I'm not quite clear on what the "correct" code looks like (maybe just a bit too early in the morning for me).
I did just discover sharedListeners in their docs which blew my mind (how long has that been there!?) and looks like we could use it in our code... I'd love to do an overall Kea refactor at some point if we have time after all this is over 😅
export const EngineOverviewLogic = kea<MakeLogicType<EngineOverviewValues, EngineOverviewActions>>({ | ||
path: ['enterprise_search', 'app_search', 'engine_overview_logic'], | ||
actions: () => ({ | ||
setPolledData: (engineMetrics) => engineMetrics, |
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.
Non-blocking feedback. But it usually only takes a moment to do, so when I convert these logic files I often change actions to return objects based on Kea's recommendation:
One more thing. It's strongly recommended to always return an object as a payload from your actions:
setPolledData: (engineMetrics) => engineMetrics, | |
setPolledData: (engineMetrics) => { engineMetrics }, |
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.
Oh this is super bizarre - I thought I left a comment on this but it appears to have disappeared :| So unfortunately based on the way the logic file is written we actually can't do this here without significantly rewriting / adding more destructuring for every single top-level value in the file. I haven't seen any other logic file do this, but in this one, we pull almost every top-level key out of the API response and set it as its own value (see the test).
Which means if we passed engineMetrics as an obj key, we now have to destructure that and do something like:
setPolledData: (_, { engineMetrics: { apiLogsUnavailable } }) => apiLogsUnavailable,
Which isn't the worst, but I went back and forth on this and ended up just deciding to leave it as it was from the current code. I definitely think this page and logic file could stand to be looked at first from a refactor POV in the future, as I believe it's one of our earliest/oldest views.
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.
Makes sense!
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 dropped some non-blocking feedback. Please feel free to merge as-is. This all looks great.
Thanks Jason! I've created some API endpoint & Kea tech debt tickets with links and details to our conversations above to investigate post 7.14 / 8.x. I'm hoping to look at those items then both for this file specifically & in general across our codebase. Feel free to use those tickets too if you encounter any files to look at later as part of our migration! |
@elasticmachine merge upstream |
💚 Build SucceededMetrics [docs]
History
To update your PR or re-run it, just comment with: |
* Add overview server route * Add EngineOverviewLogic * tfw when you forget index.ts Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Summary
Setup PR for the Engine Overview view. It adds a server route to fetch overview metrics data and a logic file as setup for the Engine Overview page.
There's nothing in the UI yet to test.
Checklist