diff --git a/apps/spruce/src/analytics/distroSettings/useDistroSettingsAnalytics.ts b/apps/spruce/src/analytics/distroSettings/useDistroSettingsAnalytics.ts index df8dd725c..0fbb9ba31 100644 --- a/apps/spruce/src/analytics/distroSettings/useDistroSettingsAnalytics.ts +++ b/apps/spruce/src/analytics/distroSettings/useDistroSettingsAnalytics.ts @@ -6,7 +6,11 @@ import { slugs } from "constants/routes"; type Action = | { name: "Saved distro"; section: string } | { name: "Created new distro"; "distro.id": string } - | { name: "Clicked duplicate distro"; "distro.id": string }; + | { name: "Clicked duplicate distro"; "distro.id": string } + | { + name: "Clicked link"; + link: "Task Queue" | "Image Build Information" | "Image Event Log"; + }; export const useDistroSettingsAnalytics = () => { const { [slugs.distroId]: distroId } = useParams(); diff --git a/apps/spruce/src/analytics/image/useImageAnalytics.ts b/apps/spruce/src/analytics/image/useImageAnalytics.ts new file mode 100644 index 000000000..bbb18d2e7 --- /dev/null +++ b/apps/spruce/src/analytics/image/useImageAnalytics.ts @@ -0,0 +1,44 @@ +import { ColumnFiltersState, PaginationState } from "@leafygreen-ui/table"; +import { useParams } from "react-router-dom"; +import { useAnalyticsRoot } from "@evg-ui/lib/analytics/hooks"; +import { AnalyticsIdentifier } from "analytics/types"; +import { ImageTabRoutes, slugs } from "constants/routes"; + +type Action = + | { + name: "Filtered table"; + "table.name": + | "Image Event Log" + | "Operating System" + | "Packages" + | "Toolchains"; + "table.filters": ColumnFiltersState; + } + | { + name: "Changed table pagination"; + "table.name": "Operating System" | "Packages" | "Toolchains"; + "table.pagination": PaginationState; + } + | { + name: "Changed image"; + from: string; + to: string; + } + | { + name: "Changed tab"; + tab: ImageTabRoutes; + } + | { + name: "Clicked 'Load more events' button"; + } + | { + name: "Used global search"; + search: string; + }; + +export const useImageAnalytics = () => { + const { [slugs.imageId]: imageId } = useParams(); + return useAnalyticsRoot<Action, AnalyticsIdentifier>("Image", { + "image.id": imageId || "", + }); +}; diff --git a/apps/spruce/src/analytics/index.ts b/apps/spruce/src/analytics/index.ts index 15946968e..41c0539b0 100644 --- a/apps/spruce/src/analytics/index.ts +++ b/apps/spruce/src/analytics/index.ts @@ -16,3 +16,4 @@ export { useUserPatchesAnalytics } from "./patches/useUserPatchesAnalytics"; export { useProjectPatchesAnalytics } from "./patches/useProjectPatchesAnalytics"; export { useVersionAnalytics } from "./version/useVersionAnalytics"; export { useWaterfallAnalytics } from "./waterfall/useWaterfallAnalytics"; +export { useImageAnalytics } from "./image/useImageAnalytics"; diff --git a/apps/spruce/src/analytics/types.ts b/apps/spruce/src/analytics/types.ts index 983735edd..1711c4fe0 100644 --- a/apps/spruce/src/analytics/types.ts +++ b/apps/spruce/src/analytics/types.ts @@ -20,4 +20,5 @@ export type AnalyticsIdentifier = | "TaskQueue" | "UserPatches" | "Version" - | "Waterfall"; + | "Waterfall" + | "Image"; diff --git a/apps/spruce/src/pages/distroSettings/index.tsx b/apps/spruce/src/pages/distroSettings/index.tsx index 5f77ac746..c76b40a37 100644 --- a/apps/spruce/src/pages/distroSettings/index.tsx +++ b/apps/spruce/src/pages/distroSettings/index.tsx @@ -2,6 +2,7 @@ import { useQuery } from "@apollo/client"; import styled from "@emotion/styled"; import { sideNavItemSidePadding } from "@leafygreen-ui/side-nav"; import { useParams, Link, Navigate } from "react-router-dom"; +import { useDistroSettingsAnalytics } from "analytics"; import Icon from "components/Icon"; import { SideNav, @@ -33,6 +34,7 @@ import { DistroSettingsTabs } from "./Tabs"; const DistroSettings: React.FC = () => { usePageTitle("Distro Settings"); + const { sendEvent } = useDistroSettingsAnalytics(); const dispatchToast = useToastContext(); const { [slugs.distroId]: distroId, [slugs.tab]: currentTab } = useParams<{ [slugs.distroId]: string; @@ -89,6 +91,9 @@ const DistroSettings: React.FC = () => { <SideNavGroup glyph={<Icon glyph="Link" />} header="Links"> <SideNavItemLink data-cy="navitem-task-queue-link" + onClick={() => + sendEvent({ name: "Clicked link", link: "Task Queue" }) + } // @ts-expect-error: FIXME. This comment was added by an automated script. to={getTaskQueueRoute(distroId)} > @@ -97,6 +102,12 @@ const DistroSettings: React.FC = () => { {showImageVisibilityPage && ( <SideNavItemLink data-cy="navitem-image-build-information-link" + onClick={() => + sendEvent({ + name: "Clicked link", + link: "Image Build Information", + }) + } to={getImageRoute( data?.distro?.imageId ?? "", ImageTabRoutes.BuildInformation, @@ -108,6 +119,12 @@ const DistroSettings: React.FC = () => { {showImageVisibilityPage && ( <SideNavItemLink data-cy="navitem-image-event-log-link" + onClick={() => + sendEvent({ + name: "Clicked link", + link: "Image Event Log", + }) + } to={getImageRoute( data?.distro?.imageId ?? "", ImageTabRoutes.EventLog, diff --git a/apps/spruce/src/pages/image/GeneralTable/index.tsx b/apps/spruce/src/pages/image/GeneralTable/index.tsx index ecda9ae68..fab98b071 100644 --- a/apps/spruce/src/pages/image/GeneralTable/index.tsx +++ b/apps/spruce/src/pages/image/GeneralTable/index.tsx @@ -28,7 +28,7 @@ export const GeneralTable: React.FC<GeneralTableProps> = ({ imageId }) => { ImageGeneralQueryVariables >(IMAGE_GENERAL, { variables: { imageId }, - onError(err) { + onError: (err) => { dispatchToast.error( `There was an error loading the image general table: ${err.message}`, ); diff --git a/apps/spruce/src/pages/image/ImageEventLog/ImageEventLog.tsx b/apps/spruce/src/pages/image/ImageEventLog/ImageEventLog.tsx index 4931bda8c..022730a0e 100644 --- a/apps/spruce/src/pages/image/ImageEventLog/ImageEventLog.tsx +++ b/apps/spruce/src/pages/image/ImageEventLog/ImageEventLog.tsx @@ -3,6 +3,7 @@ import styled from "@emotion/styled"; import Card from "@leafygreen-ui/card"; import { SearchInput } from "@leafygreen-ui/search-input"; import { Subtitle } from "@leafygreen-ui/typography"; +import { useImageAnalytics } from "analytics"; import { LoadingButton } from "components/Buttons"; import { size } from "constants/tokens"; import { ImageEvent } from "gql/generated/types"; @@ -22,6 +23,8 @@ export const ImageEventLog: React.FC<ImageEventLogProps> = ({ handleFetchMore, loading, }) => { + const { sendEvent } = useImageAnalytics(); + const [globalSearch, setGlobalSearch] = useState(""); const handleGlobalSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => { setGlobalSearch(e.target.value); @@ -37,6 +40,9 @@ export const ImageEventLog: React.FC<ImageEventLogProps> = ({ aria-labelledby="event-log-global-search" data-cy="event-log-global-search" onChange={handleGlobalSearchChange} + onSubmit={() => { + sendEvent({ name: "Used global search", search: globalSearch }); + }} placeholder="Global search by name" value={globalSearch} /> @@ -61,7 +67,10 @@ export const ImageEventLog: React.FC<ImageEventLogProps> = ({ <LoadingButton data-cy="load-more-button" loading={loading} - onClick={handleFetchMore} + onClick={() => { + sendEvent({ name: "Clicked 'Load more events' button" }); + handleFetchMore(); + }} variant="primary" > Load more events diff --git a/apps/spruce/src/pages/image/ImageEventLog/ImageEventLogTable.tsx b/apps/spruce/src/pages/image/ImageEventLog/ImageEventLogTable.tsx index 8709ad14a..3465056d6 100644 --- a/apps/spruce/src/pages/image/ImageEventLog/ImageEventLogTable.tsx +++ b/apps/spruce/src/pages/image/ImageEventLog/ImageEventLogTable.tsx @@ -9,7 +9,9 @@ import { getFilteredRowModel, getFacetedUniqueValues, } from "@leafygreen-ui/table"; +import { useImageAnalytics } from "analytics"; import { BaseTable } from "components/Table/BaseTable"; +import { onChangeHandler } from "components/Table/utils"; import { tableColumnOffset } from "constants/tokens"; import { ImageEventEntry, @@ -68,7 +70,9 @@ export const ImageEventLogTable: React.FC<ImageEventLogTableProps> = ({ entries, globalFilter, }) => { + const { sendEvent } = useImageAnalytics(); const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]); + const tableContainerRef = useRef<HTMLDivElement>(null); const table = useLeafyGreenTable<ImageEventEntry>({ columns, @@ -77,7 +81,15 @@ export const ImageEventLogTable: React.FC<ImageEventLogTableProps> = ({ defaultColumn: { enableColumnFilter: false, }, - onColumnFiltersChange: setColumnFilters, + onColumnFiltersChange: onChangeHandler<ColumnFiltersState>( + setColumnFilters, + (f) => + sendEvent({ + name: "Filtered table", + "table.name": "Image Event Log", + "table.filters": f, + }), + ), state: { columnFilters, globalFilter, diff --git a/apps/spruce/src/pages/image/ImageSelect/index.tsx b/apps/spruce/src/pages/image/ImageSelect/index.tsx index d7ab0d156..f6a903251 100644 --- a/apps/spruce/src/pages/image/ImageSelect/index.tsx +++ b/apps/spruce/src/pages/image/ImageSelect/index.tsx @@ -2,6 +2,7 @@ import { useQuery } from "@apollo/client"; import { Combobox, ComboboxOption } from "@leafygreen-ui/combobox"; import { Skeleton } from "@leafygreen-ui/skeleton-loader"; import { useNavigate } from "react-router-dom"; +import { useImageAnalytics } from "analytics"; import { getImageRoute } from "constants/routes"; import { zIndex } from "constants/tokens"; import { useToastContext } from "context/toast"; @@ -13,6 +14,7 @@ interface ImageSelectProps { } export const ImageSelect: React.FC<ImageSelectProps> = ({ selectedImage }) => { + const { sendEvent } = useImageAnalytics(); const navigate = useNavigate(); const dispatchToast = useToastContext(); @@ -36,6 +38,11 @@ export const ImageSelect: React.FC<ImageSelectProps> = ({ selectedImage }) => { label="Images" // @ts-expect-error: onChange expects type string | null onChange={(imageId: string) => { + sendEvent({ + name: "Changed image", + from: selectedImage, + to: imageId, + }); navigate(getImageRoute(imageId)); }} placeholder="Select an image" diff --git a/apps/spruce/src/pages/image/OperatingSystemTable/index.tsx b/apps/spruce/src/pages/image/OperatingSystemTable/index.tsx index 8702506c0..0ca66e041 100644 --- a/apps/spruce/src/pages/image/OperatingSystemTable/index.tsx +++ b/apps/spruce/src/pages/image/OperatingSystemTable/index.tsx @@ -6,7 +6,9 @@ import { ColumnFiltersState, PaginationState, } from "@leafygreen-ui/table"; +import { useImageAnalytics } from "analytics"; import { BaseTable } from "components/Table/BaseTable"; +import { onChangeHandler } from "components/Table/utils"; import { DEFAULT_PAGE_SIZE } from "constants/index"; import { useToastContext } from "context/toast"; import { @@ -24,12 +26,14 @@ export const OperatingSystemTable: React.FC<OperatingSystemTableProps> = ({ imageId, }) => { const dispatchToast = useToastContext(); + const { sendEvent } = useImageAnalytics(); const [pagination, setPagination] = useState<PaginationState>({ pageIndex: 0, pageSize: DEFAULT_PAGE_SIZE, }); const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]); + const { data: osData, loading } = useQuery< ImageOperatingSystemQuery, ImageOperatingSystemQueryVariables @@ -71,8 +75,22 @@ export const OperatingSystemTable: React.FC<OperatingSystemTableProps> = ({ pagination, columnFilters, }, - onColumnFiltersChange: setColumnFilters, - onPaginationChange: setPagination, + onColumnFiltersChange: onChangeHandler<ColumnFiltersState>( + setColumnFilters, + (f) => + sendEvent({ + name: "Filtered table", + "table.name": "Operating System", + "table.filters": f, + }), + ), + onPaginationChange: onChangeHandler<PaginationState>(setPagination, (p) => + sendEvent({ + name: "Changed table pagination", + "table.name": "Operating System", + "table.pagination": p, + }), + ), }); return ( diff --git a/apps/spruce/src/pages/image/PackagesTable/index.tsx b/apps/spruce/src/pages/image/PackagesTable/index.tsx index 2b3ca159b..1aa90583b 100644 --- a/apps/spruce/src/pages/image/PackagesTable/index.tsx +++ b/apps/spruce/src/pages/image/PackagesTable/index.tsx @@ -6,7 +6,9 @@ import { ColumnFiltersState, PaginationState, } from "@leafygreen-ui/table"; +import { useImageAnalytics } from "analytics"; import { BaseTable } from "components/Table/BaseTable"; +import { onChangeHandler } from "components/Table/utils"; import { DEFAULT_PAGE_SIZE } from "constants/index"; import { useToastContext } from "context/toast"; import { @@ -22,11 +24,13 @@ type PackagesTableProps = { export const PackagesTable: React.FC<PackagesTableProps> = ({ imageId }) => { const dispatchToast = useToastContext(); + const { sendEvent } = useImageAnalytics(); const [pagination, setPagination] = useState<PaginationState>({ pageIndex: 0, pageSize: DEFAULT_PAGE_SIZE, }); const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]); + const { data: packagesData, loading } = useQuery< ImagePackagesQuery, ImagePackagesQueryVariables @@ -68,8 +72,22 @@ export const PackagesTable: React.FC<PackagesTableProps> = ({ imageId }) => { pagination, columnFilters, }, - onColumnFiltersChange: setColumnFilters, - onPaginationChange: setPagination, + onColumnFiltersChange: onChangeHandler<ColumnFiltersState>( + setColumnFilters, + (f) => + sendEvent({ + name: "Filtered table", + "table.name": "Packages", + "table.filters": f, + }), + ), + onPaginationChange: onChangeHandler<PaginationState>(setPagination, (p) => + sendEvent({ + name: "Changed table pagination", + "table.name": "Packages", + "table.pagination": p, + }), + ), }); return ( diff --git a/apps/spruce/src/pages/image/ToolchainsTable/index.tsx b/apps/spruce/src/pages/image/ToolchainsTable/index.tsx index e0670323c..f2a912b1d 100644 --- a/apps/spruce/src/pages/image/ToolchainsTable/index.tsx +++ b/apps/spruce/src/pages/image/ToolchainsTable/index.tsx @@ -6,7 +6,9 @@ import { ColumnFiltersState, PaginationState, } from "@leafygreen-ui/table"; +import { useImageAnalytics } from "analytics"; import { BaseTable } from "components/Table/BaseTable"; +import { onChangeHandler } from "components/Table/utils"; import { DEFAULT_PAGE_SIZE } from "constants/index"; import { useToastContext } from "context/toast"; import { @@ -24,6 +26,7 @@ export const ToolchainsTable: React.FC<ToolchainsTableProps> = ({ imageId, }) => { const dispatchToast = useToastContext(); + const { sendEvent } = useImageAnalytics(); const [pagination, setPagination] = useState<PaginationState>({ pageIndex: 0, pageSize: DEFAULT_PAGE_SIZE, @@ -67,8 +70,22 @@ export const ToolchainsTable: React.FC<ToolchainsTableProps> = ({ }, manualFiltering: true, manualPagination: true, - onColumnFiltersChange: setColumnFilters, - onPaginationChange: setPagination, + onColumnFiltersChange: onChangeHandler<ColumnFiltersState>( + setColumnFilters, + (f) => + sendEvent({ + name: "Filtered table", + "table.name": "Toolchains", + "table.filters": f, + }), + ), + onPaginationChange: onChangeHandler<PaginationState>(setPagination, (p) => + sendEvent({ + name: "Changed table pagination", + "table.name": "Toolchains", + "table.pagination": p, + }), + ), state: { pagination, columnFilters, diff --git a/apps/spruce/src/pages/image/index.tsx b/apps/spruce/src/pages/image/index.tsx index 4f8ae979c..98b648eef 100644 --- a/apps/spruce/src/pages/image/index.tsx +++ b/apps/spruce/src/pages/image/index.tsx @@ -1,6 +1,7 @@ import styled from "@emotion/styled"; import { sideNavItemSidePadding } from "@leafygreen-ui/side-nav"; import { Link, useParams, Navigate } from "react-router-dom"; +import { useImageAnalytics } from "analytics"; import { SideNav, SideNavGroup, @@ -20,9 +21,9 @@ const Image: React.FC = () => { [slugs.imageId]: string; [slugs.tab]: ImageTabRoutes; }>(); + const { sendEvent } = useImageAnalytics(); const { image: firstImage } = useFirstImage(); - const selectedImage = imageId ?? firstImage; if ( @@ -50,6 +51,7 @@ const Image: React.FC = () => { active={tab === currentTab} as={Link} data-cy={`navitem-${tab}`} + onClick={() => sendEvent({ name: "Changed tab", tab })} to={getImageRoute(selectedImage, tab)} > {getTabTitle(tab).title}