From 95d0b27c017be1a4cf6bc6c41116473481811d5d Mon Sep 17 00:00:00 2001 From: "J.C. Zhong" Date: Fri, 6 Jan 2023 15:34:05 -0800 Subject: [PATCH] Add some frontend view/click events (#1118) * Add frontend event logging * add some view events * fix one element type * refactor * add search resutls view events * comment --- components/DataDoc/DataDoc.tsx | 27 +++++++- components/DataDoc/DataDocCellControl.tsx | 21 +++++- .../DataDocBoardsButton.tsx | 12 +++- components/DataDocCell/DataDocCell.tsx | 6 ++ .../DataDocDAGExporterButton.tsx | 10 ++- .../DataDocQueryCell/DataDocQueryCell.tsx | 24 +++++++ .../DataDocRightSidebar.tsx | 18 ++++- .../DataDocRunAllButton.tsx | 6 ++ .../DataDocScheduleButton.tsx | 10 ++- .../DeleteDataDocButton.tsx | 6 ++ .../DataDocTemplateButton.tsx | 10 ++- .../DataDocViewersBadge.tsx | 10 ++- .../DataTableNavigatorSearch.tsx | 6 ++ components/DataTableView/DataTableView.tsx | 18 +++++ .../DataTableViewMini/DataTableViewMini.tsx | 14 ++-- components/Landing/Landing.tsx | 20 ++++-- components/QueryComposer/QueryComposer.tsx | 48 +++++++++++-- components/Search/SearchOverview.tsx | 67 +++++++++++++++++-- components/Search/SearchResultItem.tsx | 51 +++++++++----- components/UIGuide/DataDocUIGuide.tsx | 10 ++- .../UIGuide/QuerybookSidebarUIGuide.tsx | 6 ++ const/analytics.ts | 60 ++++++++++++++++- hooks/queryEditor/useLint.ts | 15 +++-- lib/analytics.ts | 9 ++- 24 files changed, 427 insertions(+), 57 deletions(-) diff --git a/components/DataDoc/DataDoc.tsx b/components/DataDoc/DataDoc.tsx index fa896460e..a230dbbe9 100644 --- a/components/DataDoc/DataDoc.tsx +++ b/components/DataDoc/DataDoc.tsx @@ -16,6 +16,7 @@ import { ISearchAndReplaceHandles, SearchAndReplace, } from 'components/SearchAndReplace/SearchAndReplace'; +import { ComponentType, ElementType } from 'const/analytics'; import { CELL_TYPE, DataCellUpdateFields, @@ -27,6 +28,7 @@ import { } from 'const/datadoc'; import { ISearchOptions, ISearchResult } from 'const/searchAndReplace'; import { DataDocContext, IDataDocContextType } from 'context/DataDoc'; +import { trackClick, trackView } from 'lib/analytics'; import { deserializeCopyCommand, serializeCopyCommand, @@ -339,6 +341,11 @@ class DataDocComponent extends React.PureComponent { @bind public async pasteCellAt(pasteIndex: number) { + trackClick({ + component: ComponentType.DATADOC_PAGE, + element: ElementType.PASTE_CELL_BUTTON, + }); + let clipboardContent = null; try { if (navigator.clipboard.readText) { @@ -378,6 +385,12 @@ class DataDocComponent extends React.PureComponent { @bind public copyCellAt(index: number, cut: boolean) { + trackClick({ + component: ComponentType.DATADOC_PAGE, + element: cut + ? ElementType.CUT_CELL_BUTTON + : ElementType.COPY_CELL_BUTTON, + }); copy( serializeCopyCommand({ cellId: this.props.dataDoc.cells[index], @@ -399,6 +412,10 @@ class DataDocComponent extends React.PureComponent { if (numberOfCells > 0) { const shouldConfirm = !cellIsEmpty; const deleteCell = async () => { + trackClick({ + component: ComponentType.DATADOC_PAGE, + element: ElementType.DELETE_CELL_BUTTON, + }); try { await dataDocActions.deleteDataDocCell(docId, cell.id); } catch (e) { @@ -450,7 +467,11 @@ class DataDocComponent extends React.PureComponent { header: 'Clone DataDoc?', message: 'You will be redirected to the new Data Doc after cloning.', - onConfirm: () => + onConfirm: () => { + trackClick({ + component: ComponentType.DATADOC_PAGE, + element: ElementType.CLONE_DATADOC_BUTTON, + }); toast.promise( cloneDataDoc(id).then((dataDoc) => history.push( @@ -462,7 +483,8 @@ class DataDocComponent extends React.PureComponent { success: 'Clone Success!', error: 'Cloning failed.', } - ), + ); + }, cancelColor: 'default', confirmIcon: 'Copy', }); @@ -783,6 +805,7 @@ class DataDocComponent extends React.PureComponent { } public componentDidMount() { + trackView(ComponentType.DATADOC_PAGE); this.autoFocusCell({}, this.props); this.openDataDoc(this.props.docId); this.publishDataDocTitle(this.props.dataDoc?.title); diff --git a/components/DataDoc/DataDocCellControl.tsx b/components/DataDoc/DataDocCellControl.tsx index 0190ec98c..7eddad251 100644 --- a/components/DataDoc/DataDocCellControl.tsx +++ b/components/DataDoc/DataDocCellControl.tsx @@ -2,8 +2,10 @@ import clsx from 'clsx'; import React, { useCallback } from 'react'; import toast from 'react-hot-toast'; +import { ComponentType, ElementType } from 'const/analytics'; import { IDataCellMeta } from 'const/datadoc'; import { useBoundFunc } from 'hooks/useBoundFunction'; +import { trackClick } from 'lib/analytics'; import { copy, sleep, titleize } from 'lib/utils'; import { getShortcutSymbols, KeyMap } from 'lib/utils/keyboard'; import { AsyncButton } from 'ui/AsyncButton/AsyncButton'; @@ -76,6 +78,10 @@ export const DataDocCellControl: React.FunctionComponent = ({ React.useState(false); const handleToggleDefaultCollapsed = React.useCallback(() => { + trackClick({ + component: ComponentType.DATADOC_PAGE, + element: ElementType.COLLAPSE_CELL_BUTTON, + }); setAnimateDefaultChange(true); Promise.all([sleep(500), toggleDefaultCollapsed()]).then(() => setAnimateDefaultChange(false) @@ -83,6 +89,10 @@ export const DataDocCellControl: React.FunctionComponent = ({ }, [toggleDefaultCollapsed]); const handleShare = useCallback(() => { + trackClick({ + component: ComponentType.DATADOC_PAGE, + element: ElementType.SHARE_CELL_BUTTON, + }); copy(shareUrl); toast('Url Copied!'); }, [shareUrl]); @@ -269,7 +279,16 @@ const InsertCellButtons: React.FC<{ index: number; }> = React.memo(({ insertCellAt, index }) => { const handleInsertcell = useCallback( - (cellType: string) => insertCellAt(index, cellType, null, null), + (cellType: string) => { + trackClick({ + component: ComponentType.DATADOC_PAGE, + element: ElementType.INSERT_CELL_BUTTON, + aux: { + type: cellType, + }, + }); + return insertCellAt(index, cellType, null, null); + }, [insertCellAt, index] ); diff --git a/components/DataDocBoardsButton/DataDocBoardsButton.tsx b/components/DataDocBoardsButton/DataDocBoardsButton.tsx index bede4d1d4..03cef432b 100644 --- a/components/DataDocBoardsButton/DataDocBoardsButton.tsx +++ b/components/DataDocBoardsButton/DataDocBoardsButton.tsx @@ -1,8 +1,10 @@ import React from 'react'; +import { DataDocBoardsModal } from 'components/DataDocBoardsModal/DataDocBoardsModal'; +import { ComponentType, ElementType } from 'const/analytics'; import { IDataDoc } from 'const/datadoc'; +import { trackClick } from 'lib/analytics'; import { IconButton } from 'ui/Button/IconButton'; -import { DataDocBoardsModal } from 'components/DataDocBoardsModal/DataDocBoardsModal'; interface IProps { dataDoc: IDataDoc; @@ -17,7 +19,13 @@ export const DataDocBoardsButton: React.FunctionComponent = ({
setShowBoards(true)} + onClick={() => { + trackClick({ + component: ComponentType.DATADOC_PAGE, + element: ElementType.LISTS_BUTTON, + }); + setShowBoards(true); + }} tooltip="View Lists" tooltipPos="left" title="Lists" diff --git a/components/DataDocCell/DataDocCell.tsx b/components/DataDocCell/DataDocCell.tsx index 9a25aa8a8..0baff3b6b 100644 --- a/components/DataDocCell/DataDocCell.tsx +++ b/components/DataDocCell/DataDocCell.tsx @@ -8,6 +8,7 @@ import { DataDocChartCell } from 'components/DataDocChartCell/DataDocChartCell'; import { DataDocQueryCell } from 'components/DataDocQueryCell/DataDocQueryCell'; import { DataDocTextCell } from 'components/DataDocTextCell/DataDocTextCell'; import { UserAvatar } from 'components/UserBadge/UserAvatar'; +import { ComponentType, ElementType } from 'const/analytics'; import { DataCellUpdateFields, IDataCell, @@ -16,6 +17,7 @@ import { import { DataDocContext } from 'context/DataDoc'; import { useMakeSelector } from 'hooks/redux/useMakeSelector'; import { useBoundFunc } from 'hooks/useBoundFunction'; +import { trackClick } from 'lib/analytics'; import { getShareUrl } from 'lib/data-doc/data-doc-utils'; import * as dataDocActions from 'redux/dataDoc/action'; import * as dataDocSelectors from 'redux/dataDoc/selector'; @@ -113,6 +115,10 @@ export const DataDocCell: React.FunctionComponent = const handleMoveCell = React.useCallback( (fromIndex: number, toIndex: number) => { + trackClick({ + component: ComponentType.DATADOC_PAGE, + element: ElementType.MOVE_CELL_BUTTON, + }); dataDocActions.moveDataDocCell(docId, fromIndex, toIndex); }, [docId] diff --git a/components/DataDocDAGExporter/DataDocDAGExporterButton.tsx b/components/DataDocDAGExporter/DataDocDAGExporterButton.tsx index 4f77299ce..b013928a2 100644 --- a/components/DataDocDAGExporter/DataDocDAGExporterButton.tsx +++ b/components/DataDocDAGExporter/DataDocDAGExporterButton.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { ComponentType, ElementType } from 'const/analytics'; +import { trackClick } from 'lib/analytics'; import { IconButton } from 'ui/Button/IconButton'; import { Modal } from 'ui/Modal/Modal'; @@ -18,7 +20,13 @@ export const DataDocDAGExporterButton: React.FunctionComponent = ({ <> setShowModal(true)} + onClick={() => { + trackClick({ + component: ComponentType.DATADOC_PAGE, + element: ElementType.DAG_EXPORTER_BUTTON, + }); + setShowModal(true); + }} tooltip="DAG Exporter" tooltipPos="left" title="Exporter" diff --git a/components/DataDocQueryCell/DataDocQueryCell.tsx b/components/DataDocQueryCell/DataDocQueryCell.tsx index dd8d3a962..4a37a8e95 100644 --- a/components/DataDocQueryCell/DataDocQueryCell.tsx +++ b/components/DataDocQueryCell/DataDocQueryCell.tsx @@ -21,8 +21,10 @@ import { QuerySnippetInsertionModal } from 'components/QuerySnippetInsertionModa import { TemplatedQueryView } from 'components/TemplateQueryView/TemplatedQueryView'; import { TranspileQueryModal } from 'components/TranspileQueryModal/TranspileQueryModal'; import { UDFForm } from 'components/UDFForm/UDFForm'; +import { ComponentType, ElementType } from 'const/analytics'; import { IDataQueryCellMeta, TDataDocMetaVariables } from 'const/datadoc'; import type { IQueryEngine, IQueryTranspiler } from 'const/queryEngine'; +import { trackClick } from 'lib/analytics'; import CodeMirror from 'lib/codemirror'; import { createSQLLinter } from 'lib/codemirror/codemirror-lint'; import { @@ -102,6 +104,7 @@ interface IState { showQuerySnippetModal: boolean; showRenderedTemplateModal: boolean; showUDFModal: boolean; + hasLintError: boolean; transpilerConfig?: { toEngine: IQueryEngine; @@ -125,6 +128,7 @@ class DataDocQueryCellComponent extends React.PureComponent { showQuerySnippetModal: false, showRenderedTemplateModal: false, showUDFModal: false, + hasLintError: false, }; } @@ -260,6 +264,13 @@ class DataDocQueryCellComponent extends React.PureComponent { } } + @bind + public onLintCompletion(hasError: boolean) { + this.setState({ + hasLintError: hasError, + }); + } + @decorate(memoizeOne) public createGetLintAnnotations( engineId: number, @@ -384,6 +395,13 @@ class DataDocQueryCellComponent extends React.PureComponent { @bind public async onRunButtonClick() { + trackClick({ + component: ComponentType.DATADOC_QUERY_CELL, + element: ElementType.RUN_QUERY_BUTTON, + aux: { + lintError: this.state.hasLintError, + }, + }); return runQuery( await this.getTransformedQuery(), this.engineId, @@ -400,6 +418,11 @@ class DataDocQueryCellComponent extends React.PureComponent { @bind public formatQuery(options = {}) { + trackClick({ + component: ComponentType.DATADOC_QUERY_CELL, + element: ElementType.FORMAT_BUTTON, + aux: options, + }); if (this.queryEditorRef.current) { this.queryEditorRef.current.formatQuery(options); } @@ -729,6 +752,7 @@ class DataDocQueryCellComponent extends React.PureComponent { ) : null } + onLintCompletion={this.onLintCompletion} /> {openSnippetDOM}
diff --git a/components/DataDocRightSidebar/DataDocRightSidebar.tsx b/components/DataDocRightSidebar/DataDocRightSidebar.tsx index 0d6f2ccb4..c22e012f4 100644 --- a/components/DataDocRightSidebar/DataDocRightSidebar.tsx +++ b/components/DataDocRightSidebar/DataDocRightSidebar.tsx @@ -5,9 +5,11 @@ import { DataDocBoardsButton } from 'components/DataDocBoardsButton/DataDocBoard import { DataDocDAGExporterButton } from 'components/DataDocDAGExporter/DataDocDAGExporterButton'; import { DataDocTemplateButton } from 'components/DataDocTemplateButton/DataDocTemplateButton'; import { DataDocUIGuide } from 'components/UIGuide/DataDocUIGuide'; +import { ComponentType, ElementType } from 'const/analytics'; import { IDataDoc, IDataDocMeta } from 'const/datadoc'; import { useAnnouncements } from 'hooks/redux/useAnnouncements'; import { useScrollToTop } from 'hooks/ui/useScrollToTop'; +import { trackClick } from 'lib/analytics'; import { fetchDAGExporters } from 'redux/dataDoc/action'; import { IStoreState } from 'redux/store/types'; import { IconButton } from 'ui/Button/IconButton'; @@ -87,7 +89,13 @@ export const DataDocRightSidebar: React.FunctionComponent = ({ { + trackClick({ + component: ComponentType.DATADOC_PAGE, + element: ElementType.GO_TO_TOP_BUTTON, + }); + scrollToTop(); + }} /> = ({ : 'Collapse query cells' } tooltipPos="left" - onClick={onCollapse} + onClick={() => { + trackClick({ + component: ComponentType.DATADOC_PAGE, + element: ElementType.COLLAPSE_DATADOC_BUTTON, + }); + onCollapse(); + }} /> = ({ header: 'Run All Cells', message: ConfirmMessageDOM(), onConfirm: () => { + trackClick({ + component: ComponentType.DATADOC_PAGE, + element: ElementType.RUN_ALL_CELLS_BUTTON, + }); toast.promise(DataDocResource.run(docId), { loading: null, success: 'DataDoc execution started!', diff --git a/components/DataDocRightSidebar/DataDocScheduleButton.tsx b/components/DataDocRightSidebar/DataDocScheduleButton.tsx index 71d177c81..2fec89433 100644 --- a/components/DataDocRightSidebar/DataDocScheduleButton.tsx +++ b/components/DataDocRightSidebar/DataDocScheduleButton.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { ComponentType, ElementType } from 'const/analytics'; +import { trackClick } from 'lib/analytics'; import { IconButton } from 'ui/Button/IconButton'; import { DataDocScheduleModal } from './DataDocScheduleModal'; @@ -19,7 +21,13 @@ export const DataDocScheduleButton: React.FunctionComponent = ({
setShowModal(true)} + onClick={() => { + trackClick({ + component: ComponentType.DATADOC_PAGE, + element: ElementType.SCHEDULE_DATADOC_BUTTON, + }); + setShowModal(true); + }} tooltip="Schedule DataDoc" tooltipPos="left" title="Schedule" diff --git a/components/DataDocRightSidebar/DeleteDataDocButton.tsx b/components/DataDocRightSidebar/DeleteDataDocButton.tsx index 3d106a6e4..ec6afde88 100644 --- a/components/DataDocRightSidebar/DeleteDataDocButton.tsx +++ b/components/DataDocRightSidebar/DeleteDataDocButton.tsx @@ -2,7 +2,9 @@ import React from 'react'; import toast from 'react-hot-toast'; import { useDispatch } from 'react-redux'; +import { ComponentType, ElementType } from 'const/analytics'; import { TooltipDirection } from 'const/tooltip'; +import { trackClick } from 'lib/analytics'; import { sendConfirm } from 'lib/querybookUI'; import { navigateWithinEnv } from 'lib/utils/query-string'; import * as dataDocActions from 'redux/dataDoc/action'; @@ -27,6 +29,10 @@ export const DeleteDataDocButton: React.FunctionComponent< header: 'Delete DataDoc?', message: 'This action is irreversible.', onConfirm: () => { + trackClick({ + component: ComponentType.DATADOC_PAGE, + element: ElementType.DELETE_DATADOC_BUTTON, + }); toast.promise( dispatch(dataDocActions.deleteDataDoc(docId)).then(() => navigateWithinEnv('/datadoc/') diff --git a/components/DataDocTemplateButton/DataDocTemplateButton.tsx b/components/DataDocTemplateButton/DataDocTemplateButton.tsx index 290ad5bf5..481c39270 100644 --- a/components/DataDocTemplateButton/DataDocTemplateButton.tsx +++ b/components/DataDocTemplateButton/DataDocTemplateButton.tsx @@ -1,7 +1,9 @@ import React from 'react'; import { DataDocTemplateVarForm } from 'components/DataDocTemplateButton/DataDocTemplateVarForm'; +import { ComponentType, ElementType } from 'const/analytics'; import { IDataDoc, IDataDocMeta } from 'const/datadoc'; +import { trackClick } from 'lib/analytics'; import { IconButton } from 'ui/Button/IconButton'; import { Modal } from 'ui/Modal/Modal'; @@ -46,7 +48,13 @@ export const DataDocTemplateButton: React.FunctionComponent = ({
setShowTemplateForm(true)} + onClick={() => { + trackClick({ + component: ComponentType.DATADOC_PAGE, + element: ElementType.TEMPLATE_CONFIG_BUTTON, + }); + setShowTemplateForm(true); + }} tooltip="Set Variables" tooltipPos="left" title="Template" diff --git a/components/DataDocViewersBadge/DataDocViewersBadge.tsx b/components/DataDocViewersBadge/DataDocViewersBadge.tsx index b4c5fbdf8..badf9deff 100644 --- a/components/DataDocViewersBadge/DataDocViewersBadge.tsx +++ b/components/DataDocViewersBadge/DataDocViewersBadge.tsx @@ -3,8 +3,10 @@ import { useDispatch } from 'react-redux'; import { DataDocViewersList } from 'components/DataDocViewersList/DataDocViewersList'; import { UserAvatarList } from 'components/UserBadge/UserAvatarList'; +import { ComponentType, ElementType } from 'const/analytics'; import { DELETED_USER_MSG } from 'const/user'; import { useShallowSelector } from 'hooks/redux/useShallowSelector'; +import { trackClick } from 'lib/analytics'; import { Permission } from 'lib/data-doc/datadoc-permission'; import * as dataDocActions from 'redux/dataDoc/action'; import * as dataDocSelectors from 'redux/dataDoc/selector'; @@ -177,7 +179,13 @@ export const DataDocViewersBadge = React.memo( return (
setShowViewsList((v) => !v)} + onClick={() => { + trackClick({ + component: ComponentType.DATADOC_PAGE, + element: ElementType.SHARE_DATADOC_BUTTON, + }); + setShowViewsList((v) => !v); + }} > {viewersDOM} {shareButtonDOM} diff --git a/components/DataTableNavigator/DataTableNavigatorSearch.tsx b/components/DataTableNavigator/DataTableNavigatorSearch.tsx index 8bd19de79..b1540ca53 100644 --- a/components/DataTableNavigator/DataTableNavigatorSearch.tsx +++ b/components/DataTableNavigator/DataTableNavigatorSearch.tsx @@ -3,7 +3,9 @@ import React, { useCallback, useMemo, useRef } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { TableTagGroupSelect } from 'components/DataTableTags/TableTagGroupSelect'; +import { ComponentType, ElementType } from 'const/analytics'; import { useToggleState } from 'hooks/useToggleState'; +import { trackClick } from 'lib/analytics'; import { changeSchemasSort, updateTableSort, @@ -136,6 +138,10 @@ export const DataTableNavigatorSearch: React.FC<{ dispatch(updateTableSort(null, !sortTableAsc)); }} onOrderByFieldToggle={() => { + trackClick({ + component: ComponentType.TABLE_NAVIGATOR_SEARCH, + element: ElementType.TABLE_ORDER_BY_BUTTON, + }); dispatch( updateTableSort( sortTableKey === 'name' ? 'relevance' : 'name' diff --git a/components/DataTableView/DataTableView.tsx b/components/DataTableView/DataTableView.tsx index 5f182f403..5cf741911 100644 --- a/components/DataTableView/DataTableView.tsx +++ b/components/DataTableView/DataTableView.tsx @@ -14,7 +14,9 @@ import { DataTableViewQueryExamples } from 'components/DataTableViewQueryExample import { DataTableViewSamples } from 'components/DataTableViewSamples/DataTableViewSamples'; import { DataTableViewSourceQuery } from 'components/DataTableViewSourceQuery/DataTableViewSourceQuery'; import { DataTableViewWarnings } from 'components/DataTableViewWarnings/DataTableViewWarnings'; +import { ComponentType, ElementType } from 'const/analytics'; import { IPaginatedQuerySampleFilters } from 'const/metastore'; +import { trackClick, trackView } from 'lib/analytics'; import { setBrowserTitle } from 'lib/querybookUI'; import history from 'lib/router-history'; import { sanitizeUrlTitle } from 'lib/utils'; @@ -38,34 +40,42 @@ const tabDefinitions = [ { name: 'Overview', key: 'overview', + elementType: ElementType.OVERVIEW_TABLE_TAB, }, { name: 'Columns', key: 'columns', + elementType: ElementType.COLUMNS_TABLE_TAB, }, { name: 'Row Samples', key: 'row_samples', + elementType: ElementType.ROW_SAMPLES_TABLE_TAB, }, { name: 'Lineage', key: 'lineage', + elementType: ElementType.LINEAGE_TABLE_TAB, }, { name: 'Source Query', key: 'source_query', + elementType: ElementType.SOURCE_QUERY_TABLE_TAB, }, { name: 'Query Examples', key: 'query_examples', + elementType: ElementType.QUERY_EXAMPLES_TABLE_TAB, }, { name: 'Lists', key: 'lists', + elementType: ElementType.LISTS_TABLE_TAB, }, { name: 'Warnings', key: 'warnings', + elementType: ElementType.WARNINGS_TABLE_TAB, }, ]; @@ -124,6 +134,13 @@ class DataTableViewComponent extends React.PureComponent< @bind public onTabSelected(key) { + const elementType = tabDefinitions.find( + (t) => t.key === key + ).elementType; + trackClick({ + component: ComponentType.TABLE_DETAIL_VIEW, + element: elementType, + }); // Temporal replaceQueryString({ tab: key }); this.setState({ selectedTabKey: key }); @@ -267,6 +284,7 @@ class DataTableViewComponent extends React.PureComponent< } public componentDidMount() { + trackView(ComponentType.TABLE_DETAIL_VIEW); this.props.getTable(this.props.tableId); this.publishDataTableTitle(this.props.tableName); } diff --git a/components/DataTableViewMini/DataTableViewMini.tsx b/components/DataTableViewMini/DataTableViewMini.tsx index a1ee1951f..3fbdc6217 100644 --- a/components/DataTableViewMini/DataTableViewMini.tsx +++ b/components/DataTableViewMini/DataTableViewMini.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { ComponentType, ElementType } from 'const/analytics'; +import { trackClick } from 'lib/analytics'; import { navigateWithinEnv } from 'lib/utils/query-string'; import { TextButton } from 'ui/Button/Button'; import { IconButton } from 'ui/Button/IconButton'; @@ -56,13 +58,17 @@ export const DataTableViewMini: React.FunctionComponent = ({ {closeButton ||
} - onViewDetails + onClick={() => { + trackClick({ + component: ComponentType.TABLE_NAVIGATOR_SEARCH, + element: ElementType.VIEW_TABLE_BUTTON, + }); + return onViewDetails ? onViewDetails(tableId) : navigateWithinEnv(`/table/${tableId}/`, { isModal: true, - }) - } + }); + }} title="View Table" className="table-details-button" /> diff --git a/components/Landing/Landing.tsx b/components/Landing/Landing.tsx index cf20e8fe4..5ddb8752b 100644 --- a/components/Landing/Landing.tsx +++ b/components/Landing/Landing.tsx @@ -3,10 +3,11 @@ import React from 'react'; import { useDispatch } from 'react-redux'; import { QuerybookSidebarUIGuide } from 'components/UIGuide/QuerybookSidebarUIGuide'; -import { ComponentType } from 'const/analytics'; +import { ComponentType, ElementType } from 'const/analytics'; import { useShallowSelector } from 'hooks/redux/useShallowSelector'; import { useBrowserTitle } from 'hooks/useBrowserTitle'; import { useTrackView } from 'hooks/useTrackView'; +import { trackClick } from 'lib/analytics'; import { titleize } from 'lib/utils'; import { navigateWithinEnv } from 'lib/utils/query-string'; import { fetchDataDocs } from 'redux/dataDoc/action'; @@ -43,7 +44,14 @@ const DefaultLanding: React.FC = ({ children }) => { dispatch(fetchDataDocs('recent')); }, [environment.id]); - const onDataDocClick = React.useCallback((docId) => { + const onDataDocClick = React.useCallback((docId, elementType) => { + trackClick({ + component: ComponentType.LANDING_PAGE, + element: elementType, + aux: { + docId, + }, + }); navigateWithinEnv(`/datadoc/${docId}/`); }, []); @@ -51,7 +59,9 @@ const DefaultLanding: React.FC = ({ children }) => { recentDataDocs.map((dataDoc) => (
onDataDocClick(dataDoc.id)} + onClick={() => + onDataDocClick(dataDoc.id, ElementType.RECENT_DATADOC) + } key={dataDoc.id} > {dataDoc.title || 'Untitled'} @@ -61,7 +71,9 @@ const DefaultLanding: React.FC = ({ children }) => { favoriteDataDocs.map((dataDoc) => (
onDataDocClick(dataDoc.id)} + onClick={() => + onDataDocClick(dataDoc.id, ElementType.FAVORITE_DATADOC) + } key={dataDoc.id} > {dataDoc.title || 'Untitled'} diff --git a/components/QueryComposer/QueryComposer.tsx b/components/QueryComposer/QueryComposer.tsx index 7ae475ae6..10b43e9f6 100644 --- a/components/QueryComposer/QueryComposer.tsx +++ b/components/QueryComposer/QueryComposer.tsx @@ -27,12 +27,15 @@ import { import { TemplatedQueryView } from 'components/TemplateQueryView/TemplatedQueryView'; import { TranspileQueryModal } from 'components/TranspileQueryModal/TranspileQueryModal'; import { UDFForm } from 'components/UDFForm/UDFForm'; +import { ComponentType, ElementType } from 'const/analytics'; import { IDataDocMetaVariable } from 'const/datadoc'; import KeyMap from 'const/keyMap'; import { IQueryEngine } from 'const/queryEngine'; import { ISearchOptions, ISearchResult } from 'const/searchAndReplace'; import { useDebounceState } from 'hooks/redux/useDebounceState'; import { useBrowserTitle } from 'hooks/useBrowserTitle'; +import { useTrackView } from 'hooks/useTrackView'; +import { trackClick } from 'lib/analytics'; import { createSQLLinter } from 'lib/codemirror/codemirror-lint'; import { replaceStringIndices, searchText } from 'lib/data-doc/search'; import { getSelectedQuery, IRange } from 'lib/sql-helper/sql-lexer'; @@ -244,6 +247,10 @@ const useQueryComposerSearchAndReplace = ( function useQueryEditorHelpers() { const queryEditorRef = useRef(null); const handleFormatQuery = useCallback(() => { + trackClick({ + component: ComponentType.ADHOC_QUERY, + element: ElementType.FORMAT_BUTTON, + }); if (queryEditorRef.current) { queryEditorRef.current.formatQuery(); } @@ -367,6 +374,7 @@ function useTranspileQuery( } const QueryComposer: React.FC = () => { + useTrackView(ComponentType.ADHOC_QUERY); useBrowserTitle('Adhoc Query'); const environmentId = useSelector( @@ -403,6 +411,8 @@ const QueryComposer: React.FC = () => { const [showRenderedTemplateModal, setShowRenderedTemplateModal] = useState(false); + const [hasLintErrors, setHasLintErrors] = useState(false); + const runButtonRef = useRef(null); const clickOnRunButton = useCallback(() => { if (runButtonRef.current) { @@ -424,14 +434,19 @@ const QueryComposer: React.FC = () => { } = useTranspileQuery(engine, queryEngines, setEngineId, setQuery); const handleCreateDataDoc = useCallback(async () => { + trackClick({ + component: ComponentType.ADHOC_QUERY, + element: ElementType.CREATE_DATADOC_BUTTON, + }); let dataDoc = null; + const meta = { variables: templatedVariables }; if (executionId) { dataDoc = await dispatch( dataDocActions.createDataDocFromAdhoc( executionId, engine.id, query, - templatedVariables + meta ) ); } else { @@ -441,7 +456,7 @@ const QueryComposer: React.FC = () => { meta: { engine: engine.id }, }; dataDoc = await dispatch( - dataDocActions.createDataDoc([cell], templatedVariables) + dataDocActions.createDataDoc([cell], meta) ); } navigateWithinEnv(`/datadoc/${dataDoc.id}/`); @@ -453,6 +468,13 @@ const QueryComposer: React.FC = () => { }, [query, queryEditorRef]); const handleRunQuery = React.useCallback(async () => { + trackClick({ + component: ComponentType.ADHOC_QUERY, + element: ElementType.RUN_QUERY_BUTTON, + aux: { + lintError: hasLintErrors, + }, + }); // Throttle to prevent double run await sleep(250); const transformedQuery = await transformQuery( @@ -483,6 +505,7 @@ const QueryComposer: React.FC = () => { dispatch, getCurrentSelectedQuery, setExecutionId, + hasLintErrors, ]); const keyMap = useKeyMap(clickOnRunButton, queryEngines, setEngineId); @@ -519,6 +542,7 @@ const QueryComposer: React.FC = () => { engine={engine} onSelection={handleEditorSelection} getLintErrors={getLintAnnotations} + onLintCompletion={setHasLintErrors} /> ); @@ -659,14 +683,26 @@ const QueryComposer: React.FC = () => { const additionalButtons: IListMenuItem[] = [ { name: 'Template Config', - onClick: () => setShowTemplateForm(true), + onClick: () => { + trackClick({ + component: ComponentType.ADHOC_QUERY, + element: ElementType.TEMPLATE_CONFIG_BUTTON, + }); + setShowTemplateForm(true); + }, icon: 'Code', tooltip: 'Set Variables', tooltipPos: 'right', }, { name: 'Render Template', - onClick: () => setShowRenderedTemplateModal(true), + onClick: () => { + trackClick({ + component: ComponentType.ADHOC_QUERY, + element: ElementType.RENDER_QUERY_BUTTON, + }); + setShowRenderedTemplateModal(true); + }, icon: 'Eye', tooltip: 'Show the rendered templated query', tooltipPos: 'right', @@ -732,6 +768,10 @@ const QueryComposer: React.FC = () => { icon="Delete" title="Clear" onClick={() => { + trackClick({ + component: ComponentType.ADHOC_QUERY, + element: ElementType.CLEAR_BUTTON, + }); setQuery(''); setExecutionId(null); }} diff --git a/components/Search/SearchOverview.tsx b/components/Search/SearchOverview.tsx index 28a2e22da..00134780a 100644 --- a/components/Search/SearchOverview.tsx +++ b/components/Search/SearchOverview.tsx @@ -1,12 +1,13 @@ import { isEmpty } from 'lodash'; import moment from 'moment'; -import React, { useMemo } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import CreatableSelect from 'react-select/creatable'; import { TableTagGroupSelect } from 'components/DataTableTags/TableTagGroupSelect'; import { UserAvatar } from 'components/UserBadge/UserAvatar'; import { UserSelect } from 'components/UserSelect/UserSelect'; +import { ComponentType, ElementType } from 'const/analytics'; import { IBoardPreview, IDataDocPreview, @@ -14,6 +15,8 @@ import { ITablePreview, } from 'const/search'; import { useShallowSelector } from 'hooks/redux/useShallowSelector'; +import { useTrackView } from 'hooks/useTrackView'; +import { trackClick, trackView } from 'lib/analytics'; import { titleize } from 'lib/utils'; import { getCurrentEnv } from 'lib/utils/query-string'; import { @@ -69,9 +72,18 @@ interface ISearchOverviewProps { fromBoardId?: number; } +const SearchTypeToElementType = { + [SearchType.Query]: ElementType.QUERY_RESULT_ITEM, + [SearchType.DataDoc]: ElementType.DATADOC_RESULT_ITEM, + [SearchType.Table]: ElementType.TABLE_RESULT_ITEM, + [SearchType.Board]: ElementType.LIST_RESULT_ITEM, +}; + export const SearchOverview: React.FC = ({ fromBoardId, }) => { + useTrackView(ComponentType.SEARCH_MODAL); + const { resultByPage, currentPage, @@ -100,6 +112,18 @@ export const SearchOverview: React.FC = ({ const results = resultByPage[currentPage] || []; const isLoading = !!searchRequest; + // Log search results + useEffect(() => { + if (!isLoading && searchString.length > 0 && results.length > 0) { + const elementType = SearchTypeToElementType[searchType]; + trackView(ComponentType.SEARCH_MODAL, elementType, { + search: searchString, + results: results.map((r) => r.id), + page: currentPage, + }); + } + }, [isLoading, searchString, results]); + const dispatch = useDispatch(); const handleUpdateSearchString = React.useCallback( (searchStringParam: string) => { @@ -141,10 +165,12 @@ export const SearchOverview: React.FC = ({ { name: 'Query', key: SearchType.Query, + elementType: ElementType.QUERY_SEARCH_TAB, }, { name: 'DataDoc', key: SearchType.DataDoc, + elementType: ElementType.DATADOC_SEARCH_TAB, }, ]; @@ -152,12 +178,14 @@ export const SearchOverview: React.FC = ({ searchTabs.push({ name: 'Tables', key: SearchType.Table, + elementType: ElementType.TABLE_SEARCH_TAB, }); } searchTabs.push({ name: 'List', key: SearchType.Board, + elementType: ElementType.LIST_SEARCH_TAB, }); return searchTabs; @@ -167,6 +195,13 @@ export const SearchOverview: React.FC = ({ const onSearchTabSelect = React.useCallback( (newSearchType: string) => { + const elementType = searchTabs.find( + (t) => t.key === newSearchType + ).elementType; + trackClick({ + component: ComponentType.SEARCH_MODAL, + element: elementType, + }); updateSearchType(newSearchType); }, [updateSearchType] @@ -210,6 +245,23 @@ export const SearchOverview: React.FC = ({ [updateSearchFilter] ); + const onTrackClick = React.useCallback( + (pos) => { + const elementType = SearchTypeToElementType[searchType]; + trackClick({ + component: ComponentType.SEARCH_MODAL, + element: elementType, + aux: { + search: searchString, + results: results.map((r) => r.id), + page: currentPage, + pos, + }, + }); + }, + [searchType, searchString, results, currentPage] + ); + const getSearchBarDOM = () => { const placeholder = searchType === SearchType.Query @@ -339,23 +391,25 @@ export const SearchOverview: React.FC = ({ const environment = getCurrentEnv(); const resultsDOM = searchType === SearchType.Query - ? (results as IQueryPreview[]).map((result) => ( + ? (results as IQueryPreview[]).map((result, index) => ( onTrackClick(index)} /> )) : searchType === SearchType.DataDoc - ? (results as IDataDocPreview[]).map((result) => ( + ? (results as IDataDocPreview[]).map((result, index) => ( onTrackClick(index)} /> )) : searchType === SearchType.Table @@ -366,16 +420,19 @@ export const SearchOverview: React.FC = ({ url={`/${environment.name}/table/${result.id}/`} searchString={searchString} fromBoardId={fromBoardId} - pos={index} + currentPage={currentPage} + index={index} + onTrackClick={() => onTrackClick(index)} /> )) - : (results as IBoardPreview[]).map((result) => ( + : (results as IBoardPreview[]).map((result, index) => ( onTrackClick(index)} /> )); diff --git a/components/Search/SearchResultItem.tsx b/components/Search/SearchResultItem.tsx index 581d5d3b9..26e783d7f 100644 --- a/components/Search/SearchResultItem.tsx +++ b/components/Search/SearchResultItem.tsx @@ -4,7 +4,6 @@ import { useSelector } from 'react-redux'; import { BoardItemAddButton } from 'components/BoardItemAddButton/BoardItemAddButton'; import { UserAvatar } from 'components/UserBadge/UserAvatar'; -import { ComponentType, ElementType } from 'const/analytics'; import { IBoardPreview, IDataDocPreview, @@ -12,7 +11,6 @@ import { ITablePreview, } from 'const/search'; import { useUser } from 'hooks/redux/useUser'; -import { trackClick } from 'lib/analytics'; import history from 'lib/router-history'; import { generateFormattedDate } from 'lib/utils/datetime'; import { stopPropagation } from 'lib/utils/noop'; @@ -88,6 +86,7 @@ interface IQueryItemProps { searchString: string; environmentName: string; fromBoardId: number | undefined; + onTrackClick: () => void; } export const QueryItem: React.FunctionComponent = ({ @@ -95,6 +94,7 @@ export const QueryItem: React.FunctionComponent = ({ environmentName, searchString, fromBoardId, + onTrackClick, }) => { const { author_uid: authorUid, @@ -112,7 +112,13 @@ export const QueryItem: React.FunctionComponent = ({ const url = isQueryCell ? `/${environmentName}/datadoc/${preview.data_doc_id}/?cellId=${id}` : `/${environmentName}/query_execution/${id}/`; - const handleClick = React.useMemo(() => openClick.bind(null, url), [url]); + const handleClick = React.useCallback( + (e) => { + onTrackClick(); + openClick(url, e); + }, + [url, onTrackClick] + ); const queryEngineById = useSelector(queryEngineByIdEnvSelector); const selfRef = useRef(); @@ -239,6 +245,7 @@ interface IDataDocItemProps { searchString: string; url: string; fromBoardId: number | undefined; + onTrackClick: () => void; } export const DataDocItem: React.FunctionComponent = ({ @@ -246,12 +253,19 @@ export const DataDocItem: React.FunctionComponent = ({ url, searchString, fromBoardId, + onTrackClick, }) => { const selfRef = useRef(); const { owner_uid: ownerUid, created_at: createdAt, id } = preview; const { userInfo: ownerInfo, loading } = useUser({ uid: ownerUid }); - const handleClick = React.useMemo(() => openClick.bind(null, url), [url]); + const handleClick = React.useCallback( + (e) => { + onTrackClick(); + openClick(url, e); + }, + [url, onTrackClick] + ); if (loading) { return ( @@ -327,7 +341,9 @@ interface IDataTableItemProps { searchString: string; url: string; fromBoardId: number | undefined; - pos: number; + currentPage: number; + index: number; + onTrackClick: () => void; } export const DataTableItem: React.FunctionComponent = ({ @@ -335,7 +351,7 @@ export const DataTableItem: React.FunctionComponent = ({ searchString, url, fromBoardId, - pos, + onTrackClick, }) => { const selfRef = useRef(); const { @@ -349,18 +365,10 @@ export const DataTableItem: React.FunctionComponent = ({ } = preview; const handleClick = React.useCallback( (e) => { - trackClick({ - component: ComponentType.LEFT_SIDEBAR, - element: ElementType.TABLE_RESULT_ITEM, - aux: { - search: searchString, - table: id, - pos, - }, - }); + onTrackClick(); openClick(url, e); }, - [url, id, pos, searchString] + [url, onTrackClick] ); const goldenIcon = golden ? ( @@ -445,11 +453,18 @@ export const BoardItem: React.FunctionComponent<{ url: string; searchString: string; fromBoardId: number | undefined; -}> = ({ preview, url, searchString, fromBoardId }) => { + onTrackClick: () => void; +}> = ({ preview, url, searchString, fromBoardId, onTrackClick }) => { const selfRef = useRef(); const { owner_uid: ownerUid, description, id } = preview; const { userInfo: ownerInfo, loading } = useUser({ uid: ownerUid }); - const handleClick = React.useMemo(() => openClick.bind(null, url), [url]); + const handleClick = React.useCallback( + (e) => { + onTrackClick(); + openClick(url, e); + }, + [url, onTrackClick] + ); if (loading) { return ( diff --git a/components/UIGuide/DataDocUIGuide.tsx b/components/UIGuide/DataDocUIGuide.tsx index 9c0926f79..56e32082f 100644 --- a/components/UIGuide/DataDocUIGuide.tsx +++ b/components/UIGuide/DataDocUIGuide.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { useLocation } from 'react-router-dom'; import Tour, { ReactourStep } from 'reactour'; +import { ComponentType, ElementType } from 'const/analytics'; +import { trackClick } from 'lib/analytics'; import { getQueryString } from 'lib/utils/query-string'; import { IconButton } from 'ui/Button/IconButton'; import { Title } from 'ui/Title/Title'; @@ -143,7 +145,13 @@ export const DataDocUIGuide: React.FunctionComponent = () => { return (
setIsOpen(true)} + onClick={() => { + trackClick({ + component: ComponentType.DATADOC_PAGE, + element: ElementType.DATADOC_UI_GUIDE_BUTTON, + }); + setIsOpen(true); + }} icon="HelpCircle" tooltipPos="left" tooltip="DataDoc UI Guide" diff --git a/components/UIGuide/QuerybookSidebarUIGuide.tsx b/components/UIGuide/QuerybookSidebarUIGuide.tsx index 0d76f3883..c85d0ca5f 100644 --- a/components/UIGuide/QuerybookSidebarUIGuide.tsx +++ b/components/UIGuide/QuerybookSidebarUIGuide.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { useLocation } from 'react-router-dom'; import Tour, { ReactourStep } from 'reactour'; +import { ComponentType, ElementType } from 'const/analytics'; +import { trackClick } from 'lib/analytics'; import { getAppName } from 'lib/utils/global'; import { getQueryString } from 'lib/utils/query-string'; import { Button } from 'ui/Button/Button'; @@ -213,6 +215,10 @@ export const QuerybookSidebarUIGuide: React.FC = () => { const [showTour, setShowTour] = React.useState(false); const startTour = React.useCallback(() => { + trackClick({ + component: ComponentType.LANDING_PAGE, + element: ElementType.TUTORIAL_BUTTON, + }); setSteps((oldTour) => oldTour.length ? oldTour : getQuerybookSidebarTourSteps() ); diff --git a/const/analytics.ts b/const/analytics.ts index 54d35d5a1..a52ab0e18 100644 --- a/const/analytics.ts +++ b/const/analytics.ts @@ -13,13 +13,21 @@ export enum ComponentType { CHANGE_LOG = 'CHANGE_LOG', LEFT_SIDEBAR = 'LEFT_SIDEBAR', SEARCH_MODAL = 'SEARCH_MODAL', + DATADOC_PAGE = 'DATADOC_PAGE', + ADHOC_QUERY = 'ADHOC_QUERY', + DATADOC_QUERY_CELL = 'DATADOC_QUERY_CELL', + RIGHT_SIDEBAR = 'RIGHT_SIDEBAR', + TABLE_DETAIL_VIEW = 'TABLE_DETAIL_VIEW', + TABLE_NAVIGATOR_SEARCH = 'TABLE_NAVIGATOR_SEARCH', } export enum ElementType { // Landing page TUTORIAL_BUTTON = 'TUTORIAL_BUTTON', + RECENT_DATADOC = 'RECENT_DATADOC', + FAVORITE_DATADOC = 'FAVORITE_DATADOC', - // Side bar + // Left sidebar HOME_BUTTON = 'HOME_BUTTON', SEARCH_BUTTON = 'SEARCH_BUTTON', ADHOC_BUTTON = 'ADHOC_BUTTON', @@ -32,11 +40,59 @@ export enum ElementType { SETTINGS_BUTTON = 'SETTINGS_BUTTON', HELP_BUTTON = 'HELP_BUTTON', + // Table navigator search + TABLE_ORDER_BY_BUTTON = 'TABLE_ORDER_BY_BUTTON', + VIEW_TABLE_BUTTON = 'VIEW_TABLE_BUTTON', + + // Right sidebar + GO_TO_TOP_BUTTON = 'GO_TO_TOP_BUTTON', + COLLAPSE_DATADOC_BUTTON = 'COLLAPSE_DOC_BUTTON', + DATADOC_UI_GUIDE_BUTTON = 'DATADOC_UI_GUIDE_BUTTON', + RUN_ALL_CELLS_BUTTON = 'RUN_ALL_CELLS_BUTTON', + DAG_EXPORTER_BUTTON = 'DAG_EXPORTER_BUTTON', + LISTS_BUTTON = 'VIEW_LISTS_BUTTON', + SCHEDULE_DATADOC_BUTTON = 'SCHEDULE_BUTTON', + CLONE_DATADOC_BUTTON = 'CLONE_DATADOC_BUTTON', + DELETE_DATADOC_BUTTON = 'DELETE_DATADOC_BUTTON', + // Search modal + QUERY_SEARCH_TAB = 'QUERY_SEARCH_TAB', + DATADOC_SEARCH_TAB = 'DATADOC_SEARCH_TAB', + TABLE_SEARCH_TAB = 'TABLE_SEARCH_TAB', + LIST_SEARCH_TAB = 'LIST_SEARCH_TAB', + QUERY_RESULT_ITEM = 'QUERY_RESULT_ITEM', + DATADOC_RESULT_ITEM = 'DATADOC_RESULT_ITEM', TABLE_RESULT_ITEM = 'TABLE_RESULT_ITEM', + LIST_RESULT_ITEM = 'LIST_RESULT_ITEM', - // Data doc + // Datadoc page + SHARE_DATADOC_BUTTON = 'SHARE_DATADOC_BUTTON', + INSERT_CELL_BUTTON = 'INSERT_CELL_BUTTON', + SHARE_CELL_BUTTON = 'SHARE_CELL_BUTTON', + COPY_CELL_BUTTON = 'COPY_CELL_BUTTON', + CUT_CELL_BUTTON = 'CUT_CELL_BUTTON', + PASTE_CELL_BUTTON = 'PASTE_CELL_BUTTON', + COLLAPSE_CELL_BUTTON = 'COLLAPSE_CELL_BUTTON', + DELETE_CELL_BUTTON = 'DELETE_CELL_BUTTON', + MOVE_CELL_BUTTON = 'MOVE_CELL_BUTTON', + + // Query Cell RUN_QUERY_BUTTON = 'RUN_QUERY_BUTTON', + FORMAT_BUTTON = 'FORMAT_BUTTON', + CLEAR_BUTTON = 'CLEAR_BUTTON', + TEMPLATE_CONFIG_BUTTON = 'TEMPLATE_CONFIG_BUTTON', + RENDER_QUERY_BUTTON = 'RENDER_QUERY_BUTTON', + CREATE_DATADOC_BUTTON = 'CREATE_DATADOC_BUTTON', + + // Table detail view + OVERVIEW_TABLE_TAB = 'OVERVIEW_TABLE_TAB', + COLUMNS_TABLE_TAB = 'COLUMNS_TABLE_TAB', + ROW_SAMPLES_TABLE_TAB = 'ROW_SAMPLES_TABLE_TAB', + LINEAGE_TABLE_TAB = 'LINEAGE_TABLE_TAB', + SOURCE_QUERY_TABLE_TAB = 'SOURCE_QUERY_TABLE_TAB', + QUERY_EXAMPLES_TABLE_TAB = 'QUERY_EXAMPLES_TABLE_TAB', + LISTS_TABLE_TAB = 'LISTS_TABLE_TAB', + WARNINGS_TABLE_TAB = 'WARNINGS_TABLE_TAB', } export interface EventData { diff --git a/hooks/queryEditor/useLint.ts b/hooks/queryEditor/useLint.ts index a96669e41..c9ef1df84 100644 --- a/hooks/queryEditor/useLint.ts +++ b/hooks/queryEditor/useLint.ts @@ -1,3 +1,11 @@ +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; + import { useDebounce } from 'hooks/useDebounce'; import CodeMirror from 'lib/codemirror'; import { getContextSensitiveWarnings } from 'lib/sql-helper/sql-context-sensitive-linter'; @@ -7,13 +15,6 @@ import { TableToken, } from 'lib/sql-helper/sql-lexer'; import { Nullable } from 'lib/typescript'; -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; function useTableLint(getTableByName: (schema: string, name: string) => any) { const tablesGettingLoadedRef = useRef>(new Set()); diff --git a/lib/analytics.ts b/lib/analytics.ts index 648d47d86..cd2b691ca 100644 --- a/lib/analytics.ts +++ b/lib/analytics.ts @@ -1,6 +1,7 @@ import { AnalyticsEvent, ComponentType, + ElementType, EventData, EventType, } from 'const/analytics'; @@ -23,10 +24,16 @@ const track = (eventType: EventType, eventData: EventData) => { }); }; -export const trackView = (component?: ComponentType) => { +export const trackView = ( + component?: ComponentType, + element?: ElementType, + aux?: object +) => { const eventData = { path: location.pathname, component, + element, + aux, }; track(EventType.VIEW, eventData); };