Browse Source

feat(widget-builder): Add existing analytics to new builder (#83949)

Takes the old analytics and adds them to the new widget builder. The
changes are:

1. Add a prop to each of the 3 existing analytics events to
differentiate old vs new design (I called them page vs slideout because
numbered versioning might get kind of ambiguous in the future)
2. Move the title and description analytics to `onBlur` or else they
emit an event on each keystroke in the old builder
3. Add events to the new builder
- We only have open, save, and field change events being emitted for
title, description, and dataset changes. I added display type.

These events also take a `source` but I haven't implemented `source` at
all so I'll do that next so we can split up flows by dashboard,
discover, or trace explorer
Nar Saynorath 1 month ago
parent
commit
c513feaccb

+ 8 - 0
static/app/utils/analytics/dashboardsAnalyticsEvents.tsx

@@ -1,8 +1,14 @@
 import type {DashboardsLayout} from 'sentry/views/dashboards/manage';
 import type {DashboardsLayout} from 'sentry/views/dashboards/manage';
 
 
+export enum WidgetBuilderVersion {
+  PAGE = 'page',
+  SLIDEOUT = 'slideout',
+}
+
 // Used in the full-page widget builder
 // Used in the full-page widget builder
 type DashboardsEventParametersWidgetBuilder = {
 type DashboardsEventParametersWidgetBuilder = {
   'dashboards_views.widget_builder.change': {
   'dashboards_views.widget_builder.change': {
+    builder_version: WidgetBuilderVersion;
     field: string;
     field: string;
     from: string;
     from: string;
     new_widget: boolean;
     new_widget: boolean;
@@ -10,9 +16,11 @@ type DashboardsEventParametersWidgetBuilder = {
     widget_type: string;
     widget_type: string;
   };
   };
   'dashboards_views.widget_builder.opened': {
   'dashboards_views.widget_builder.opened': {
+    builder_version: WidgetBuilderVersion;
     new_widget: boolean;
     new_widget: boolean;
   };
   };
   'dashboards_views.widget_builder.save': {
   'dashboards_views.widget_builder.save': {
+    builder_version: WidgetBuilderVersion;
     data_set: string;
     data_set: string;
     new_widget: boolean;
     new_widget: boolean;
   };
   };

+ 1 - 0
static/app/views/dashboards/types.tsx

@@ -174,4 +174,5 @@ export enum DashboardWidgetSource {
   DASHBOARDS = 'dashboards',
   DASHBOARDS = 'dashboards',
   LIBRARY = 'library',
   LIBRARY = 'library',
   ISSUE_DETAILS = 'issueDetail',
   ISSUE_DETAILS = 'issueDetail',
+  TRACE_EXPLORER = 'traceExplorer',
 }
 }

+ 18 - 3
static/app/views/dashboards/widgetBuilder/components/datasetSelector.tsx

@@ -6,15 +6,21 @@ import RadioGroup, {type RadioOption} from 'sentry/components/forms/controls/rad
 import ExternalLink from 'sentry/components/links/externalLink';
 import ExternalLink from 'sentry/components/links/externalLink';
 import {t, tct} from 'sentry/locale';
 import {t, tct} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import {space} from 'sentry/styles/space';
+import {trackAnalytics} from 'sentry/utils/analytics';
+import {WidgetBuilderVersion} from 'sentry/utils/analytics/dashboardsAnalyticsEvents';
 import useOrganization from 'sentry/utils/useOrganization';
 import useOrganization from 'sentry/utils/useOrganization';
 import {WidgetType} from 'sentry/views/dashboards/types';
 import {WidgetType} from 'sentry/views/dashboards/types';
 import {SectionHeader} from 'sentry/views/dashboards/widgetBuilder/components/common/sectionHeader';
 import {SectionHeader} from 'sentry/views/dashboards/widgetBuilder/components/common/sectionHeader';
 import {useWidgetBuilderContext} from 'sentry/views/dashboards/widgetBuilder/contexts/widgetBuilderContext';
 import {useWidgetBuilderContext} from 'sentry/views/dashboards/widgetBuilder/contexts/widgetBuilderContext';
+import useDashboardWidgetSource from 'sentry/views/dashboards/widgetBuilder/hooks/useDashboardWidgetSource';
+import useIsEditingWidget from 'sentry/views/dashboards/widgetBuilder/hooks/useIsEditingWidget';
 import {BuilderStateAction} from 'sentry/views/dashboards/widgetBuilder/hooks/useWidgetBuilderState';
 import {BuilderStateAction} from 'sentry/views/dashboards/widgetBuilder/hooks/useWidgetBuilderState';
 
 
 function WidgetBuilderDatasetSelector() {
 function WidgetBuilderDatasetSelector() {
   const organization = useOrganization();
   const organization = useOrganization();
   const {state, dispatch} = useWidgetBuilderContext();
   const {state, dispatch} = useWidgetBuilderContext();
+  const source = useDashboardWidgetSource();
+  const isEditing = useIsEditingWidget();
 
 
   const datasetChoices: Array<RadioOption<WidgetType>> = [];
   const datasetChoices: Array<RadioOption<WidgetType>> = [];
   datasetChoices.push([WidgetType.ERRORS, t('Errors')]);
   datasetChoices.push([WidgetType.ERRORS, t('Errors')]);
@@ -52,12 +58,21 @@ function WidgetBuilderDatasetSelector() {
         label={t('Dataset')}
         label={t('Dataset')}
         value={state.dataset ?? WidgetType.ERRORS}
         value={state.dataset ?? WidgetType.ERRORS}
         choices={datasetChoices}
         choices={datasetChoices}
-        onChange={(newValue: WidgetType) =>
+        onChange={(newValue: WidgetType) => {
           dispatch({
           dispatch({
             type: BuilderStateAction.SET_DATASET,
             type: BuilderStateAction.SET_DATASET,
             payload: newValue,
             payload: newValue,
-          })
-        }
+          });
+          trackAnalytics('dashboards_views.widget_builder.change', {
+            from: source,
+            widget_type: state.dataset ?? '',
+            builder_version: WidgetBuilderVersion.SLIDEOUT,
+            field: 'dataSet',
+            value: newValue,
+            new_widget: !isEditing,
+            organization,
+          });
+        }}
       />
       />
     </Fragment>
     </Fragment>
   );
   );

+ 30 - 0
static/app/views/dashboards/widgetBuilder/components/nameAndDescFields.tsx

@@ -6,8 +6,13 @@ import TextArea from 'sentry/components/forms/controls/textarea';
 import TextField from 'sentry/components/forms/fields/textField';
 import TextField from 'sentry/components/forms/fields/textField';
 import {t} from 'sentry/locale';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import {space} from 'sentry/styles/space';
+import {trackAnalytics} from 'sentry/utils/analytics';
+import {WidgetBuilderVersion} from 'sentry/utils/analytics/dashboardsAnalyticsEvents';
+import useOrganization from 'sentry/utils/useOrganization';
 import {SectionHeader} from 'sentry/views/dashboards/widgetBuilder/components/common/sectionHeader';
 import {SectionHeader} from 'sentry/views/dashboards/widgetBuilder/components/common/sectionHeader';
 import {useWidgetBuilderContext} from 'sentry/views/dashboards/widgetBuilder/contexts/widgetBuilderContext';
 import {useWidgetBuilderContext} from 'sentry/views/dashboards/widgetBuilder/contexts/widgetBuilderContext';
+import useDashboardWidgetSource from 'sentry/views/dashboards/widgetBuilder/hooks/useDashboardWidgetSource';
+import useIsEditingWidget from 'sentry/views/dashboards/widgetBuilder/hooks/useIsEditingWidget';
 import {BuilderStateAction} from 'sentry/views/dashboards/widgetBuilder/hooks/useWidgetBuilderState';
 import {BuilderStateAction} from 'sentry/views/dashboards/widgetBuilder/hooks/useWidgetBuilderState';
 
 
 interface WidgetBuilderNameAndDescriptionProps {
 interface WidgetBuilderNameAndDescriptionProps {
@@ -19,8 +24,11 @@ function WidgetBuilderNameAndDescription({
   error,
   error,
   setError,
   setError,
 }: WidgetBuilderNameAndDescriptionProps) {
 }: WidgetBuilderNameAndDescriptionProps) {
+  const organization = useOrganization();
   const {state, dispatch} = useWidgetBuilderContext();
   const {state, dispatch} = useWidgetBuilderContext();
   const [isDescSelected, setIsDescSelected] = useState(state.description ? true : false);
   const [isDescSelected, setIsDescSelected] = useState(state.description ? true : false);
+  const isEditing = useIsEditingWidget();
+  const source = useDashboardWidgetSource();
 
 
   return (
   return (
     <Fragment>
     <Fragment>
@@ -37,6 +45,17 @@ function WidgetBuilderNameAndDescription({
           setError?.({...error, title: undefined});
           setError?.({...error, title: undefined});
           dispatch({type: BuilderStateAction.SET_TITLE, payload: newTitle});
           dispatch({type: BuilderStateAction.SET_TITLE, payload: newTitle});
         }}
         }}
+        onBlur={() => {
+          trackAnalytics('dashboards_views.widget_builder.change', {
+            from: source,
+            widget_type: state.dataset ?? '',
+            builder_version: WidgetBuilderVersion.SLIDEOUT,
+            field: 'title',
+            value: state.title ?? '',
+            new_widget: !isEditing,
+            organization,
+          });
+        }}
         required
         required
         error={error?.title}
         error={error?.title}
         inline={false}
         inline={false}
@@ -63,6 +82,17 @@ function WidgetBuilderNameAndDescription({
           onChange={e => {
           onChange={e => {
             dispatch({type: BuilderStateAction.SET_DESCRIPTION, payload: e.target.value});
             dispatch({type: BuilderStateAction.SET_DESCRIPTION, payload: e.target.value});
           }}
           }}
+          onBlur={() => {
+            trackAnalytics('dashboards_views.widget_builder.change', {
+              from: source,
+              widget_type: state.dataset ?? '',
+              builder_version: WidgetBuilderVersion.SLIDEOUT,
+              field: 'description',
+              value: state.description ?? '',
+              new_widget: !isEditing,
+              organization,
+            });
+          }}
         />
         />
       )}
       )}
     </Fragment>
     </Fragment>

+ 9 - 1
static/app/views/dashboards/widgetBuilder/components/saveButton.tsx

@@ -8,6 +8,8 @@ import {
 } from 'sentry/actionCreators/indicator';
 } from 'sentry/actionCreators/indicator';
 import {Button} from 'sentry/components/button';
 import {Button} from 'sentry/components/button';
 import {t} from 'sentry/locale';
 import {t} from 'sentry/locale';
+import {trackAnalytics} from 'sentry/utils/analytics';
+import {WidgetBuilderVersion} from 'sentry/utils/analytics/dashboardsAnalyticsEvents';
 import useApi from 'sentry/utils/useApi';
 import useApi from 'sentry/utils/useApi';
 import useOrganization from 'sentry/utils/useOrganization';
 import useOrganization from 'sentry/utils/useOrganization';
 import {useParams} from 'sentry/utils/useParams';
 import {useParams} from 'sentry/utils/useParams';
@@ -29,6 +31,12 @@ function SaveButton({isEditing, onSave, setError}: SaveButtonProps) {
   const [isSaving, setIsSaving] = useState(false);
   const [isSaving, setIsSaving] = useState(false);
 
 
   const handleSave = useCallback(async () => {
   const handleSave = useCallback(async () => {
+    trackAnalytics('dashboards_views.widget_builder.save', {
+      builder_version: WidgetBuilderVersion.SLIDEOUT,
+      data_set: state.dataset ?? '',
+      new_widget: !isEditing,
+      organization: organization.slug,
+    });
     const widget = convertBuilderStateToWidget(state);
     const widget = convertBuilderStateToWidget(state);
     setIsSaving(true);
     setIsSaving(true);
     try {
     try {
@@ -42,7 +50,7 @@ function SaveButton({isEditing, onSave, setError}: SaveButtonProps) {
       setError(errorDetails);
       setError(errorDetails);
       addErrorMessage(t('Unable to save widget'));
       addErrorMessage(t('Unable to save widget'));
     }
     }
-  }, [api, onSave, organization.slug, state, widgetIndex, setError]);
+  }, [api, onSave, organization.slug, state, widgetIndex, setError, isEditing]);
 
 
   return (
   return (
     <Button priority="primary" onClick={handleSave} busy={isSaving}>
     <Button priority="primary" onClick={handleSave} busy={isSaving}>

+ 17 - 0
static/app/views/dashboards/widgetBuilder/components/typeSelector.tsx

@@ -7,10 +7,15 @@ import FieldGroup from 'sentry/components/forms/fieldGroup';
 import {IconGraph, IconNumber, IconTable} from 'sentry/icons';
 import {IconGraph, IconNumber, IconTable} from 'sentry/icons';
 import {t} from 'sentry/locale';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import {space} from 'sentry/styles/space';
+import {trackAnalytics} from 'sentry/utils/analytics';
+import {WidgetBuilderVersion} from 'sentry/utils/analytics/dashboardsAnalyticsEvents';
+import useOrganization from 'sentry/utils/useOrganization';
 import {getDatasetConfig} from 'sentry/views/dashboards/datasetConfig/base';
 import {getDatasetConfig} from 'sentry/views/dashboards/datasetConfig/base';
 import {DisplayType} from 'sentry/views/dashboards/types';
 import {DisplayType} from 'sentry/views/dashboards/types';
 import {SectionHeader} from 'sentry/views/dashboards/widgetBuilder/components/common/sectionHeader';
 import {SectionHeader} from 'sentry/views/dashboards/widgetBuilder/components/common/sectionHeader';
 import {useWidgetBuilderContext} from 'sentry/views/dashboards/widgetBuilder/contexts/widgetBuilderContext';
 import {useWidgetBuilderContext} from 'sentry/views/dashboards/widgetBuilder/contexts/widgetBuilderContext';
+import useDashboardWidgetSource from 'sentry/views/dashboards/widgetBuilder/hooks/useDashboardWidgetSource';
+import useIsEditingWidget from 'sentry/views/dashboards/widgetBuilder/hooks/useIsEditingWidget';
 import {BuilderStateAction} from 'sentry/views/dashboards/widgetBuilder/hooks/useWidgetBuilderState';
 import {BuilderStateAction} from 'sentry/views/dashboards/widgetBuilder/hooks/useWidgetBuilderState';
 
 
 const typeIcons = {
 const typeIcons = {
@@ -37,6 +42,9 @@ interface WidgetBuilderTypeSelectorProps {
 function WidgetBuilderTypeSelector({error, setError}: WidgetBuilderTypeSelectorProps) {
 function WidgetBuilderTypeSelector({error, setError}: WidgetBuilderTypeSelectorProps) {
   const {state, dispatch} = useWidgetBuilderContext();
   const {state, dispatch} = useWidgetBuilderContext();
   const config = getDatasetConfig(state.dataset);
   const config = getDatasetConfig(state.dataset);
+  const source = useDashboardWidgetSource();
+  const isEditing = useIsEditingWidget();
+  const organization = useOrganization();
 
 
   return (
   return (
     <Fragment>
     <Fragment>
@@ -81,6 +89,15 @@ function WidgetBuilderTypeSelector({error, setError}: WidgetBuilderTypeSelectorP
                 payload: [state.query[0]!],
                 payload: [state.query[0]!],
               });
               });
             }
             }
+            trackAnalytics('dashboards_views.widget_builder.change', {
+              from: source,
+              widget_type: state.dataset ?? '',
+              builder_version: WidgetBuilderVersion.SLIDEOUT,
+              field: 'displayType',
+              value: newValue?.value ?? '',
+              new_widget: !isEditing,
+              organization,
+            });
           }}
           }}
           components={{
           components={{
             SingleValue: (containerProps: any) => {
             SingleValue: (containerProps: any) => {

+ 20 - 3
static/app/views/dashboards/widgetBuilder/components/widgetBuilderSlideout.tsx

@@ -9,9 +9,11 @@ import SlideOverPanel from 'sentry/components/slideOverPanel';
 import {IconClose} from 'sentry/icons';
 import {IconClose} from 'sentry/icons';
 import {t} from 'sentry/locale';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import {space} from 'sentry/styles/space';
+import {trackAnalytics} from 'sentry/utils/analytics';
+import {WidgetBuilderVersion} from 'sentry/utils/analytics/dashboardsAnalyticsEvents';
 import type {TableDataWithTitle} from 'sentry/utils/discover/discoverQuery';
 import type {TableDataWithTitle} from 'sentry/utils/discover/discoverQuery';
 import useMedia from 'sentry/utils/useMedia';
 import useMedia from 'sentry/utils/useMedia';
-import {useParams} from 'sentry/utils/useParams';
+import useOrganization from 'sentry/utils/useOrganization';
 import {useValidateWidgetQuery} from 'sentry/views/dashboards/hooks/useValidateWidget';
 import {useValidateWidgetQuery} from 'sentry/views/dashboards/hooks/useValidateWidget';
 import {
 import {
   type DashboardDetails,
   type DashboardDetails,
@@ -35,6 +37,7 @@ import WidgetBuilderTypeSelector from 'sentry/views/dashboards/widgetBuilder/com
 import Visualize from 'sentry/views/dashboards/widgetBuilder/components/visualize';
 import Visualize from 'sentry/views/dashboards/widgetBuilder/components/visualize';
 import WidgetTemplatesList from 'sentry/views/dashboards/widgetBuilder/components/widgetTemplatesList';
 import WidgetTemplatesList from 'sentry/views/dashboards/widgetBuilder/components/widgetTemplatesList';
 import {useWidgetBuilderContext} from 'sentry/views/dashboards/widgetBuilder/contexts/widgetBuilderContext';
 import {useWidgetBuilderContext} from 'sentry/views/dashboards/widgetBuilder/contexts/widgetBuilderContext';
+import useIsEditingWidget from 'sentry/views/dashboards/widgetBuilder/hooks/useIsEditingWidget';
 import {convertBuilderStateToWidget} from 'sentry/views/dashboards/widgetBuilder/utils/convertBuilderStateToWidget';
 import {convertBuilderStateToWidget} from 'sentry/views/dashboards/widgetBuilder/utils/convertBuilderStateToWidget';
 
 
 type WidgetBuilderSlideoutProps = {
 type WidgetBuilderSlideoutProps = {
@@ -66,17 +69,31 @@ function WidgetBuilderSlideout({
   onDataFetched,
   onDataFetched,
   thresholdMetaState,
   thresholdMetaState,
 }: WidgetBuilderSlideoutProps) {
 }: WidgetBuilderSlideoutProps) {
+  const organization = useOrganization();
   const {state} = useWidgetBuilderContext();
   const {state} = useWidgetBuilderContext();
   const [initialState] = useState(state);
   const [initialState] = useState(state);
   const [error, setError] = useState<Record<string, any>>({});
   const [error, setError] = useState<Record<string, any>>({});
-  const {widgetIndex} = useParams();
   const theme = useTheme();
   const theme = useTheme();
+  const isEditing = useIsEditingWidget();
 
 
   const validatedWidgetResponse = useValidateWidgetQuery(
   const validatedWidgetResponse = useValidateWidgetQuery(
     convertBuilderStateToWidget(state)
     convertBuilderStateToWidget(state)
   );
   );
 
 
-  const isEditing = widgetIndex !== undefined;
+  useEffect(() => {
+    if (!openWidgetTemplates) {
+      trackAnalytics('dashboards_views.widget_builder.opened', {
+        builder_version: WidgetBuilderVersion.SLIDEOUT,
+        new_widget: !isEditing,
+        organization,
+      });
+    }
+    // Ignore isEditing because it won't change during the
+    // useful lifetime of the widget builder, but it
+    // flickers when an edited widget is saved.
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [openWidgetTemplates, organization]);
+
   const title = openWidgetTemplates
   const title = openWidgetTemplates
     ? t('Add from Widget Library')
     ? t('Add from Widget Library')
     : isEditing
     : isEditing

+ 18 - 0
static/app/views/dashboards/widgetBuilder/hooks/useDashboardWidgetSource.tsx

@@ -0,0 +1,18 @@
+import {defined} from 'sentry/utils';
+import useUrlParams from 'sentry/utils/useUrlParams';
+import {DashboardWidgetSource} from 'sentry/views/dashboards/types';
+
+function useDashboardWidgetSource(): DashboardWidgetSource | '' {
+  const {getParamValue} = useUrlParams('source');
+  const source = getParamValue();
+
+  const validSources = Object.values(
+    DashboardWidgetSource
+  ) satisfies DashboardWidgetSource[];
+
+  return defined(source) && validSources.includes(source as DashboardWidgetSource)
+    ? (source as DashboardWidgetSource)
+    : '';
+}
+
+export default useDashboardWidgetSource;

+ 8 - 0
static/app/views/dashboards/widgetBuilder/hooks/useIsEditingWidget.tsx

@@ -0,0 +1,8 @@
+import {useParams} from 'sentry/utils/useParams';
+
+function useIsEditingWidget() {
+  const {widgetIndex} = useParams();
+  return widgetIndex !== undefined;
+}
+
+export default useIsEditingWidget;

+ 45 - 10
static/app/views/dashboards/widgetBuilder/widgetBuilder.tsx

@@ -25,6 +25,7 @@ import type {TagCollection} from 'sentry/types/group';
 import type {RouteComponentProps} from 'sentry/types/legacyReactRouter';
 import type {RouteComponentProps} from 'sentry/types/legacyReactRouter';
 import {defined} from 'sentry/utils';
 import {defined} from 'sentry/utils';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import {trackAnalytics} from 'sentry/utils/analytics';
+import {WidgetBuilderVersion} from 'sentry/utils/analytics/dashboardsAnalyticsEvents';
 import {CustomMeasurementsProvider} from 'sentry/utils/customMeasurements/customMeasurementsProvider';
 import {CustomMeasurementsProvider} from 'sentry/utils/customMeasurements/customMeasurementsProvider';
 import type {TableDataWithTitle} from 'sentry/utils/discover/discoverQuery';
 import type {TableDataWithTitle} from 'sentry/utils/discover/discoverQuery';
 import EventView from 'sentry/utils/discover/eventView';
 import EventView from 'sentry/utils/discover/eventView';
@@ -316,6 +317,7 @@ function WidgetBuilder({
     trackAnalytics('dashboards_views.widget_builder.opened', {
     trackAnalytics('dashboards_views.widget_builder.opened', {
       organization,
       organization,
       new_widget: !isEditing,
       new_widget: !isEditing,
+      builder_version: WidgetBuilderVersion.PAGE,
     });
     });
 
 
     if (isEmptyObject(tags) && dataSet !== DataSet.SPANS) {
     if (isEmptyObject(tags) && dataSet !== DataSet.SPANS) {
@@ -547,16 +549,6 @@ function WidgetBuilder({
   function handleDisplayTypeOrAnnotationChange<
   function handleDisplayTypeOrAnnotationChange<
     F extends keyof Pick<State, 'displayType' | 'title' | 'description'>,
     F extends keyof Pick<State, 'displayType' | 'title' | 'description'>,
   >(field: F, value: State[F]) {
   >(field: F, value: State[F]) {
-    if (value) {
-      trackAnalytics('dashboards_views.widget_builder.change', {
-        from: source,
-        field,
-        value,
-        widget_type: widgetType,
-        organization,
-        new_widget: !isEditing,
-      });
-    }
     setState(prevState => {
     setState(prevState => {
       const newState = cloneDeep(prevState);
       const newState = cloneDeep(prevState);
       set(newState, field, value);
       set(newState, field, value);
@@ -579,6 +571,7 @@ function WidgetBuilder({
       widget_type: widgetType,
       widget_type: widgetType,
       organization,
       organization,
       new_widget: !isEditing,
       new_widget: !isEditing,
+      builder_version: WidgetBuilderVersion.PAGE,
     });
     });
     setState(prevState => {
     setState(prevState => {
       const newState = cloneDeep(prevState);
       const newState = cloneDeep(prevState);
@@ -913,6 +906,7 @@ function WidgetBuilder({
         organization,
         organization,
         data_set: widgetData.widgetType ?? defaultWidgetType,
         data_set: widgetData.widgetType ?? defaultWidgetType,
         new_widget: false,
         new_widget: false,
+        builder_version: WidgetBuilderVersion.PAGE,
       });
       });
       return;
       return;
     }
     }
@@ -924,6 +918,7 @@ function WidgetBuilder({
       organization,
       organization,
       data_set: widgetData.widgetType ?? defaultWidgetType,
       data_set: widgetData.widgetType ?? defaultWidgetType,
       new_widget: true,
       new_widget: true,
+      builder_version: WidgetBuilderVersion.PAGE,
     });
     });
   }
   }
 
 
@@ -1197,6 +1192,20 @@ function WidgetBuilder({
                                         );
                                         );
                                       }}
                                       }}
                                       value={state.title}
                                       value={state.title}
+                                      onBlur={() => {
+                                        trackAnalytics(
+                                          'dashboards_views.widget_builder.change',
+                                          {
+                                            from: source,
+                                            field: 'title',
+                                            value: state.title ?? '',
+                                            widget_type: widgetType,
+                                            organization,
+                                            new_widget: !isEditing,
+                                            builder_version: WidgetBuilderVersion.PAGE,
+                                          }
+                                        );
+                                      }}
                                     />
                                     />
                                     <StyledTextAreaField
                                     <StyledTextAreaField
                                       name="description"
                                       name="description"
@@ -1213,6 +1222,20 @@ function WidgetBuilder({
                                         );
                                         );
                                       }}
                                       }}
                                       value={state.description}
                                       value={state.description}
+                                      onBlur={() => {
+                                        trackAnalytics(
+                                          'dashboards_views.widget_builder.change',
+                                          {
+                                            from: source,
+                                            field: 'description',
+                                            value: state.description ?? '',
+                                            widget_type: widgetType,
+                                            organization,
+                                            new_widget: !isEditing,
+                                            builder_version: WidgetBuilderVersion.PAGE,
+                                          }
+                                        );
+                                      }}
                                     />
                                     />
                                   </NameWidgetStep>
                                   </NameWidgetStep>
                                   <VisualizationStep
                                   <VisualizationStep
@@ -1228,6 +1251,18 @@ function WidgetBuilder({
                                         'displayType',
                                         'displayType',
                                         newDisplayType
                                         newDisplayType
                                       );
                                       );
+                                      trackAnalytics(
+                                        'dashboards_views.widget_builder.change',
+                                        {
+                                          from: source,
+                                          field: 'displayType',
+                                          value: newDisplayType,
+                                          widget_type: widgetType,
+                                          organization,
+                                          new_widget: !isEditing,
+                                          builder_version: WidgetBuilderVersion.PAGE,
+                                        }
+                                      );
                                     }}
                                     }}
                                     isWidgetInvalid={!state.queryConditionsValid}
                                     isWidgetInvalid={!state.queryConditionsValid}
                                     onWidgetSplitDecision={
                                     onWidgetSplitDecision={