Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: hackathon metalytics (tracking team usage metrics) #26304

Merged
merged 60 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
faf9a05
wip
benjackwhite Nov 20, 2024
d631978
Add actual scope info
benjackwhite Nov 20, 2024
a699ede
fix
benjackwhite Nov 20, 2024
7b3910a
feat: creating API end point that writes to kafkta
Nov 20, 2024
56dac9d
Added app metrics 2 to hogql
benjackwhite Nov 20, 2024
0e32e8e
Merge branch 'hackathon/metalytics' of github.com:PostHog/posthog int…
benjackwhite Nov 20, 2024
ac7867c
Fixes
benjackwhite Nov 20, 2024
0656952
Update query snapshots
github-actions[bot] Nov 20, 2024
987da60
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 20, 2024
99629a1
Fix
benjackwhite Nov 21, 2024
371f082
Fixes
benjackwhite Nov 21, 2024
45b0758
Merge branch 'hackathon/metalytics' of github.com:PostHog/posthog int…
benjackwhite Nov 21, 2024
8e4b622
Update UI snapshots for `webkit` (2)
github-actions[bot] Nov 21, 2024
9eff497
Update UI snapshots for `chromium` (1)
github-actions[bot] Nov 21, 2024
16cbc1b
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 21, 2024
de074f4
Fix
benjackwhite Nov 21, 2024
ed84770
Merge branch 'hackathon/metalytics' of github.com:PostHog/posthog int…
benjackwhite Nov 21, 2024
f90bbe8
feat: Add members logic and user view tracking in metalytics component
Nov 21, 2024
89961eb
refactor: Separate view count and recent users loaders in metalyticsL…
Nov 21, 2024
76736c1
refactor: Update SQL query timestamp and interval syntax in metalytic…
Nov 21, 2024
913f846
feat: Add selectors for recent user members in metalyticsLogic
Nov 21, 2024
e2ed316
feat: Add console log for recentUserMembers in metalyticsLogic
Nov 21, 2024
b866feb
Fixes
benjackwhite Nov 21, 2024
1aea873
feature: person bubbles
Nov 21, 2024
07e7d64
feat: Add new icons and IconWithCount component to MetalyticsSummary
Nov 21, 2024
bb9a16d
style: Add margin between IconPulse and IconDashboard in MetalyticsSu…
Nov 21, 2024
63f2975
Added opener
benjackwhite Nov 21, 2024
3123cbe
fix: MetalyticsSummary component
Nov 21, 2024
67cb581
pulse icon
Nov 21, 2024
5d5f4e3
adding the pulse icon
Nov 21, 2024
2d2503b
Fixes
benjackwhite Nov 21, 2024
6eb546c
Fix
benjackwhite Nov 21, 2024
2240d16
feat: Add view count display to SidePanelActivityMetalytics component
Nov 21, 2024
fe40166
feat: Add recentUserMembers to SidePanelActivityMetalytics component
Nov 21, 2024
7685d59
feat: Replace UserBasicType with ProfileBubbles in SidePanelActivityM…
Nov 21, 2024
667764c
feat: Add ProfileBubbles for recent viewers in SidePanelActivityMetal…
Nov 21, 2024
8ff1c67
refactor: Adjust recent viewers section and ProfileBubbles limit
Nov 21, 2024
3fae703
feat: Make SidePanelActivityMetalytics scrollable with max height
Nov 21, 2024
790691d
refactor: Update Metalytics activity query to include timestamp and 3…
Nov 21, 2024
8a7fdc3
refactor: Improve HogQL query indentation in SidePanelActivityMetalytics
Nov 21, 2024
ec560b3
style: Place Total Views and Recent Viewers side by side in flex cont…
Nov 21, 2024
6c21efd
feat: Add feature flag for MetalyticsSummary component in TopBar
Nov 21, 2024
20e38ba
fix: sql query, styling and remvoing feature flag code
Nov 22, 2024
6e4699b
putting things behind a feature flag
surbhi-posthog Nov 26, 2024
39e88b3
fix: fixing feature styling
surbhi-posthog Nov 26, 2024
63c4172
Fixes
benjackwhite Dec 4, 2024
a0badd2
Fix up
benjackwhite Dec 4, 2024
779704f
Fixes
benjackwhite Dec 4, 2024
e87c922
Fixes
benjackwhite Dec 4, 2024
613f039
Fix
benjackwhite Dec 4, 2024
1b06679
Fix
benjackwhite Dec 4, 2024
1b6db67
Fixes
benjackwhite Dec 4, 2024
65c2b90
Merge branch 'master' into hackathon/metalytics
benjackwhite Dec 4, 2024
6475d5d
Fix
benjackwhite Dec 4, 2024
db0021c
Fix
benjackwhite Dec 4, 2024
3df86e6
Niceties
benjackwhite Dec 4, 2024
4cbae02
Fix
benjackwhite Dec 4, 2024
f5387e8
fix
benjackwhite Dec 4, 2024
50ee5bb
Remove query
benjackwhite Dec 5, 2024
37bd9c6
Merge branch 'master' into hackathon/metalytics
benjackwhite Dec 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion frontend/src/layout/navigation-3000/components/TopBar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,6 @@

.TopBar3000__actions {
display: flex;
flex-grow: 1;
gap: 0.5rem;
align-items: center;
justify-content: flex-end;
Expand Down
9 changes: 8 additions & 1 deletion frontend/src/layout/navigation-3000/components/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import clsx from 'clsx'
import { useActions, useValues } from 'kea'
import { router } from 'kea-router'
import { EditableField } from 'lib/components/EditableField/EditableField'
import { FlaggedFeature } from 'lib/components/FlaggedFeature'
import { MetalyticsSummary } from 'lib/components/Metalytics/MetalyticsSummary'
import { IconMenu } from 'lib/lemon-ui/icons'
import { Link } from 'lib/lemon-ui/Link'
import { Popover } from 'lib/lemon-ui/Popover/Popover'
Expand Down Expand Up @@ -101,7 +103,12 @@ export function TopBar(): JSX.Element | null {
)}
<Here breadcrumb={breadcrumbs[breadcrumbs.length - 1]} isOnboarding={isOnboarding} />
</div>
<div className="TopBar3000__actions" ref={setActionsContainer} />
<FlaggedFeature flag="metalytics">
<div className="shrink-1">
<MetalyticsSummary />
</div>
</FlaggedFeature>
<div className="TopBar3000__actions border-danger" ref={setActionsContainer} />
</div>
</div>
) : null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
import { ActivityScope, AvailableFeature } from '~/types'

import { SidePanelPaneHeader } from '../../components/SidePanelPaneHeader'
import { SidePanelActivityMetalytics } from './SidePanelActivityMetalytics'

const SCROLL_TRIGGER_OFFSET = 100

Expand Down Expand Up @@ -126,11 +127,11 @@ export const SidePanelActivity = (): JSX.Element => {
<SidePanelPaneHeader title="Team activity" />
<PayGateMini
feature={AvailableFeature.AUDIT_LOGS}
className="flex flex-col overflow-hidden flex-1"
className="flex flex-col flex-1 overflow-hidden"
overrideShouldShowGate={user?.is_impersonated || !!featureFlags[FEATURE_FLAGS.AUDIT_LOGS_ACCESS]}
>
<div className="flex flex-col overflow-hidden flex-1">
<div className="shrink-0 mx-2">
<div className="flex flex-col flex-1 overflow-hidden">
<div className="mx-2 shrink-0">
<LemonTabs
activeKey={activeTab as SidePanelActivityTab}
onChange={(key) => setActiveTab(key)}
Expand All @@ -143,76 +144,82 @@ export const SidePanelActivity = (): JSX.Element => {
key: SidePanelActivityTab.All,
label: 'All activity',
},
...(featureFlags[FEATURE_FLAGS.METALYTICS]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you got it right here.

? [
{
key: SidePanelActivityTab.Metalytics,
label: 'Analytics',
},
]
: []),
]}
/>
</div>

{/* Controls */}
<div className="shrink-0 space-y-2 px-2 pb-2">
{activeTab === SidePanelActivityTab.Unread ? (
<>
<LemonBanner type="info" dismissKey="notifications-introduction">
Notifications shows you changes others make to{' '}
<Link to={urls.savedInsights('history')}>Insights</Link> and{' '}
<Link to={urls.featureFlags('history')}>Feature Flags</Link> that you created. Come
join <Link to="https://posthog.com/community">our community forum</Link> and tell us
what else should be here!
</LemonBanner>
{activeTab === SidePanelActivityTab.Unread ? (
<div className="px-2 pb-2 space-y-2 shrink-0">
<LemonBanner type="info" dismissKey="notifications-introduction">
Notifications shows you changes others make to{' '}
<Link to={urls.savedInsights('history')}>Insights</Link> and{' '}
<Link to={urls.featureFlags('history')}>Feature Flags</Link> that you created. Come join{' '}
<Link to="https://posthog.com/community">our community forum</Link> and tell us what
else should be here!
</LemonBanner>

<div className="flex items-center justify-between gap-2">
{toggleExtendedDescription}
{hasUnread ? (
<LemonButton type="secondary" onClick={() => markAllAsRead()}>
Mark all as read
</LemonButton>
) : null}
</div>
</>
) : activeTab === SidePanelActivityTab.All ? (
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2">
{toggleExtendedDescription}
{allActivityResponseLoading ? <Spinner textColored /> : null}
</div>
{toggleExtendedDescription}
{hasUnread ? (
<LemonButton type="secondary" onClick={() => markAllAsRead()}>
Mark all as read
</LemonButton>
) : null}
</div>
</div>
) : activeTab === SidePanelActivityTab.All ? (
<div className="flex items-center justify-between gap-2 px-2 pb-2 space-y-2 shrink-0">
<div className="flex items-center gap-2">
{toggleExtendedDescription}
{allActivityResponseLoading ? <Spinner textColored /> : null}
</div>

<div className="flex items-center gap-2">
<span>Filter for activity on:</span>
<LemonSelect
size="small"
options={scopeMenuOptions}
placeholder="All activity"
value={(activeScopeMenuOption as ActivityScope) ?? undefined}
onChange={(value) =>
setFilters({
...filters,
scope: value ?? undefined,
item_id: undefined,
})
}
dropdownMatchSelectWidth={false}
/>
<div className="flex items-center gap-2">
<span>Filter for activity on:</span>
<LemonSelect
size="small"
options={scopeMenuOptions}
placeholder="All activity"
value={(activeScopeMenuOption as ActivityScope) ?? undefined}
onChange={(value) =>
setFilters({
...filters,
scope: value ?? undefined,
item_id: undefined,
})
}
dropdownMatchSelectWidth={false}
/>

<span>by</span>
<MemberSelect
value={filters?.user ?? null}
onChange={(user) =>
setFilters({
...filters,
user: user?.id ?? undefined,
})
}
/>
</div>
<span>by</span>
<MemberSelect
value={filters?.user ?? null}
onChange={(user) =>
setFilters({
...filters,
user: user?.id ?? undefined,
})
}
/>
</div>
) : null}
</div>
</div>
) : null}

<div className="flex flex-col flex-1 overflow-hidden" ref={contentRef} onScroll={handleScroll}>
<ScrollableShadows direction="vertical" innerClassName="p-2 space-y-px">
{activeTab === SidePanelActivityTab.Unread ? (
<>
{importantChangesLoading && !hasNotifications ? (
<LemonSkeleton className="my-2 h-12" repeat={10} fade />
<LemonSkeleton className="h-12 my-2" repeat={10} fade />
) : hasNotifications ? (
notifications.map((logItem, index) => (
<ActivityLogRow
Expand All @@ -222,15 +229,15 @@ export const SidePanelActivity = (): JSX.Element => {
/>
))
) : (
<div className="border rounded text-center border-dashed p-6 text-muted-alt">
<div className="p-6 text-center border border-dashed rounded text-muted-alt">
You're all caught up!
</div>
)}
</>
) : activeTab === SidePanelActivityTab.All ? (
<>
{allActivityResponseLoading && !allActivity.length ? (
<LemonSkeleton className="my-2 h-12" repeat={10} fade />
<LemonSkeleton className="h-12 my-2" repeat={10} fade />
) : allActivity.length ? (
<>
{allActivity.map((logItem, index) => (
Expand All @@ -241,7 +248,7 @@ export const SidePanelActivity = (): JSX.Element => {
/>
))}

<div className="m-4 h-10 flex items-center justify-center gap-2 text-muted-alt">
<div className="flex items-center justify-center h-10 gap-2 m-4 text-muted-alt">
{allActivityResponseLoading ? (
<>
<Spinner textColored /> Loading older activity
Expand All @@ -261,7 +268,7 @@ export const SidePanelActivity = (): JSX.Element => {
</div>
</>
) : (
<div className="border rounded text-center border-dashed p-6 flex flex-col gap-2 items-center">
<div className="flex flex-col items-center gap-2 p-6 text-center border border-dashed rounded">
<span>No activity yet</span>
{filters ? (
<LemonButton type="secondary" onClick={() => setFilters(null)}>
Expand All @@ -271,6 +278,8 @@ export const SidePanelActivity = (): JSX.Element => {
</div>
)}
</>
) : activeTab === SidePanelActivityTab.Metalytics ? (
<SidePanelActivityMetalytics />
) : null}
</ScrollableShadows>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Spinner, Tooltip } from '@posthog/lemon-ui'
import { useValues } from 'kea'
import { humanizeScope } from 'lib/components/ActivityLog/humanizeActivity'
import { metalyticsLogic } from 'lib/components/Metalytics/metalyticsLogic'
import { ProfileBubbles } from 'lib/lemon-ui/ProfilePicture/ProfileBubbles'

export function SidePanelActivityMetalytics(): JSX.Element {
const { scope, instanceId, viewCount, recentUserMembers, viewCountLoading, recentUsersLoading } =
useValues(metalyticsLogic)

if (!instanceId) {
return (
<p className="border-dashed ">
You can see internal analytics of how your Organization members are using PostHog for things such as
Dashboards, Insights, Playlists etc. Open an app to see the viewership data here.
</p>
)
}

const humanizedScope = `this ${scope ? humanizeScope(scope, true) : 'app'}`

return (
<div className="space-y-4 ">
<p>
You are viewing "meta" analytics of how your organization members are interacting with{' '}
<b>{humanizedScope}</b>.
</p>
<div className="flex flex-wrap gap-4">
<Tooltip
title={`The total number of times ${humanizedScope} has been viewed by members of your organization.`}
placement="top"
>
<div className="flex-1 p-4 border rounded bg-bg-light min-w-40">
<div className="text-sm text-muted">Views</div>
<div className="text-2xl font-semibold">
{viewCountLoading ? <Spinner /> : viewCount?.views ?? 0}
</div>
</div>
</Tooltip>

<Tooltip
title={`The total number of unique organization members who have viewed ${humanizedScope}.`}
placement="top"
>
<div className="flex-1 p-4 border rounded bg-bg-light min-w-40">
<div className="text-sm text-muted">Viewers</div>
<div className="text-2xl font-semibold">
{viewCountLoading ? <Spinner /> : viewCount?.users ?? 0}
</div>
</div>
</Tooltip>

<Tooltip title={`The most recent 30 users who have viewed ${humanizedScope}.`} placement="top">
<div className="flex-1 p-4 border rounded bg-bg-light min-w-40">
<div className="text-sm text-muted">Recent viewers (30 days)</div>
{recentUsersLoading ? (
<Spinner />
) : (
<ProfileBubbles
className="mt-2"
people={recentUserMembers.map((member) => ({
email: member.user.email,
name: member.user.first_name,
title: member.user.email,
}))}
limit={3}
/>
)}
</div>
</Tooltip>
</div>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const activityForSceneLogic = kea<activityForSceneLogicType>([
connect({
values: [sceneLogic, ['sceneConfig']],
}),

selectors({
sceneActivityFilters: [
(s) => [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { toParams } from 'lib/utils'
import posthog from 'posthog-js'
import { projectLogic } from 'scenes/projectLogic'

import { sidePanelStateLogic } from '../../sidePanelStateLogic'
import { ActivityFilters, activityForSceneLogic } from './activityForSceneLogic'
import type { sidePanelActivityLogicType } from './sidePanelActivityLogicType'

Expand All @@ -29,12 +30,14 @@ export interface ChangesResponse {
export enum SidePanelActivityTab {
Unread = 'unread',
All = 'all',
Metalytics = 'metalytics',
}

export const sidePanelActivityLogic = kea<sidePanelActivityLogicType>([
path(['scenes', 'navigation', 'sidepanel', 'sidePanelActivityLogic']),
connect({
values: [activityForSceneLogic, ['sceneActivityFilters'], projectLogic, ['currentProjectId']],
actions: [sidePanelStateLogic, ['openSidePanel']],
}),
actions({
togglePolling: (pageIsVisible: boolean) => ({ pageIsVisible }),
Expand Down Expand Up @@ -183,6 +186,11 @@ export const sidePanelActivityLogic = kea<sidePanelActivityLogicType>([
actions.loadOlderActivity()
}
},
openSidePanel: ({ options }) => {
if (options) {
actions.setActiveTab(options as SidePanelActivityTab)
}
},
})),
selectors({
allActivity: [
Expand Down
39 changes: 39 additions & 0 deletions frontend/src/lib/components/Metalytics/MetalyticsSummary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { IconPulse } from '@posthog/icons'
import { LemonBadge, LemonButton } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'

import { sidePanelStateLogic } from '~/layout/navigation-3000/sidepanel/sidePanelStateLogic'
import { SidePanelTab } from '~/types'

import { metalyticsLogic } from './metalyticsLogic'

export function MetalyticsSummary(): JSX.Element | null {
const { instanceId, viewCount, viewCountLoading } = useValues(metalyticsLogic)
const safeViewCount = viewCount?.views ?? 0
const safeUniqueUsers = viewCount?.users ?? 0
const { openSidePanel } = useActions(sidePanelStateLogic)

if (!instanceId || viewCountLoading) {
return null
}

return (
<span className="relative inline-flex">
<LemonButton
loading={viewCountLoading}
icon={<IconPulse />}
size="small"
onClick={() => openSidePanel(SidePanelTab.Activity, 'metalytics')}
tooltip={`${safeUniqueUsers} PostHog members have viewed this a total of ${safeViewCount} times. Click to see more.`}
/>
<LemonBadge.Number
count={safeViewCount}
size="small"
position="top-right"
showZero={false}
status="primary"
maxDigits={3}
/>
</span>
)
}
Loading
Loading