diff --git a/apps/studio/components/grid/components/grid/Grid.tsx b/apps/studio/components/grid/components/grid/Grid.tsx index a48ce79e7c8..e59a571e5da 100644 --- a/apps/studio/components/grid/components/grid/Grid.tsx +++ b/apps/studio/components/grid/components/grid/Grid.tsx @@ -4,6 +4,7 @@ import { forwardRef, useRef } from 'react' import DataGrid, { DataGridHandle, RowsChangeData } from 'react-data-grid' import { memo } from 'react-tracked' +import { TelemetryActions } from 'common/telemetry-constants' import { formatClipboardValue } from 'components/grid/utils/common' import { TableGridInnerLoadingState } from 'components/interfaces/TableGridEditor/LoadingState' import { formatForeignKeys } from 'components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.utils' @@ -11,6 +12,8 @@ import { ForeignRowSelectorProps } from 'components/interfaces/TableGridEditor/S import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import AlertError from 'components/ui/AlertError' import { useForeignKeyConstraintsQuery } from 'data/database/foreign-key-constraints-query' +import { useSendEventMutation } from 'data/telemetry/send-event-mutation' +import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useUrlState } from 'hooks/ui/useUrlState' import { copyToClipboard } from 'lib/helpers' import { Button, cn } from 'ui' @@ -133,6 +136,8 @@ export const Grid = memo( const table = state.table + const { mutate: sendEvent } = useSendEventMutation() + const org = useSelectedOrganization() const { project } = useProjectContext() const { data } = useForeignKeyConstraintsQuery({ projectRef: project?.ref, @@ -214,8 +219,21 @@ export const Grid = memo(

{onAddRow !== undefined && onImportData !== undefined && ( - )}
diff --git a/apps/studio/components/grid/components/header/Header.tsx b/apps/studio/components/grid/components/header/Header.tsx index 1b414f79ff1..c86b0e44424 100644 --- a/apps/studio/components/grid/components/header/Header.tsx +++ b/apps/studio/components/grid/components/header/Header.tsx @@ -1,10 +1,13 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import saveAs from 'file-saver' import { ArrowUp, ChevronDown, FileText, Trash } from 'lucide-react' +import Link from 'next/link' import Papa from 'papaparse' import { ReactNode, useState } from 'react' import { toast } from 'sonner' +import { useParams } from 'common' +import { TelemetryActions } from 'common/telemetry-constants' import { useDispatch, useTrackedState } from 'components/grid/store/Store' import type { Filter, Sort, SupaTable } from 'components/grid/types' import { formatTableRowsToSQL } from 'components/interfaces/TableGridEditor/TableEntity.utils' @@ -12,7 +15,9 @@ import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectConte import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useTableRowsCountQuery } from 'data/table-rows/table-rows-count-query' import { fetchAllTableRows, useTableRowsQuery } from 'data/table-rows/table-rows-query' +import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useUrlState } from 'hooks/ui/useUrlState' import { useRoleImpersonationStateSnapshot, @@ -30,7 +35,6 @@ import { } from 'ui' import FilterPopover from './filter/FilterPopover' import { SortPopover } from './sort' -import Link from 'next/link' // [Joshen] CSV exports require this guard as a fail-safe if the table is // just too large for a browser to keep all the rows in memory before @@ -105,6 +109,9 @@ type DefaultHeaderProps = { onImportData?: () => void } const DefaultHeader = ({ table, onAddColumn, onAddRow, onImportData }: DefaultHeaderProps) => { + const { ref } = useParams() + const org = useSelectedOrganization() + const canAddNew = onAddRow !== undefined || onAddColumn !== undefined // [Joshen] Using this logic to block both column and row creation/update/delete @@ -114,6 +121,8 @@ const DefaultHeader = ({ table, onAddColumn, onAddRow, onImportData }: DefaultHe arrayKeys: ['sort', 'filter'], }) + const { mutate: sendEvent } = useSendEventMutation() + return (
@@ -195,7 +204,17 @@ const DefaultHeader = ({ table, onAddColumn, onAddRow, onImportData }: DefaultHe { + onImportData() + sendEvent({ + action: TelemetryActions.IMPORT_DATA_BUTTON_CLICKED, + properties: { tableType: 'Existing Table' }, + groups: { + project: ref ?? 'Unknown', + organization: org?.slug ?? 'Unknown', + }, + }) + }} >
{ const { resolvedTheme } = useTheme() + const { ref: projectRef } = useParams() + const org = useSelectedOrganization() + + const { mutate: sendEvent } = useSendEventMutation() return ( - + + sendEvent({ + action: TelemetryActions.EXAMPLE_PROJECT_CARD_CLICKED, + properties: { cardTitle: title }, + groups: { project: projectRef ?? 'Unknown', organization: org?.slug ?? 'Unknown' }, + }) + } + >
{ +export const EnterpriseCard = ({ plan, isCurrentPlan, billingPartner }: EnterpriseCardProps) => { + const selectedOrganization = useSelectedOrganization() + const orgSlug = selectedOrganization?.slug + const features = pickFeatures(plan, billingPartner) + const { data: subscription } = useOrgSubscriptionQuery({ orgSlug }) + const currentPlan = subscription?.plan.name + + const { mutate: sendEvent } = useSendEventMutation() return (
) } - -export default EnterpriseCard diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx index edb8ef31603..e072a5a38d7 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx @@ -7,6 +7,7 @@ import { useRouter } from 'next/router' import { useEffect, useRef, useState } from 'react' import { toast } from 'sonner' +import { TelemetryActions } from 'common/telemetry-constants' import { billingPartnerLabel } from 'components/interfaces/Billing/Subscription/Subscription.utils' import Table from 'components/to-be-cleaned/Table' import AlertError from 'components/ui/AlertError' @@ -22,6 +23,7 @@ import { useOrgPlansQuery } from 'data/subscriptions/org-plans-query' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useOrgSubscriptionUpdateMutation } from 'data/subscriptions/org-subscription-update-mutation' import type { OrgPlan, SubscriptionTier } from 'data/subscriptions/types' +import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useFlag } from 'hooks/ui/useFlag' @@ -31,7 +33,7 @@ import { pickFeatures, pickFooter, plans as subscriptionsPlans } from 'shared-da import { useOrgSettingsPageStateSnapshot } from 'state/organization-settings' import { Button, Modal, SidePanel, cn } from 'ui' import DowngradeModal from './DowngradeModal' -import EnterpriseCard from './EnterpriseCard' +import { EnterpriseCard } from './EnterpriseCard' import ExitSurveyModal from './ExitSurveyModal' import MembersExceedLimitModal from './MembersExceedLimitModal' import PaymentMethodSelection from './PaymentMethodSelection' @@ -41,6 +43,7 @@ const PlanUpdateSidePanel = () => { const router = useRouter() const selectedOrganization = useSelectedOrganization() const slug = selectedOrganization?.slug + const { mutate: sendEvent } = useSendEventMutation() const queryClient = useQueryClient() const originalPlanRef = useRef() @@ -260,7 +263,17 @@ const PlanUpdateSidePanel = () => { hasOrioleProjects || !canUpdateSubscription } - onClick={() => setSelectedTier(plan.id as any)} + onClick={() => { + setSelectedTier(plan.id as any) + sendEvent({ + action: TelemetryActions.STUDIO_PRICING_PLAN_CTA_CLICKED, + properties: { + selectedPlan: plan.name, + currentPlan: subscription?.plan?.name, + }, + groups: { organization: slug ?? 'Unknown' }, + }) + }} tooltip={{ content: { side: 'bottom', diff --git a/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx b/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx index 43e5fb3b6d6..8c9c6aeb680 100644 --- a/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx +++ b/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx @@ -316,6 +316,11 @@ export const SQLEditor = () => { throw error }, }) + + sendEvent({ + action: TelemetryActions.SQL_EDITOR_QUERY_RUN_BUTTON_CLICKED, + groups: { project: ref ?? 'Unknown', organization: org?.slug ?? 'Unknown' }, + }) } }, // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx index 61eb987db1a..fb07121d2a3 100644 --- a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx +++ b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx @@ -13,7 +13,9 @@ import { toast } from 'sonner' import { useParams } from 'common' import { RoleImpersonationPopover } from 'components/interfaces/RoleImpersonationSelector' import DatabaseSelector from 'components/ui/DatabaseSelector' +import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' +import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { IS_PLATFORM, LOCAL_STORAGE_KEYS } from 'lib/constants' import { detectOS } from 'lib/helpers' import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2' @@ -51,6 +53,9 @@ const UtilityActions = ({ const os = detectOS() const { ref } = useParams() const snapV2 = useSqlEditorV2StateSnapshot() + const org = useSelectedOrganization() + + const { mutate: sendEvent } = useSendEventMutation() const [isAiOpen] = useLocalStorageQuery(LOCAL_STORAGE_KEYS.SQL_EDITOR_AI_OPEN, true) const [intellisenseEnabled, setIntellisenseEnabled] = useLocalStorageQuery( @@ -191,7 +196,7 @@ const UtilityActions = ({ />
) : ( - )} diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx index 1f43e731be2..14e527074d8 100644 --- a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx +++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx @@ -4,6 +4,13 @@ import Link from 'next/link' import { useRouter } from 'next/router' import { ChangeEvent, useEffect, useRef, useState } from 'react' import { toast } from 'sonner' + +import { useParams } from 'common' +import { TelemetryActions } from 'common/telemetry-constants' +import { useSendFeedbackMutation } from 'data/feedback/feedback-send' +import { useSendEventMutation } from 'data/telemetry/send-event-mutation' +import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' +import { timeout } from 'lib/helpers' import { Button, DropdownMenu, @@ -12,10 +19,6 @@ import { DropdownMenuTrigger, Input, } from 'ui' - -import { useParams } from 'common' -import { useSendFeedbackMutation } from 'data/feedback/feedback-send' -import { timeout } from 'lib/helpers' import { convertB64toBlob, uploadAttachment } from './FeedbackDropdown.utils' interface FeedbackWidgetProps { @@ -38,11 +41,14 @@ const FeedbackWidget = ({ const router = useRouter() const { ref, slug } = useParams() + const org = useSelectedOrganization() const uploadButtonRef = useRef(null) const [isSending, setSending] = useState(false) const [isSavingScreenshot, setIsSavingScreenshot] = useState(false) + const { mutate: sendEvent } = useSendEventMutation() + const { mutate: submitFeedback } = useSendFeedbackMutation({ onSuccess: () => { setFeedback('') @@ -261,7 +267,17 @@ const FeedbackWidget = ({ accept="image/png" onChange={onFilesUpload} /> -
diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPopover.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPopover.tsx index dca75ae9cab..35de6f6164c 100644 --- a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPopover.tsx +++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/HelpPopover.tsx @@ -4,7 +4,10 @@ import Link from 'next/link' import { useRouter } from 'next/router' import SVG from 'react-inlinesvg' +import { TelemetryActions } from 'common/telemetry-constants' import { ButtonTooltip } from 'components/ui/ButtonTooltip' +import { useSendEventMutation } from 'data/telemetry/send-event-mutation' +import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { Button, Popover, @@ -17,6 +20,10 @@ import { useProjectContext } from '../ProjectContext' const HelpPopover = () => { const router = useRouter() const { project } = useProjectContext() + const org = useSelectedOrganization() + + const { mutate: sendEvent } = useSendEventMutation() + const projectRef = project?.parent_project_ref ?? router.query.ref const supportUrl = `/support/new${projectRef ? `?ref=${projectRef}` : ''}` @@ -29,6 +36,12 @@ const HelpPopover = () => { className="px-1" icon={} tooltip={{ content: { side: 'bottom', text: 'Help' } }} + onClick={() => { + sendEvent({ + action: TelemetryActions.HELP_BUTTON_CLICKED, + groups: { project: project?.ref, organization: org?.slug }, + }) + }} /> diff --git a/apps/www/components/Pricing/PricingComparisonTable.tsx b/apps/www/components/Pricing/PricingComparisonTable.tsx index 22f838126c8..a8857901c8b 100644 --- a/apps/www/components/Pricing/PricingComparisonTable.tsx +++ b/apps/www/components/Pricing/PricingComparisonTable.tsx @@ -57,7 +57,7 @@ const MobileHeader = ({ organizations={organizations} onClick={() => sendTelemetryEvent({ - action: TelemetryActions.PRICING_PLAN_CTA_CLICKED, + action: TelemetryActions.WWW_PRICING_PLAN_CTA_CLICKED, properties: { plan, showUpgradeText: true, @@ -74,7 +74,7 @@ const MobileHeader = ({ href={selectedPlan.href} onClick={() => sendTelemetryEvent({ - action: TelemetryActions.PRICING_PLAN_CTA_CLICKED, + action: TelemetryActions.WWW_PRICING_PLAN_CTA_CLICKED, properties: { plan, showUpgradeText: false, @@ -403,7 +403,7 @@ const PricingComparisonTable = ({ organizations={organizations} onClick={() => sendTelemetryEvent({ - action: TelemetryActions.PRICING_PLAN_CTA_CLICKED, + action: TelemetryActions.WWW_PRICING_PLAN_CTA_CLICKED, properties: { plan: plan.name, showUpgradeText: true, @@ -425,7 +425,7 @@ const PricingComparisonTable = ({ href={plan.href} onClick={() => sendTelemetryEvent({ - action: TelemetryActions.PRICING_PLAN_CTA_CLICKED, + action: TelemetryActions.WWW_PRICING_PLAN_CTA_CLICKED, properties: { plan: plan.name, showUpgradeText: false, diff --git a/apps/www/components/Pricing/PricingPlans.tsx b/apps/www/components/Pricing/PricingPlans.tsx index 9c3a9b4f259..79869937bf1 100644 --- a/apps/www/components/Pricing/PricingPlans.tsx +++ b/apps/www/components/Pricing/PricingPlans.tsx @@ -29,7 +29,7 @@ const PricingPlans = ({ organizations, hasExistingOrganizations }: PricingPlansP const sendPricingEvent = () => { sendTelemetryEvent({ - action: TelemetryActions.PRICING_PLAN_CTA_CLICKED, + action: TelemetryActions.WWW_PRICING_PLAN_CTA_CLICKED, properties: { plan: plan.name, showUpgradeText: isUpgradablePlan && hasExistingOrganizations ? true : false, diff --git a/packages/common/telemetry-constants.ts b/packages/common/telemetry-constants.ts index 92d2d8960c4..79465c0ba8b 100644 --- a/packages/common/telemetry-constants.ts +++ b/packages/common/telemetry-constants.ts @@ -50,8 +50,7 @@ export enum TelemetryActions { HOMEPAGE_HERO_REQUEST_DEMO_CLICKED = 'homepage_hero_request_demo_clicked', HOMEPAGE_FRAMEWORK_QUICKSTART_CLICKED = 'homepage_framework_quickstart_clicked', HOMEPAGE_PRODUCT_CARD_CLICKED = 'homepage_product_card_clicked', - PRICING_PLAN_CTA_CLICKED = 'pricing_plan_cta_clicked', - PRICING_COMPARISON_PLAN_CLICKED = 'pricing_comparison_plan_clicked', + WWW_PRICING_PLAN_CTA_CLICKED = 'www_pricing_plan_cta_clicked', EVENT_PAGE_CTA_CLICKED = 'event_page_cta_clicked', HOMEPAGE_GITHUB_BUTTON_CLICKED = 'homepage_github_button_clicked', HOMEPAGE_GITHUB_DISCUSSIONS_BUTTON_CLICKED = 'homepage_github_discussions_button_clicked', @@ -63,6 +62,14 @@ export enum TelemetryActions { SEE_DOCUMENTATION_BUTTON_CLICKED = 'see_documentation_button_clicked', REQUEST_DEMO_BUTTON_CLICKED = 'request_demo_button_clicked', SIGN_IN_BUTTON_CLICKED = 'sign_in_button_clicked', + + HELP_BUTTON_CLICKED = 'help_button_clicked', + SEND_FEEDBACK_BUTTON_CLICKED = 'send_feedback_button_clicked', + EXAMPLE_PROJECT_CARD_CLICKED = 'example_project_card_clicked', + IMPORT_DATA_BUTTON_CLICKED = 'import_data_button_clicked', + IMPORT_DATA_ADDED = 'import_data_added', + SQL_EDITOR_QUERY_RUN_BUTTON_CLICKED = 'sql_editor_query_run_button_clicked', + STUDIO_PRICING_PLAN_CTA_CLICKED = 'studio_pricing_plan_cta_clicked', } /** @@ -734,14 +741,14 @@ export interface HomepageProductCardClickedEvent { } /** - * User clicked the green button on a plan in the pricing page. + * User clicked on the CTA button on a plan in the pricing page. * * @group Events * @source www * @page /pricing */ -export interface PricingPlanCtaClickedEvent { - action: TelemetryActions.PRICING_PLAN_CTA_CLICKED +export interface WwwPricingPlanCtaClickedEvent { + action: TelemetryActions.WWW_PRICING_PLAN_CTA_CLICKED properties: { /** * The plan type that was clicked. @@ -761,24 +768,6 @@ export interface PricingPlanCtaClickedEvent { } } -/** - * User clicked the plan in the pricing comparison section. - * - * @group Events - * @source www - * @page /pricing - */ -export interface PricingComparisonPlanClickedEvent { - action: TelemetryActions.PRICING_COMPARISON_PLAN_CLICKED - properties: { - category: 'pricing_comparison' - /** - * The plan type that was clicked. - */ - plan: 'free' | 'pro' | 'team' | 'enterprise' - } -} - /** * User clicked the main CTA button in an event page. * @@ -946,6 +935,130 @@ export interface SignInButtonClickedEvent { } } +/** + * User clicked the "Help" button in the top right corner of the page header. + * + * @group Events + * @source studio + */ +export interface HelpButtonClickedEvent { + action: TelemetryActions.HELP_BUTTON_CLICKED + groups: { + project?: string + organization?: string + } +} + +/** + * User clicked the "Send Feedback" button in the top right corner of the page header. + * + * @group Events + * @source studio + */ +export interface SendFeedbackButtonClickedEvent { + action: TelemetryActions.SEND_FEEDBACK_BUTTON_CLICKED + groups: { + project?: string + organization?: string + } +} + +/** + * User clicked on an example project card. + * + * @group Events + * @source studio + * @page /project/{ref} + */ +export interface ExampleProjectCardClickedEvent { + action: TelemetryActions.EXAMPLE_PROJECT_CARD_CLICKED + properties: { + /** + * The title of the example project card clicked. + */ + cardTitle: string + } + groups: { + project: string + organization: string + } +} + +/** + * User clicked the "Import Data" button. + * + * @group Events + * @source studio + * @page /dashboard/project/{ref}/editor + */ +export interface ImportDataButtonClickedEvent { + action: TelemetryActions.IMPORT_DATA_BUTTON_CLICKED + properties: { + /** + * The type of table the data is imported to. + * New Table means added when creating new table by clicking from New table sidebar, + * Existing Table means added to an existing table by going to the table and clicking from the green Insert button.. + */ + tableType: 'New Table' | 'Existing Table' + } + groups: { + project: string + organization: string + } +} + +/** + * User added data from the import data via CSV/spreadsheet successfully. + * + * @group Events + * @source studio + * @page /dashboard/project/{ref}/editor + */ +export interface ImportDataAddedEvent { + action: TelemetryActions.IMPORT_DATA_ADDED + groups: { + project: string + organization: string + } +} + +/** + * User clicked the run query button in the SQL editor. + * + * @group Events + * @source studio + * @page /dashboard/project/{ref}/sql/{id} + */ +export interface SqlEditorQueryRunButtonClickedEvent { + action: TelemetryActions.SQL_EDITOR_QUERY_RUN_BUTTON_CLICKED + groups: { + project: string + organization: string + } +} + +/** + * User clicked on the CTA button on a plan in the pricing side panel in studio. + * + * @group Events + * @source studio + * @page /billing?panel=subscriptionPlan + */ +export interface StudioPricingPlanCtaClickedEvent { + action: TelemetryActions.STUDIO_PRICING_PLAN_CTA_CLICKED + properties: { + /** + * The plan type that was clicked. + */ + selectedPlan: string + /** + * The plan type the org is currently on. + */ + currentPlan?: string + } + groups: { organization: string } +} + export type TelemetryEvent = | SignUpEvent | SignInEvent @@ -985,8 +1098,7 @@ export type TelemetryEvent = | HomepageHeroRequestDemoClickedEvent | HomepageFrameworkQuickstartClickedEvent | HomepageProductCardClickedEvent - | PricingPlanCtaClickedEvent - | PricingComparisonPlanClickedEvent + | WwwPricingPlanCtaClickedEvent | EventPageCtaClickedEvent | HomepageGitHubButtonClickedEvent | HomepageGitHubDiscussionsButtonClickedEvent @@ -1000,3 +1112,10 @@ export type TelemetryEvent = | SeeDocumentationButtonClickedEvent | RequestDemoButtonClickedEvent | SignInButtonClickedEvent + | HelpButtonClickedEvent + | ExampleProjectCardClickedEvent + | ImportDataButtonClickedEvent + | ImportDataAddedEvent + | SendFeedbackButtonClickedEvent + | SqlEditorQueryRunButtonClickedEvent + | StudioPricingPlanCtaClickedEvent