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(cdp): activity logs #25253

Merged
merged 41 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
5a95264
feat(cdp): activity logs
mariusandra Sep 27, 2024
22fe735
add 2 queries
mariusandra Sep 30, 2024
69e3349
save activity and mask encrypted inputs
mariusandra Sep 30, 2024
d800a8f
fix
mariusandra Sep 30, 2024
db6b57d
Merge branch 'master' into hog-activity-log
mariusandra Sep 30, 2024
894a96e
Merge branch 'master' into hog-activity-log
mariusandra Oct 1, 2024
40fe2be
fix tests
mariusandra Oct 1, 2024
5103aa4
Update UI snapshots for `chromium` (2)
github-actions[bot] Oct 1, 2024
5ee4602
Update UI snapshots for `chromium` (2)
github-actions[bot] Oct 1, 2024
e99cdee
Update UI snapshots for `chromium` (2)
github-actions[bot] Oct 1, 2024
51b496e
show actual changes
mariusandra Oct 1, 2024
d374ae7
Update UI snapshots for `chromium` (1)
github-actions[bot] Oct 1, 2024
a815b63
Update UI snapshots for `chromium` (1)
github-actions[bot] Oct 1, 2024
676e9d1
activity description diffs
mariusandra Oct 1, 2024
9d32243
merge lists
mariusandra Oct 1, 2024
381b0ed
Merge branch 'hog-activity-log' of github.com:PostHog/posthog into ho…
mariusandra Oct 1, 2024
fb35f5e
Merge branch 'master' into hog-activity-log
mariusandra Oct 1, 2024
2246196
Update UI snapshots for `chromium` (2)
github-actions[bot] Oct 1, 2024
16bbafb
Update UI snapshots for `chromium` (2)
github-actions[bot] Oct 1, 2024
3bfb38b
Merge branch 'master' into hog-activity-log
mariusandra Oct 8, 2024
16de157
Merge branch 'hog-activity-log' of github.com:PostHog/posthog into ho…
mariusandra Oct 8, 2024
bbfbfa0
light cleanup
mariusandra Oct 8, 2024
d1ba045
Merge branch 'master' into hog-activity-log
mariusandra Oct 8, 2024
e99235e
fix some tests
mariusandra Oct 8, 2024
afaedfa
number pagination
mariusandra Oct 8, 2024
d2e4d81
scope array
mariusandra Oct 8, 2024
7da4883
Merge branch 'master' into hog-activity-log
mariusandra Oct 15, 2024
3002bc2
support page number pagination as well
mariusandra Oct 15, 2024
c805d3c
type of hog function activity (only destinations for now)
mariusandra Oct 15, 2024
6b8cc59
standardize on count
mariusandra Oct 15, 2024
9fd38f8
order
mariusandra Oct 15, 2024
a89c895
order
mariusandra Oct 15, 2024
0e7e320
fix test
mariusandra Oct 15, 2024
aa3336f
mypy
mariusandra Oct 15, 2024
3bf90b4
Update UI snapshots for `chromium` (2)
github-actions[bot] Oct 15, 2024
7950b5f
Update UI snapshots for `chromium` (2)
github-actions[bot] Oct 15, 2024
cc80c69
mypy
mariusandra Oct 15, 2024
d09acaf
Merge branch 'hog-activity-log' of github.com:PostHog/posthog into ho…
mariusandra Oct 15, 2024
efd8f32
Merge branch 'master' into hog-activity-log
mariusandra Oct 15, 2024
dd0dda1
add plugin configs
mariusandra Oct 15, 2024
95ef4cc
one more
mariusandra Oct 15, 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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 44 additions & 18 deletions frontend/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export interface CountedPaginatedResponse<T> extends PaginatedResponse<T> {
}

export interface ActivityLogPaginatedResponse<T> extends PaginatedResponse<T> {
total_count: number // FIXME: This is non-standard naming, DRF uses `count` and we should use that consistently
count: number
}

export interface ApiMethodOptions {
Expand Down Expand Up @@ -1049,33 +1049,59 @@ const api = {
filters: Partial<Pick<ActivityLogItem, 'item_id' | 'scope'> & { user?: UserBasicType['id'] }>,
teamId: TeamType['id'] = ApiConfig.getCurrentTeamId()
): Promise<PaginatedResponse<ActivityLogItem>> {
return new ApiRequest().activity_log(teamId).withQueryString(toParams(filters)).get()
return api.activity.listRequest(filters, teamId).get()
},

listRequest(
filters: Partial<{
scope?: ActivityScope
scopes?: ActivityScope[] | string
user?: UserBasicType['id']
page?: number
page_size?: number
item_id?: number | string
}>,
teamId: TeamType['id'] = ApiConfig.getCurrentTeamId()
): ApiRequest {
if (Array.isArray(filters.scopes)) {
filters.scopes = filters.scopes.join(',')
}
return new ApiRequest().activity_log(teamId).withQueryString(toParams(filters))
},

listLegacy(
activityLogProps: ActivityLogProps,
props: ActivityLogProps,
page: number = 1,
teamId: TeamType['id'] = ApiConfig.getCurrentTeamId()
): Promise<ActivityLogPaginatedResponse<ActivityLogItem>> {
const scopes = Array.isArray(props.scope) ? [...props.scope] : [props.scope]

// Opt into the new /activity_log API
if ([ActivityScope.PLUGIN, ActivityScope.HOG_FUNCTION].includes(scopes[0]) || scopes.length > 1) {
return api.activity
.listRequest({
scopes,
...(props.id ? { item_id: props.id } : {}),
page: page || 1,
page_size: ACTIVITY_PAGE_SIZE,
})
.get()
}

// TODO: Can we replace all these endpoint specific implementations with the generic REST endpoint above?
const requestForScope: { [key in ActivityScope]?: (props: ActivityLogProps) => ApiRequest | null } = {
[ActivityScope.FEATURE_FLAG]: (props) => {
const requestForScope: { [key in ActivityScope]?: () => ApiRequest | null } = {
[ActivityScope.FEATURE_FLAG]: () => {
return new ApiRequest().featureFlagsActivity((props.id ?? null) as number | null, teamId)
},
[ActivityScope.PERSON]: (props) => {
[ActivityScope.PERSON]: () => {
return new ApiRequest().personActivity(props.id)
},
[ActivityScope.INSIGHT]: () => {
return new ApiRequest().insightsActivity(teamId)
},
[ActivityScope.PLUGIN]: () => {
return activityLogProps.id
? new ApiRequest().pluginConfig(activityLogProps.id as number, teamId).withAction('activity')
: new ApiRequest().plugins().withAction('activity')
},
[ActivityScope.PLUGIN_CONFIG]: () => {
return activityLogProps.id
? new ApiRequest().pluginConfig(activityLogProps.id as number, teamId).withAction('activity')
return props.id
? new ApiRequest().pluginConfig(props.id as number, teamId).withAction('activity')
: new ApiRequest().plugins().withAction('activity')
},
[ActivityScope.DATA_MANAGEMENT]: () => {
Expand All @@ -1090,21 +1116,21 @@ const api = {
return new ApiRequest().dataManagementActivity()
},
[ActivityScope.NOTEBOOK]: () => {
return activityLogProps.id
? new ApiRequest().notebook(`${activityLogProps.id}`).withAction('activity')
return props.id
? new ApiRequest().notebook(`${props.id}`).withAction('activity')
: new ApiRequest().notebooks().withAction('activity')
},
[ActivityScope.TEAM]: () => {
return new ApiRequest().projectsDetail().withAction('activity')
},
[ActivityScope.SURVEY]: (props) => {
[ActivityScope.SURVEY]: () => {
return new ApiRequest().surveyActivity((props.id ?? null) as string, teamId)
},
}

const pagingParameters = { page: page || 1, limit: ACTIVITY_PAGE_SIZE }
const request = requestForScope[activityLogProps.scope]?.(activityLogProps)
return request && request !== null
const request = requestForScope[scopes[0]]?.()
return request
? request.withQueryString(toParams(pagingParameters)).get()
: Promise.resolve({ results: [], count: 0 })
},
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/lib/components/ActivityLog/ActivityLog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export type ActivityLogProps = ActivityLogLogicProps & {
renderSideAction?: (logItem: HumanizedActivityLogItem) => JSX.Element
}

const Empty = ({ scope }: { scope: string }): JSX.Element => {
const noun = scope
const Empty = ({ scope }: { scope: string | string[] }): JSX.Element => {
const noun = (Array.isArray(scope) ? scope[0] : scope)
.replace(/([A-Z])/g, ' $1')
.trim()
.toLowerCase()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ describe('the activity log logic', () => {
})

describe('humanizing plugins', () => {
const pluginTestSetup = makeTestSetup(
ActivityScope.PLUGIN,
'/api/projects/:id/plugin_configs/:config_id/activity'
)
const pluginTestSetup = makeTestSetup(ActivityScope.PLUGIN, '/api/projects/:id/activity_log')
it('can handle installation of a plugin', async () => {
const logic = await pluginTestSetup('the installed plugin', 'installed', null)
const actual = logic.values.humanizedActivity
Expand Down
28 changes: 21 additions & 7 deletions frontend/src/lib/components/ActivityLog/activityLogLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ import { dataManagementActivityDescriber } from 'scenes/data-management/dataMana
import { flagActivityDescriber } from 'scenes/feature-flags/activityDescriptions'
import { notebookActivityDescriber } from 'scenes/notebooks/Notebook/notebookActivityDescriber'
import { personActivityDescriber } from 'scenes/persons/activityDescriptions'
import { hogFunctionActivityDescriber } from 'scenes/pipeline/hogfunctions/activityDescriptions'
import { pluginActivityDescriber } from 'scenes/pipeline/pipelinePluginActivityDescriptions'
import { insightActivityDescriber } from 'scenes/saved-insights/activityDescriptions'
import { surveyActivityDescriber } from 'scenes/surveys/surveyActivityDescriber'
import { teamActivityDescriber } from 'scenes/teamActivityDescriber'
import { urls } from 'scenes/urls'

import { ActivityScope } from '~/types'
import { ActivityScope, PipelineNodeTab, PipelineStage, PipelineTab } from '~/types'

import type { activityLogLogicType } from './activityLogLogicType'

Expand All @@ -38,6 +39,8 @@ export const describerFor = (logItem?: ActivityLogItem): Describer | undefined =
case ActivityScope.PLUGIN:
case ActivityScope.PLUGIN_CONFIG:
return pluginActivityDescriber
case ActivityScope.HOG_FUNCTION:
return hogFunctionActivityDescriber
case ActivityScope.COHORT:
return cohortActivityDescriber
case ActivityScope.INSIGHT:
Expand All @@ -59,23 +62,26 @@ export const describerFor = (logItem?: ActivityLogItem): Describer | undefined =
}

export type ActivityLogLogicProps = {
scope: ActivityScope
scope: ActivityScope | ActivityScope[]
// if no id is provided, the list is not scoped by id and shows all activity ordered by time
id?: number | string
}

export const activityLogLogic = kea<activityLogLogicType>([
props({} as ActivityLogLogicProps),
key(({ scope, id }) => `activity/${scope}/${id || 'all'}`),
key(({ scope, id }) => `activity/${Array.isArray(scope) ? scope.join(',') : scope}/${id || 'all'}`),
path((key) => ['lib', 'components', 'ActivityLog', 'activitylog', 'logic', key]),
actions({
setPage: (page: number) => ({ page }),
}),
loaders(({ values, props }) => ({
activity: [
{ results: [], total_count: 0 } as ActivityLogPaginatedResponse<ActivityLogItem>,
{ results: [], count: 0 } as ActivityLogPaginatedResponse<ActivityLogItem>,
{
fetchActivity: async () => await api.activity.listLegacy(props, values.page),
fetchActivity: async () => {
const response = await api.activity.listLegacy(props, values.page)
return { results: response.results, count: (response as any).total_count ?? response.count }
},
},
],
})),
Expand Down Expand Up @@ -110,7 +116,7 @@ export const activityLogLogic = kea<activityLogLogicType>([
totalCount: [
(s) => [s.activity],
(activity): number | null => {
return activity.total_count ?? null
return activity.count ?? null
},
],
})),
Expand All @@ -128,14 +134,15 @@ export const activityLogLogic = kea<activityLogLogicType>([
forceUsePageParam?: boolean
): void => {
const pageInURL = searchParams['page']
const firstScope = Array.isArray(props.scope) ? props.scope[0] : props.scope

const shouldPage =
forceUsePageParam ||
(pageScope === ActivityScope.PERSON && hashParams['activeTab'] === 'history') ||
([ActivityScope.FEATURE_FLAG, ActivityScope.INSIGHT, ActivityScope.PLUGIN].includes(pageScope) &&
searchParams['tab'] === 'history')

if (shouldPage && pageInURL && pageInURL !== values.page && pageScope === props.scope) {
if (shouldPage && pageInURL && pageInURL !== values.page && pageScope === firstScope) {
actions.setPage(pageInURL)
}

Expand All @@ -161,6 +168,13 @@ export const activityLogLogic = kea<activityLogLogicType>([
onPageChange(searchParams, hashParams, ActivityScope.INSIGHT),
[urls.featureFlag(':id')]: (_, searchParams, hashParams) =>
onPageChange(searchParams, hashParams, ActivityScope.FEATURE_FLAG, true),
[urls.pipelineNode(PipelineStage.Destination, ':id', PipelineNodeTab.History)]: (
_,
searchParams,
hashParams
) => onPageChange(searchParams, hashParams, ActivityScope.HOG_FUNCTION),
[urls.pipeline(PipelineTab.History)]: (_, searchParams, hashParams) =>
onPageChange(searchParams, hashParams, ActivityScope.PLUGIN),
}
}),
events(({ actions }) => ({
Expand Down
63 changes: 8 additions & 55 deletions frontend/src/lib/monaco/CodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ import { Spinner } from 'lib/lemon-ui/Spinner'
import { codeEditorLogic } from 'lib/monaco/codeEditorLogic'
import { codeEditorLogicType } from 'lib/monaco/codeEditorLogicType'
import { findNextFocusableElement, findPreviousFocusableElement } from 'lib/monaco/domUtils'
import { hogQLAutocompleteProvider } from 'lib/monaco/hogQLAutocompleteProvider'
import { hogQLMetadataProvider } from 'lib/monaco/hogQLMetadataProvider'
import * as hog from 'lib/monaco/languages/hog'
import * as hogJson from 'lib/monaco/languages/hogJson'
import * as hogQL from 'lib/monaco/languages/hogQL'
import * as hogTemplate from 'lib/monaco/languages/hogTemplate'
import { initHogLanguage } from 'lib/monaco/languages/hog'
import { initHogJsonLanguage } from 'lib/monaco/languages/hogJson'
import { initHogQLLanguage } from 'lib/monaco/languages/hogQL'
import { initHogTemplateLanguage } from 'lib/monaco/languages/hogTemplate'
import { inStorybookTestRunner } from 'lib/utils'
import { editor, editor as importedEditor, IDisposable } from 'monaco-editor'
import * as monaco from 'monaco-editor'
Expand Down Expand Up @@ -47,61 +45,16 @@ function initEditor(
;(model as any).codeEditorLogic = builtCodeEditorLogic

if (editorProps?.language === 'hog') {
if (!monaco.languages.getLanguages().some(({ id }) => id === 'hog')) {
monaco.languages.register({ id: 'hog', extensions: ['.hog'], mimetypes: ['application/hog'] })
monaco.languages.setLanguageConfiguration('hog', hog.conf())
monaco.languages.setMonarchTokensProvider('hog', hog.language())
monaco.languages.registerCompletionItemProvider('hog', hogQLAutocompleteProvider(HogLanguage.hog))
monaco.languages.registerCodeActionProvider('hog', hogQLMetadataProvider())
}
initHogLanguage(monaco)
}
if (editorProps?.language === 'hogQL' || editorProps?.language === 'hogQLExpr') {
const language: HogLanguage = editorProps.language as HogLanguage
if (!monaco.languages.getLanguages().some(({ id }) => id === language)) {
monaco.languages.register(
language === 'hogQL'
? {
id: language,
extensions: ['.sql', '.hogql'],
mimetypes: ['application/hogql'],
}
: {
id: language,
mimetypes: ['application/hogql+expr'],
}
)
monaco.languages.setLanguageConfiguration(language, hogQL.conf())
monaco.languages.setMonarchTokensProvider(language, hogQL.language())
monaco.languages.registerCompletionItemProvider(language, hogQLAutocompleteProvider(language))
monaco.languages.registerCodeActionProvider(language, hogQLMetadataProvider())
}
initHogQLLanguage(monaco, editorProps.language as HogLanguage)
}
if (editorProps?.language === 'hogTemplate') {
if (!monaco.languages.getLanguages().some(({ id }) => id === 'hogTemplate')) {
monaco.languages.register({
id: 'hogTemplate',
mimetypes: ['application/hog+template'],
})
monaco.languages.setLanguageConfiguration('hogTemplate', hogTemplate.conf())
monaco.languages.setMonarchTokensProvider('hogTemplate', hogTemplate.language())
monaco.languages.registerCompletionItemProvider(
'hogTemplate',
hogQLAutocompleteProvider(HogLanguage.hogTemplate)
)
monaco.languages.registerCodeActionProvider('hogTemplate', hogQLMetadataProvider())
}
initHogTemplateLanguage(monaco)
}
if (editorProps?.language === 'hogJson') {
if (!monaco.languages.getLanguages().some(({ id }) => id === 'hogJson')) {
monaco.languages.register({
id: 'hogJson',
mimetypes: ['application/hog+json'],
})
monaco.languages.setLanguageConfiguration('hogJson', hogJson.conf())
monaco.languages.setMonarchTokensProvider('hogJson', hogJson.language())
monaco.languages.registerCompletionItemProvider('hogJson', hogQLAutocompleteProvider(HogLanguage.hogJson))
monaco.languages.registerCodeActionProvider('hogJson', hogQLMetadataProvider())
}
initHogJsonLanguage(monaco)
}
if (options.tabFocusMode) {
editor.onKeyDown((evt) => {
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/lib/monaco/languages/hog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@

// Adapted from: https://raw.githubusercontent.com/microsoft/monaco-editor/main/src/basic-languages/typescript/typescript.ts

import { Monaco } from '@monaco-editor/react'
import { hogQLAutocompleteProvider } from 'lib/monaco/hogQLAutocompleteProvider'
import { hogQLMetadataProvider } from 'lib/monaco/hogQLMetadataProvider'
import { languages } from 'monaco-editor'

import { HogLanguage } from '~/queries/schema'

export const conf: () => languages.LanguageConfiguration = () => ({
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,

Expand Down Expand Up @@ -244,3 +249,13 @@ export const language: () => languages.IMonarchLanguage = () => ({
],
},
})

export function initHogLanguage(monaco: Monaco): void {
if (!monaco.languages.getLanguages().some(({ id }) => id === 'hog')) {
monaco.languages.register({ id: 'hog', extensions: ['.hog'], mimetypes: ['application/hog'] })
monaco.languages.setLanguageConfiguration('hog', conf())
monaco.languages.setMonarchTokensProvider('hog', language())
monaco.languages.registerCompletionItemProvider('hog', hogQLAutocompleteProvider(HogLanguage.hog))
monaco.languages.registerCodeActionProvider('hog', hogQLMetadataProvider())
}
}
18 changes: 18 additions & 0 deletions frontend/src/lib/monaco/languages/hogJson.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// eslint-disable-next-line eslint-comments/disable-enable-pair
/* eslint-disable no-useless-escape */
import { Monaco } from '@monaco-editor/react'
import { hogQLAutocompleteProvider } from 'lib/monaco/hogQLAutocompleteProvider'
import { hogQLMetadataProvider } from 'lib/monaco/hogQLMetadataProvider'
import { languages } from 'monaco-editor'

import { HogLanguage } from '~/queries/schema'

import { conf as _conf, language as _language } from './hog'

export const conf: () => languages.LanguageConfiguration = () => ({
Expand Down Expand Up @@ -151,3 +156,16 @@ export const language: () => languages.IMonarchLanguage = () => ({
],
},
})

export function initHogJsonLanguage(monaco: Monaco): void {
if (!monaco.languages.getLanguages().some(({ id }) => id === 'hogJson')) {
monaco.languages.register({
id: 'hogJson',
mimetypes: ['application/hog+json'],
})
monaco.languages.setLanguageConfiguration('hogJson', conf())
monaco.languages.setMonarchTokensProvider('hogJson', language())
monaco.languages.registerCompletionItemProvider('hogJson', hogQLAutocompleteProvider(HogLanguage.hogJson))
monaco.languages.registerCodeActionProvider('hogJson', hogQLMetadataProvider())
}
}
Loading
Loading