Add analytics to new DS picker and onboarding experience (#67060)

* Add analytics to ds picker advanced mode

* Add analytics to ds picker dropdown
This commit is contained in:
Ivan Ortega Alba 2023-04-25 21:07:16 +02:00 committed by GitHub
parent ad964a0e1d
commit 581cc85ba5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 76 additions and 15 deletions

View File

@ -6,6 +6,7 @@ import React, { useCallback, useRef, useState } from 'react';
import { usePopper } from 'react-popper';
import { DataSourceInstanceSettings, GrafanaTheme2 } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
import { DataSourceJsonData } from '@grafana/schema';
import { Button, CustomScrollbar, Icon, Input, ModalsController, Portal, useStyles2 } from '@grafana/ui';
import config from 'app/core/config';
@ -18,6 +19,14 @@ import { DataSourceModal } from './DataSourceModal';
import { PickerContentProps, DataSourceDropdownProps } from './types';
import { dataSourceLabel } from './utils';
const INTERACTION_EVENT_NAME = 'dashboards_dspicker_clicked';
const INTERACTION_ITEM = {
OPEN_DROPDOWN: 'open_dspicker',
SELECT_DS: 'select_ds',
ADD_FILE: 'add_file',
OPEN_ADVANCED_DS_PICKER: 'open_advanced_ds_picker',
};
export function DataSourceDropdown(props: DataSourceDropdownProps) {
const { current, onChange, ...restProps } = props;
@ -25,6 +34,10 @@ export function DataSourceDropdown(props: DataSourceDropdownProps) {
const [markerElement, setMarkerElement] = useState<HTMLInputElement | null>();
const [selectorElement, setSelectorElement] = useState<HTMLDivElement | null>();
const [filterTerm, setFilterTerm] = useState<string>();
const openDropdown = () => {
reportInteraction(INTERACTION_EVENT_NAME, { item: INTERACTION_ITEM.OPEN_DROPDOWN });
setOpen(true);
};
const currentDataSourceInstanceSettings = useDatasource(current);
@ -93,20 +106,13 @@ export function DataSourceDropdown(props: DataSourceDropdownProps) {
</Portal>
</FocusScope>
) : (
<div
className={styles.trigger}
onClick={() => {
setOpen(true);
}}
>
<div className={styles.trigger} onClick={openDropdown}>
<Input
className={styles.input}
prefix={<DataSourceLogo dataSource={currentDataSourceInstanceSettings} />}
suffix={<Icon name="angle-down" />}
value={dataSourceLabel(currentDataSourceInstanceSettings)}
onFocus={() => {
setOpen(true);
}}
onFocus={openDropdown}
/>
</div>
)}
@ -135,6 +141,7 @@ const PickerContent = React.forwardRef<HTMLDivElement, PickerContentProps>((prop
const changeCallback = useCallback(
(ds: DataSourceInstanceSettings<DataSourceJsonData>) => {
onChange(ds);
reportInteraction(INTERACTION_EVENT_NAME, { item: INTERACTION_ITEM.SELECT_DS, ds_type: ds.type });
},
[onChange]
);
@ -142,6 +149,7 @@ const PickerContent = React.forwardRef<HTMLDivElement, PickerContentProps>((prop
const clickAddCSVCallback = useCallback(() => {
onClickAddCSV?.();
onClose();
reportInteraction(INTERACTION_EVENT_NAME, { item: INTERACTION_ITEM.ADD_FILE });
}, [onClickAddCSV, onClose]);
const styles = useStyles2(getStylesPickerContent);
@ -176,6 +184,7 @@ const PickerContent = React.forwardRef<HTMLDivElement, PickerContentProps>((prop
showModal(DataSourceModal, {
enableFileUpload: props.enableFileUpload,
fileUploadOptions: props.fileUploadOptions,
reportedInteractionFrom: 'ds_picker',
current,
onDismiss: hideModal,
onChange: (ds) => {
@ -183,6 +192,7 @@ const PickerContent = React.forwardRef<HTMLDivElement, PickerContentProps>((prop
hideModal();
},
});
reportInteraction(INTERACTION_EVENT_NAME, { item: INTERACTION_ITEM.OPEN_ADVANCED_DS_PICKER });
}}
>
Open advanced data source picker

View File

@ -1,8 +1,10 @@
import { css } from '@emotion/css';
import { once } from 'lodash';
import React, { useState } from 'react';
import { DropzoneOptions } from 'react-dropzone';
import { DataSourceInstanceSettings, DataSourceRef, GrafanaTheme2 } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
import {
Modal,
FileDropzone,
@ -20,6 +22,15 @@ import { DATASOURCES_ROUTES } from 'app/features/datasources/constants';
import { DataSourceList } from './DataSourceList';
const INTERACTION_EVENT_NAME = 'dashboards_dspickermodal_clicked';
const INTERACTION_ITEM = {
SELECT_DS: 'select_ds',
UPLOAD_FILE: 'upload_file',
CONFIG_NEW_DS: 'config_new_ds',
SEARCH: 'search',
DISMISS: 'dismiss',
};
interface DataSourceModalProps {
onChange: (ds: DataSourceInstanceSettings) => void;
current: DataSourceRef | string | null | undefined;
@ -27,6 +38,7 @@ interface DataSourceModalProps {
recentlyUsed?: string[];
enableFileUpload?: boolean;
fileUploadOptions?: DropzoneOptions;
reportedInteractionFrom?: string;
}
export function DataSourceModal({
@ -35,13 +47,36 @@ export function DataSourceModal({
onChange,
current,
onDismiss,
reportedInteractionFrom,
}: DataSourceModalProps) {
const styles = useStyles2(getDataSourceModalStyles);
const [search, setSearch] = useState('');
const analyticsInteractionSrc = reportedInteractionFrom || 'modal';
const newDataSourceURL = config.featureToggles.dataConnectionsConsole
? CONNECTIONS_ROUTES.DataSourcesNew
: DATASOURCES_ROUTES.New;
const onDismissModal = () => {
onDismiss();
reportInteraction(INTERACTION_EVENT_NAME, { item: INTERACTION_ITEM.DISMISS, src: analyticsInteractionSrc });
};
const onChangeDataSource = (ds: DataSourceInstanceSettings) => {
onChange(ds);
reportInteraction(INTERACTION_EVENT_NAME, {
item: INTERACTION_ITEM.SELECT_DS,
ds_type: ds.type,
src: analyticsInteractionSrc,
});
};
// Memoizing to keep once() cached so it avoids reporting multiple times
const reportSearchUsageOnce = React.useMemo(
() =>
once(() => {
reportInteraction(INTERACTION_EVENT_NAME, { item: 'search', src: analyticsInteractionSrc });
}),
[analyticsInteractionSrc]
);
return (
<Modal
title="Select data source"
@ -50,8 +85,8 @@ export function DataSourceModal({
isOpen={true}
className={styles.modal}
contentClassName={styles.modalContent}
onClickBackdrop={onDismiss}
onDismiss={onDismiss}
onClickBackdrop={onDismissModal}
onDismiss={onDismissModal}
>
<div className={styles.leftColumn}>
<Input
@ -59,7 +94,10 @@ export function DataSourceModal({
value={search}
prefix={<Icon name="search" />}
placeholder="Search data source"
onChange={(e) => setSearch(e.currentTarget.value)}
onChange={(e) => {
setSearch(e.currentTarget.value);
reportSearchUsageOnce();
}}
/>
<CustomScrollbar>
<DataSourceList
@ -67,7 +105,7 @@ export function DataSourceModal({
mixed={false}
variables
filter={(ds) => ds.name.includes(search) && !ds.meta.builtIn}
onChange={onChange}
onChange={onChangeDataSource}
current={current}
/>
</CustomScrollbar>
@ -79,7 +117,7 @@ export function DataSourceModal({
filter={(ds) => !!ds.meta.builtIn}
dashboard
mixed
onChange={onChange}
onChange={onChangeDataSource}
current={current}
/>
{enableFileUpload && (
@ -94,6 +132,10 @@ export function DataSourceModal({
onDrop: (...args) => {
fileUploadOptions?.onDrop?.(...args);
onDismiss();
reportInteraction(INTERACTION_EVENT_NAME, {
item: INTERACTION_ITEM.UPLOAD_FILE,
src: analyticsInteractionSrc,
});
},
}}
>
@ -102,7 +144,16 @@ export function DataSourceModal({
)}
</div>
<div className={styles.dsCTAs}>
<LinkButton variant="secondary" href={newDataSourceURL}>
<LinkButton
variant="secondary"
href={newDataSourceURL}
onClick={() => {
reportInteraction(INTERACTION_EVENT_NAME, {
item: INTERACTION_ITEM.CONFIG_NEW_DS,
src: analyticsInteractionSrc,
});
}}
>
Configure a new data source
</LinkButton>
</div>