mirror of https://github.com/grafana/grafana
Elasticsearch: Add tracking for plugin adoption stats (#59954)
* Elasticsearch: Add tracking * Update public/app/plugins/datasource/elasticsearch/tracking.ts Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com> * Update public/app/plugins/datasource/elasticsearch/tracking.ts Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com> * Update public/app/plugins/datasource/elasticsearch/tracking.ts Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com> * Refactor getLineLimit * Update public/app/plugins/datasource/elasticsearch/tracking.ts Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com> * Update public/app/plugins/datasource/elasticsearch/tracking.ts Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com> * Update not tracking for volume queries Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>pull/60017/head
parent
c5ee4e4ae1
commit
4b56493789
@ -0,0 +1,152 @@ |
|||||||
|
import { DashboardLoadedEvent } from '@grafana/data'; |
||||||
|
import { reportInteraction } from '@grafana/runtime'; |
||||||
|
import './module'; |
||||||
|
|
||||||
|
jest.mock('@grafana/runtime', () => { |
||||||
|
return { |
||||||
|
...jest.requireActual('@grafana/runtime'), |
||||||
|
reportInteraction: jest.fn(), |
||||||
|
getAppEvents: () => ({ |
||||||
|
subscribe: jest.fn((_, handler) => { |
||||||
|
// Trigger test event
|
||||||
|
handler( |
||||||
|
new DashboardLoadedEvent({ |
||||||
|
dashboardId: 'dashboard123', |
||||||
|
orgId: 1, |
||||||
|
userId: 2, |
||||||
|
grafanaVersion: 'v9.0.0', |
||||||
|
queries: { |
||||||
|
elasticsearch: [ |
||||||
|
{ |
||||||
|
alias: '', |
||||||
|
bucketAggs: [], |
||||||
|
datasource: { |
||||||
|
type: 'elasticsearch', |
||||||
|
uid: 'PE50363A9B6833EE7', |
||||||
|
}, |
||||||
|
metrics: [ |
||||||
|
{ |
||||||
|
id: '1', |
||||||
|
settings: { |
||||||
|
limit: '501', |
||||||
|
}, |
||||||
|
type: 'logs', |
||||||
|
}, |
||||||
|
], |
||||||
|
query: 'abc:def', |
||||||
|
refId: 'A', |
||||||
|
timeField: '@timestamp', |
||||||
|
}, |
||||||
|
{ |
||||||
|
alias: '', |
||||||
|
bucketAggs: [], |
||||||
|
datasource: { |
||||||
|
type: 'elasticsearch', |
||||||
|
uid: 'es1', |
||||||
|
}, |
||||||
|
metrics: [ |
||||||
|
{ |
||||||
|
id: '1', |
||||||
|
settings: { |
||||||
|
size: '600', |
||||||
|
}, |
||||||
|
type: 'raw_data', |
||||||
|
}, |
||||||
|
], |
||||||
|
query: '', |
||||||
|
}, |
||||||
|
{ |
||||||
|
alias: 'alias', |
||||||
|
bucketAggs: [ |
||||||
|
{ |
||||||
|
field: '@timestamp', |
||||||
|
id: '2', |
||||||
|
settings: { |
||||||
|
interval: 'auto', |
||||||
|
}, |
||||||
|
type: 'date_histogram', |
||||||
|
}, |
||||||
|
], |
||||||
|
datasource: { |
||||||
|
type: 'elasticsearch', |
||||||
|
uid: 'es1', |
||||||
|
}, |
||||||
|
metrics: [ |
||||||
|
{ |
||||||
|
id: '3', |
||||||
|
type: 'count', |
||||||
|
}, |
||||||
|
], |
||||||
|
query: 'abc:def', |
||||||
|
}, |
||||||
|
{ |
||||||
|
alias: '', |
||||||
|
bucketAggs: [], |
||||||
|
datasource: { |
||||||
|
type: 'elasticsearch', |
||||||
|
uid: 'PE50363A9B6833EE7', |
||||||
|
}, |
||||||
|
metrics: [ |
||||||
|
{ |
||||||
|
id: '1', |
||||||
|
settings: { |
||||||
|
size: '600', |
||||||
|
}, |
||||||
|
type: 'raw_document', |
||||||
|
}, |
||||||
|
], |
||||||
|
query: '', |
||||||
|
refId: 'A', |
||||||
|
timeField: '@timestamp', |
||||||
|
}, |
||||||
|
{ |
||||||
|
alias: '', |
||||||
|
bucketAggs: [ |
||||||
|
{ |
||||||
|
field: '@timestamp', |
||||||
|
id: '2', |
||||||
|
settings: { |
||||||
|
interval: 'auto', |
||||||
|
}, |
||||||
|
type: 'date_histogram', |
||||||
|
}, |
||||||
|
], |
||||||
|
datasource: { |
||||||
|
type: 'elasticsearch', |
||||||
|
uid: 'es1', |
||||||
|
}, |
||||||
|
metrics: [ |
||||||
|
{ |
||||||
|
field: 'counter', |
||||||
|
id: '1', |
||||||
|
type: 'avg', |
||||||
|
}, |
||||||
|
], |
||||||
|
query: '$test:abc', |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
}) |
||||||
|
); |
||||||
|
}), |
||||||
|
}), |
||||||
|
}; |
||||||
|
}); |
||||||
|
|
||||||
|
describe('queriesOnInitDashboard', () => { |
||||||
|
it('should report a grafana_elasticsearch_dashboard_loaded interaction ', () => { |
||||||
|
expect(reportInteraction).toHaveBeenCalledWith('grafana_elasticsearch_dashboard_loaded', { |
||||||
|
grafana_version: 'v9.0.0', |
||||||
|
dashboard_id: 'dashboard123', |
||||||
|
org_id: 1, |
||||||
|
queries_count: 5, |
||||||
|
queries_with_changed_line_limit_count: 1, |
||||||
|
queries_with_lucene_query_count: 3, |
||||||
|
queries_with_template_variables_count: 1, |
||||||
|
raw_data_queries_count: 1, |
||||||
|
raw_document_queries_count: 1, |
||||||
|
logs_queries_count: 1, |
||||||
|
metric_queries_count: 2, |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
@ -1,7 +1,13 @@ |
|||||||
import { DataSourcePlugin } from '@grafana/data'; |
import { DashboardLoadedEvent, DataSourcePlugin } from '@grafana/data'; |
||||||
|
import { getAppEvents } from '@grafana/runtime'; |
||||||
|
|
||||||
import { QueryEditor } from './components/QueryEditor'; |
import { QueryEditor } from './components/QueryEditor'; |
||||||
import { ConfigEditor } from './configuration/ConfigEditor'; |
import { ConfigEditor } from './configuration/ConfigEditor'; |
||||||
import { ElasticDatasource } from './datasource'; |
import { ElasticDatasource } from './datasource'; |
||||||
|
import { onDashboardLoadedHandler } from './tracking'; |
||||||
|
import { ElasticsearchQuery } from './types'; |
||||||
|
|
||||||
export const plugin = new DataSourcePlugin(ElasticDatasource).setQueryEditor(QueryEditor).setConfigEditor(ConfigEditor); |
export const plugin = new DataSourcePlugin(ElasticDatasource).setQueryEditor(QueryEditor).setConfigEditor(ConfigEditor); |
||||||
|
|
||||||
|
// Subscribe to on dashboard loaded event so that we can track plugin adoption
|
||||||
|
getAppEvents().subscribe<DashboardLoadedEvent<ElasticsearchQuery>>(DashboardLoadedEvent, onDashboardLoadedHandler); |
||||||
|
|||||||
@ -0,0 +1,129 @@ |
|||||||
|
import { DashboardLoadedEvent, DataQueryResponse } from '@grafana/data'; |
||||||
|
import { reportInteraction } from '@grafana/runtime'; |
||||||
|
import { variableRegex } from 'app/features/variables/utils'; |
||||||
|
|
||||||
|
import { REF_ID_STARTER_LOG_VOLUME } from './datasource'; |
||||||
|
import pluginJson from './plugin.json'; |
||||||
|
import { ElasticsearchQuery } from './types'; |
||||||
|
|
||||||
|
type ElasticSearchOnDashboardLoadedTrackingEvent = { |
||||||
|
grafana_version?: string; |
||||||
|
dashboard_id?: string; |
||||||
|
org_id?: number; |
||||||
|
|
||||||
|
/* The number of Elasticsearch queries present in the dashboard*/ |
||||||
|
queries_count: number; |
||||||
|
|
||||||
|
/* The number of Elasticsearch logs queries present in the dashboard*/ |
||||||
|
logs_queries_count: number; |
||||||
|
|
||||||
|
/* The number of Elasticsearch metric queries present in the dashboard*/ |
||||||
|
metric_queries_count: number; |
||||||
|
|
||||||
|
/* The number of Elasticsearch raw data queries present in the dashboard*/ |
||||||
|
raw_data_queries_count: number; |
||||||
|
|
||||||
|
/* The number of Elasticsearch raw documents queries present in the dashboard*/ |
||||||
|
raw_document_queries_count: number; |
||||||
|
|
||||||
|
/* The number of Elasticsearch queries with used template variables present in the dashboard*/ |
||||||
|
queries_with_template_variables_count: number; |
||||||
|
|
||||||
|
/* The number of Elasticsearch queries with changed line limit present in the dashboard*/ |
||||||
|
queries_with_changed_line_limit_count: number; |
||||||
|
|
||||||
|
/* The number of Elasticsearch queries with lucene query present in the dashboard*/ |
||||||
|
queries_with_lucene_query_count: number; |
||||||
|
}; |
||||||
|
|
||||||
|
export const onDashboardLoadedHandler = ({ |
||||||
|
payload: { dashboardId, orgId, grafanaVersion, queries }, |
||||||
|
}: DashboardLoadedEvent<ElasticsearchQuery>) => { |
||||||
|
try { |
||||||
|
// We only want to track visible ElasticSearch queries
|
||||||
|
const elasticsearchQueries = queries[pluginJson.id].filter((query) => !query.hide); |
||||||
|
if (!elasticsearchQueries?.length) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const queriesWithTemplateVariables = elasticsearchQueries.filter(isQueryWithTemplateVariables); |
||||||
|
const queriesWithLuceneQuery = elasticsearchQueries.filter((query) => !!query.query); |
||||||
|
const logsQueries = elasticsearchQueries.filter((query) => getQueryType(query) === 'logs'); |
||||||
|
const metricQueries = elasticsearchQueries.filter((query) => getQueryType(query) === 'metric'); |
||||||
|
const rawDataQueries = elasticsearchQueries.filter((query) => getQueryType(query) === 'raw_data'); |
||||||
|
const rawDocumentQueries = elasticsearchQueries.filter((query) => getQueryType(query) === 'raw_document'); |
||||||
|
const queriesWithChangedLineLimit = elasticsearchQueries.filter(isQueryWithChangedLineLimit); |
||||||
|
|
||||||
|
const event: ElasticSearchOnDashboardLoadedTrackingEvent = { |
||||||
|
grafana_version: grafanaVersion, |
||||||
|
dashboard_id: dashboardId, |
||||||
|
org_id: orgId, |
||||||
|
queries_count: elasticsearchQueries.length, |
||||||
|
logs_queries_count: logsQueries.length, |
||||||
|
metric_queries_count: metricQueries.length, |
||||||
|
raw_data_queries_count: rawDataQueries.length, |
||||||
|
raw_document_queries_count: rawDocumentQueries.length, |
||||||
|
queries_with_template_variables_count: queriesWithTemplateVariables.length, |
||||||
|
queries_with_changed_line_limit_count: queriesWithChangedLineLimit.length, |
||||||
|
queries_with_lucene_query_count: queriesWithLuceneQuery.length, |
||||||
|
}; |
||||||
|
|
||||||
|
reportInteraction('grafana_elasticsearch_dashboard_loaded', event); |
||||||
|
} catch (error) { |
||||||
|
console.error('error in elasticsearch tracking handler', error); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const getQueryType = (query: ElasticsearchQuery): string | undefined => { |
||||||
|
if (!query.metrics || !query.metrics.length) { |
||||||
|
return undefined; |
||||||
|
} |
||||||
|
const nonMetricQueryTypes = ['logs', 'raw_data', 'raw_document']; |
||||||
|
if (nonMetricQueryTypes.includes(query.metrics[0].type)) { |
||||||
|
return query.metrics[0].type; |
||||||
|
} |
||||||
|
return 'metric'; |
||||||
|
}; |
||||||
|
|
||||||
|
const getLineLimit = (query: ElasticsearchQuery): number | undefined => { |
||||||
|
if (query.metrics?.[0]?.type !== 'logs') { |
||||||
|
return undefined; |
||||||
|
} |
||||||
|
|
||||||
|
const lineLimit = query.metrics?.[0].settings?.limit; |
||||||
|
return lineLimit ? parseInt(lineLimit, 10) : undefined; |
||||||
|
}; |
||||||
|
|
||||||
|
const isQueryWithChangedLineLimit = (query: ElasticsearchQuery): boolean => { |
||||||
|
const lineLimit = getLineLimit(query); |
||||||
|
return lineLimit !== undefined && lineLimit !== 500; |
||||||
|
}; |
||||||
|
|
||||||
|
const isQueryWithTemplateVariables = (query: ElasticsearchQuery): boolean => { |
||||||
|
return variableRegex.test(query.query ?? ''); |
||||||
|
}; |
||||||
|
|
||||||
|
const shouldNotReportBasedOnRefId = (refId: string): boolean => { |
||||||
|
if (refId.startsWith(REF_ID_STARTER_LOG_VOLUME)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
}; |
||||||
|
|
||||||
|
export function trackQuery(response: DataQueryResponse, queries: ElasticsearchQuery[], app: string): void { |
||||||
|
for (const query of queries) { |
||||||
|
if (shouldNotReportBasedOnRefId(query.refId)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
reportInteraction('grafana_elasticsearch_query_executed', { |
||||||
|
app, |
||||||
|
with_lucene_query: query.query ? true : false, |
||||||
|
query_type: getQueryType(query), |
||||||
|
line_limit: getLineLimit(query), |
||||||
|
alias: query.alias, |
||||||
|
has_error: response.error !== undefined, |
||||||
|
has_data: response.data.some((frame) => frame.length > 0), |
||||||
|
simultaneously_sent_query_count: queries.length, |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue