-
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
[Lens] Track actions in the UI by time #47919
[Lens] Track actions in the UI by time #47919
Conversation
💔 Build Failed
|
a007ec4
to
a832b1e
Compare
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 a preliminary review. It looks reasonable. My comments are mostly nits. I think we discussed all of the more relevant stuff already.
|
||
private async postToServer() { | ||
try { | ||
await this.http.post(`${this.basePath}${BASE_API_URL}/telemetry`, { |
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 if we should track this.request
or something and then just exit here, if the previous request is still running for some reason. 10 seconds should be enough, but...
task manager has to wait for all plugins to initialize first. It's fine to ignore it as next time around it will be | ||
initialized (or it will throw a different type of error) | ||
*/ | ||
if (errMessage.includes('NotInitialized')) { |
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 there not a code or something systematic that we could use to do this check?
[TELEMETRY_TASK_TYPE]: { | ||
title: 'Lens telemetry fetch task', | ||
type: TELEMETRY_TASK_TYPE, | ||
timeout: '1m', |
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 if we should have a longer timeout, just in case
💔 Build Failed |
💔 Build Failed |
💔 Build Failed |
💔 Build Failed |
💔 Build Failed |
💔 Build Failed |
Pinging @elastic/kibana-app (Team:KibanaApp) |
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 w/ tweaks.
buckets: SubAggregationMap extends { | ||
filters: { filters: Record<string, unknown> }; | ||
} | ||
? { |
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'm unfamiliar with this TypeScript signature. TIL
) { | ||
trackUiEvent('app_date_change'); | ||
} else { | ||
trackUiEvent('app_query_change'); |
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 actually interesting? I'm not sure it's worth collecting, as I expect it to be just super noisy.
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 could combine this with filter changes, but I do think it's interesting
const renderEditor = (routeProps: RouteComponentProps<{ id?: string }>) => { | ||
trackUiEvent('loaded'); |
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 worth tracking? Maybe. 404 certainly is.
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 we don't track some kind of loaded
event it won't be possible to create any kind of funnel, because these metrics are in a window and no other metrics are
if (lastSelectedSuggestion === index) { | ||
rollbackToCurrentVisualization(); | ||
} else { | ||
trackSuggestionEvent(`index_${index}_of_${suggestions.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.
Maybe suggestion_${index}_of_${suggestions.length}
? I had to stare at this for a bit to realize that index was not an Elasticsearch index name.
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 why it's a different function call- Raya wanted to track the position of suggestions. I'll rename to position
metrics.aggregations!.daily.buckets.forEach(daily => { | ||
const byType: Record<string, number> = byDateByType[daily.key] || {}; | ||
daily.groups.buckets.regularEvents.names.buckets.forEach(bucket => { | ||
byType[bucket.key] = bucket.sums.value || 0 + (byType[daily.key] || 0); |
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.
It's hard for me to follow this, so am just commenting here for you to double-check that the use of ||
is really what you want here, same for below.
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.
Yes, it is
const byDateByType: Record<string, Record<string, number>> = {}; | ||
const suggestionsByDate: Record<string, Record<string, number>> = {}; | ||
|
||
metrics.aggregations!.daily.buckets.forEach(daily => { |
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 sure this can never be undefined? Same for the deeper properties.
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.
As long as the .kibana
index exists, the aggregations
key will be returned. And if not, this entire task will fail but that's also handled
query: { | ||
bool: { | ||
filter: [ | ||
{ term: { type: 'lens-ui-telemetry' } }, |
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 if we should pull 'lens-ui-telemetry'
out into a const?
}; | ||
} | ||
|
||
function getNextMidnight() { |
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're using moment everywhere else, why not 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.
This was copied
} | ||
|
||
export interface LensVisualizationUsage { | ||
saved_overall: Record<string, number>; |
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 to be sure this can't explode in size, we should double-check that our generated event names have a realistically small potential set of values.
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.
These aren't generated names, they're all strings in code
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.
Great work @wylieconlon I can't wait to start using it !!
- how does the suggestions fields work e.g.
"index_0_of_5": 1
? - Using
workspace_drop_non_empty: 1
I want to know how common users drag more than 1 fields, does it make sense to do AVG(workspace_drop_non_empty/workspace_drop_empty)? suggestion_confirmed: 1
is it suggestion selected & saved?- What is the definition of
loaded
? - What's the definition of
app_query_change
is it a change from the configuration panel?
`
Renamed to
No, that would not make sense. I've changed it so that there is now a number that tracks all drops regardless of their destination, and then breaks that out into:
Both of those should add up to 100%
This is number of clicks on the "Reload" button
This tracks any page load, including ones that happen because the user saved. I can change this definition.
For example, a KQL filter was typed into the query bar. |
💚 Build Succeeded |
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 w/ tweaks. I think you need to correct the operator precedence stuff I commented on in this review and my previous one. But other than that, I think this is fine.
@@ -121,7 +122,8 @@ export function DragDrop(props: Props) { | |||
setState({ ...state, isActive: false }); | |||
setDragging(undefined); | |||
|
|||
if (onDrop) { | |||
if (onDrop && droppable) { | |||
trackUiEvent('drop_total'); |
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 is probably fine, but if we ever want to make this drag / drop thing more general, this would be something that requires yanking out.
@@ -137,6 +138,10 @@ export const IndexPatternDimensionPanel = memo(function IndexPatternDimensionPan | |||
field: droppedItem.field, | |||
}); | |||
|
|||
trackUiEvent('drop_onto_dimension'); | |||
const hasData = Object.values(props.state.layers).some(({ columns }) => columns.length); | |||
trackUiEvent(hasData ? 'drop_non_empty' : 'drop_empty'); |
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 is fine, but also, do we need three different events? drop_onto_dimension would be a sum of drop_non_empty and drop_empty
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.
It's actually five events:
drop_total
is every drop
Two pairs:
drop_onto_workspace
+ drop_onto_dimension
= drop_total
drop_empty
+ drop_non_empty
= drop_total
} | ||
|
||
private async postToServer() { | ||
this.readFromStorage(); |
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 is probably fine, but it does introduce a disk read for a whole bunch of user-driven events. I wonder if this'll have any impact on performance on low-end devices... Probably not.
Anyway, an alternative would be to track all the stuff in memory, but when you flush it to storage, read and merge then, and reset your in-memory 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.
There are probably some alternatives here, but this is the simplest implementation of it
|
||
function addEvents(prevEvents: Record<string, number>, newEvents: Record<string, number>) { | ||
Object.keys(newEvents).forEach(key => { | ||
prevEvents[key] = prevEvents[key] || 0 + newEvents[key]; |
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'm not sure why lint won't let you put parenthesis there, but I think you should, and should add a lint exception comment.
💔 Build Failed |
💔 Build Failed |
💔 Build Failed |
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 collection method almost duplicates @kbn/analytics code just for providing a new collection method. This UI metric can be easily baked into the @kbn/analytics
library by introducing it as a new METRIC_TYPE
. Doing so it makes it easier to manage telemetry, refactor code and have others utilize these metric types.
I have opened an issue for this as a follow up to this PR: #48352
While it is nice to have this tracking as soon as possible for lens. Ideally implementing this would be approached differently; I've opened an issue so we can revisit the use of this metric type so we can learn its usefulness for the consumers of this usage data and then introduce it as METRIC_TYPE
in @kbn/analytics
for others to be able to use it as well.
I have opened an issue for this as a follow up to this PR: #48353
While not completely thrilled about introducing new telemetry code floating outside our telemetry packages (ui_metric / telemetry / and @kbn/analytics
); I believe we can merge this and revisit these concerns afterwards.
💚 Build Succeeded |
@Bamieh You are right that it is heavily based on the existing code, and I am looking forward to integrating some of these changes for the rest of Kibana to use in releases. |
* [Lens] Track actions in the UI by time * Switch collector to use task data * Report summarized version of task data when requested * Track a more complete set of metrics * Collect suggestion events separately * Pre-aggregate by date in localStorage * Add integration tests * Fix test linter * Fix telemetry naming and run at midnight instead of every minute * Improve cleanup at app level * Fix lint errors * Remove unused mock * Fix tests * Fix types * Update event names and fix local tracking * Respond to review comments * Fix task time * Fix test
* [Lens] Track actions in the UI by time * Switch collector to use task data * Report summarized version of task data when requested * Track a more complete set of metrics * Collect suggestion events separately * Pre-aggregate by date in localStorage * Add integration tests * Fix test linter * Fix telemetry naming and run at midnight instead of every minute * Improve cleanup at app level * Fix lint errors * Remove unused mock * Fix tests * Fix types * Update event names and fix local tracking * Respond to review comments * Fix task time * Fix test
This PR introduces:
lens-ui-telemetry
representing the name, date, type and count of the events being tracked.lens-ui-telemetry
documents and savedlens
documents into daily buckets. This task also deletes any events older than 90 days, as we are not reporting on events past that point.The full list of event names that are introduced in this PR is:
Events that track a click:
Events that are triggered by the suggestion panel:
Example of telemetry collected in its full structure: