Skip to content

Commit

Permalink
Merge branch 'master' into feature-flag-payload-official-property
Browse files Browse the repository at this point in the history
  • Loading branch information
havenbarnes authored Dec 2, 2024
2 parents 5e6988a + 7ec7356 commit 83a0ce2
Show file tree
Hide file tree
Showing 260 changed files with 4,333 additions and 2,461 deletions.
2 changes: 2 additions & 0 deletions bin/copy-posthog-js
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ cp node_modules/posthog-js/dist/surveys.js* frontend/dist/
cp node_modules/posthog-js/dist/exception-autocapture.js* frontend/dist/
cp node_modules/posthog-js/dist/web-vitals.js* frontend/dist/
cp node_modules/posthog-js/dist/dead-clicks-autocapture.js* frontend/dist/
cp node_modules/posthog-js/dist/customizations.full.js* frontend/dist/

10 changes: 10 additions & 0 deletions ee/api/test/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ def test_user_that_does_not_belong_to_an_org_cannot_create_a_projec(self):
},
)

def test_rename_project_as_org_member_allowed(self):
self.organization_membership.level = OrganizationMembership.Level.MEMBER
self.organization_membership.save()

response = self.client.patch(f"/api/projects/@current/", {"name": "Erinaceus europaeus"})
self.project.refresh_from_db()

self.assertEqual(response.status_code, 200)
self.assertEqual(self.project.name, "Erinaceus europaeus")

def test_list_projects_restricted_ones_hidden(self):
self.organization_membership.level = OrganizationMembership.Level.MEMBER
self.organization_membership.save()
Expand Down
110 changes: 55 additions & 55 deletions ee/api/test/test_team.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,61 +184,6 @@ def test_no_delete_team_not_belonging_to_organization(self):

# Updating projects

def test_rename_team_as_org_member_allowed(self):
self.organization_membership.level = OrganizationMembership.Level.MEMBER
self.organization_membership.save()

response = self.client.patch(f"/api/environments/@current/", {"name": "Erinaceus europaeus"})
self.team.refresh_from_db()

self.assertEqual(response.status_code, HTTP_200_OK)
self.assertEqual(self.team.name, "Erinaceus europaeus")

def test_rename_private_team_as_org_member_forbidden(self):
self.organization_membership.level = OrganizationMembership.Level.MEMBER
self.organization_membership.save()
self.team.access_control = True
self.team.save()

response = self.client.patch(f"/api/environments/@current/", {"name": "Acherontia atropos"})
self.team.refresh_from_db()

self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)
self.assertEqual(self.team.name, "Default project")

def test_rename_private_team_current_as_org_outsider_forbidden(self):
self.organization_membership.delete()

response = self.client.patch(f"/api/environments/@current/", {"name": "Acherontia atropos"})
self.team.refresh_from_db()

self.assertEqual(response.status_code, HTTP_404_NOT_FOUND)

def test_rename_private_team_id_as_org_outsider_forbidden(self):
self.organization_membership.delete()

response = self.client.patch(f"/api/environments/{self.team.id}/", {"name": "Acherontia atropos"})
self.team.refresh_from_db()

self.assertEqual(response.status_code, HTTP_404_NOT_FOUND)

def test_rename_private_team_as_org_member_and_team_member_allowed(self):
self.organization_membership.level = OrganizationMembership.Level.MEMBER
self.organization_membership.save()
self.team.access_control = True
self.team.save()
ExplicitTeamMembership.objects.create(
team=self.team,
parent_membership=self.organization_membership,
level=ExplicitTeamMembership.Level.MEMBER,
)

response = self.client.patch(f"/api/environments/@current/", {"name": "Acherontia atropos"})
self.team.refresh_from_db()

self.assertEqual(response.status_code, HTTP_200_OK)
self.assertEqual(self.team.name, "Acherontia atropos")

def test_enable_access_control_as_org_member_forbidden(self):
self.organization_membership.level = OrganizationMembership.Level.MEMBER
self.organization_membership.save()
Expand Down Expand Up @@ -608,6 +553,61 @@ def test_cannot_create_team_in_project_without_org_access(self):
self.not_found_response("Organization not found."),
)

def test_rename_team_as_org_member_allowed(self):
self.organization_membership.level = OrganizationMembership.Level.MEMBER
self.organization_membership.save()

response = self.client.patch(f"/api/environments/@current/", {"name": "Erinaceus europaeus"})
self.team.refresh_from_db()

self.assertEqual(response.status_code, HTTP_200_OK)
self.assertEqual(self.team.name, "Erinaceus europaeus")

def test_rename_private_team_as_org_member_forbidden(self):
self.organization_membership.level = OrganizationMembership.Level.MEMBER
self.organization_membership.save()
self.team.access_control = True
self.team.save()

response = self.client.patch(f"/api/environments/@current/", {"name": "Acherontia atropos"})
self.team.refresh_from_db()

self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)
self.assertEqual(self.team.name, "Default project")

def test_rename_private_team_current_as_org_outsider_forbidden(self):
self.organization_membership.delete()

response = self.client.patch(f"/api/environments/@current/", {"name": "Acherontia atropos"})
self.team.refresh_from_db()

self.assertEqual(response.status_code, HTTP_404_NOT_FOUND)

def test_rename_private_team_id_as_org_outsider_forbidden(self):
self.organization_membership.delete()

response = self.client.patch(f"/api/environments/{self.team.id}/", {"name": "Acherontia atropos"})
self.team.refresh_from_db()

self.assertEqual(response.status_code, HTTP_404_NOT_FOUND)

def test_rename_private_team_as_org_member_and_team_member_allowed(self):
self.organization_membership.level = OrganizationMembership.Level.MEMBER
self.organization_membership.save()
self.team.access_control = True
self.team.save()
ExplicitTeamMembership.objects.create(
team=self.team,
parent_membership=self.organization_membership,
level=ExplicitTeamMembership.Level.MEMBER,
)

response = self.client.patch(f"/api/environments/@current/", {"name": "Acherontia atropos"})
self.team.refresh_from_db()

self.assertEqual(response.status_code, HTTP_200_OK)
self.assertEqual(self.team.name, "Acherontia atropos")

def test_list_teams_restricted_ones_hidden(self):
self.organization_membership.level = OrganizationMembership.Level.MEMBER
self.organization_membership.save()
Expand Down
5 changes: 2 additions & 3 deletions ee/hogai/eval/tests/test_eval_funnel_generator.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
from langgraph.graph.state import CompiledStateGraph
from pydantic import BaseModel

from ee.hogai.assistant import AssistantGraph
from ee.hogai.eval.utils import EvalBaseTest
from ee.hogai.utils import AssistantNodeName
from posthog.schema import HumanMessage
from posthog.schema import AssistantFunnelsQuery, HumanMessage


class TestEvalFunnelGenerator(EvalBaseTest):
def _call_node(self, query: str, plan: str) -> BaseModel:
def _call_node(self, query: str, plan: str) -> AssistantFunnelsQuery:
graph: CompiledStateGraph = (
AssistantGraph(self.team)
.add_edge(AssistantNodeName.START, AssistantNodeName.FUNNEL_GENERATOR)
Expand Down
23 changes: 20 additions & 3 deletions ee/hogai/eval/tests/test_eval_trends_generator.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
from langgraph.graph.state import CompiledStateGraph
from pydantic import BaseModel

from ee.hogai.assistant import AssistantGraph
from ee.hogai.eval.utils import EvalBaseTest
from ee.hogai.utils import AssistantNodeName
from posthog.schema import HumanMessage
from posthog.schema import AssistantTrendsQuery, HumanMessage


class TestEvalTrendsGenerator(EvalBaseTest):
def _call_node(self, query: str, plan: str) -> BaseModel:
def _call_node(self, query: str, plan: str) -> AssistantTrendsQuery:
graph: CompiledStateGraph = (
AssistantGraph(self.team)
.add_edge(AssistantNodeName.START, AssistantNodeName.TRENDS_GENERATOR)
Expand All @@ -34,3 +33,21 @@ def test_node_replaces_equals_with_contains(self):
assert "icontains" in actual_output
assert "John" not in actual_output
assert "john" in actual_output

def test_node_leans_towards_line_graph(self):
query = "How often do users download files?"
# We ideally want to consider both total count of downloads per period, as well as how often a median user downloads
plan = """Events:
- downloaded_file
- math operation: total count
- downloaded_file
- math operation: median count per user
"""
actual_output = self._call_node(query, plan)
assert actual_output.trendsFilter.display == "ActionsLineGraph"
assert actual_output.series[0].kind == "EventsNode"
assert actual_output.series[0].event == "downloaded_file"
assert actual_output.series[0].math == "total"
assert actual_output.series[1].kind == "EventsNode"
assert actual_output.series[1].event == "downloaded_file"
assert actual_output.series[1].math == "median_count_per_actor"
2 changes: 1 addition & 1 deletion ee/hogai/trends/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
- 95th percentile
- 99th percentile
Available math aggregation types counting by users who completed an event are:
Available math aggregation types counting number of events completed per user (intensity of usage) are:
- average
- minimum
- maximum
Expand Down
2 changes: 1 addition & 1 deletion ee/hogai/trends/toolkit.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def generate_trends_schema() -> dict:
schema = AssistantTrendsQuery.model_json_schema()
return {
"name": "output_insight_schema",
"description": "Outputs the JSON schema of a funnel insight",
"description": "Outputs the JSON schema of a trends insight",
"parameters": {
"type": "object",
"properties": {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-other-toolbar--actions--dark.png
Binary file modified frontend/__snapshots__/scenes-other-toolbar--actions--light.png
Binary file modified frontend/__snapshots__/scenes-other-toolbar--experiments--dark.png
5 changes: 4 additions & 1 deletion frontend/src/layout/ErrorProjectUnavailable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Link } from '@posthog/lemon-ui'
import { useValues } from 'kea'
import { PageHeader } from 'lib/components/PageHeader'
import { useEffect, useState } from 'react'
import { CreateOrganizationModal } from 'scenes/organization/CreateOrganizationModal'
import { urls } from 'scenes/urls'
import { userLogic } from 'scenes/userLogic'

Expand Down Expand Up @@ -42,7 +43,9 @@ export function ErrorProjectUnavailable(): JSX.Element {
return (
<div>
<PageHeader />
{user?.team && !user.organization?.teams.some((team) => team.id === user?.team?.id) ? (
{!user?.organization ? (
<CreateOrganizationModal isVisible inline />
) : user?.team && !user.organization?.teams.some((team) => team.id === user?.team?.id) ? (
<>
<h1>Project access has been removed</h1>
<p>
Expand Down
41 changes: 22 additions & 19 deletions frontend/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { ActivityLogItem } from 'lib/components/ActivityLog/humanizeActivity'
import { apiStatusLogic } from 'lib/logic/apiStatusLogic'
import { objectClean, toParams } from 'lib/utils'
import posthog from 'posthog-js'
import { stringifiedFingerprint } from 'scenes/error-tracking/utils'
import { RecordingComment } from 'scenes/session-recordings/player/inspector/playerInspectorLogic'
import { SavedSessionRecordingPlaylistsResult } from 'scenes/session-recordings/saved-playlists/savedSessionRecordingPlaylistsLogic'

Expand All @@ -14,7 +13,7 @@ import { Variable } from '~/queries/nodes/DataVisualization/types'
import {
DashboardFilter,
DatabaseSerializedFieldType,
ErrorTrackingGroup,
ErrorTrackingIssue,
HogCompileResponse,
HogQLVariable,
QuerySchema,
Expand Down Expand Up @@ -712,14 +711,12 @@ class ApiRequest {
return this.projectsDetail(teamId).addPathComponent('error_tracking')
}

public errorTrackingGroup(fingerprint: ErrorTrackingGroup['fingerprint'], teamId?: TeamType['id']): ApiRequest {
return this.errorTracking(teamId)
.addPathComponent('group')
.addPathComponent(stringifiedFingerprint(fingerprint))
public errorTrackingIssue(id: ErrorTrackingIssue['id'], teamId?: TeamType['id']): ApiRequest {
return this.errorTracking(teamId).addPathComponent('issue').addPathComponent(id)
}

public errorTrackingGroupMerge(fingerprint: ErrorTrackingGroup['fingerprint']): ApiRequest {
return this.errorTrackingGroup(fingerprint).addPathComponent('merge')
public errorTrackingIssueMerge(into: ErrorTrackingIssue['id']): ApiRequest {
return this.errorTrackingIssue(into).addPathComponent('merge')
}

public errorTrackingSymbolSets(teamId?: TeamType['id']): ApiRequest {
Expand Down Expand Up @@ -1862,21 +1859,22 @@ const api = {

errorTracking: {
async updateIssue(
fingerprint: ErrorTrackingGroup['fingerprint'],
data: Partial<Pick<ErrorTrackingGroup, 'assignee' | 'status'>>
): Promise<ErrorTrackingGroup> {
return await new ApiRequest().errorTrackingGroup(fingerprint).update({ data })
id: ErrorTrackingIssue['id'],
data: Partial<Pick<ErrorTrackingIssue, 'assignee' | 'status'>>
): Promise<ErrorTrackingIssue> {
return await new ApiRequest().errorTrackingIssue(id).update({ data })
},

async merge(
primaryFingerprint: ErrorTrackingGroup['fingerprint'],
mergingFingerprints: ErrorTrackingGroup['fingerprint'][]
async mergeInto(
primaryIssueId: ErrorTrackingIssue['id'],
mergingIssueIds: ErrorTrackingIssue['id'][]
): Promise<{ content: string }> {
return await new ApiRequest()
.errorTrackingGroup(primaryFingerprint)
.create({ data: { merging_fingerprints: mergingFingerprints } })
.errorTrackingIssueMerge(primaryIssueId)
.create({ data: { ids: mergingIssueIds } })
},
async updateSymbolSet(id: ErrorTrackingSymbolSet['id'], data: FormData): Promise<ErrorTrackingGroup> {

async updateSymbolSet(id: ErrorTrackingSymbolSet['id'], data: FormData): Promise<void> {
return await new ApiRequest().errorTrackingSymbolSet(id).update({ data })
},

Expand Down Expand Up @@ -2358,7 +2356,12 @@ const api = {
viewId: DataWarehouseViewLink['id'],
data: Pick<
DataWarehouseViewLink,
'source_table_name' | 'source_table_key' | 'joining_table_name' | 'joining_table_key' | 'field_name'
| 'source_table_name'
| 'source_table_key'
| 'joining_table_name'
| 'joining_table_key'
| 'field_name'
| 'configuration'
>
): Promise<DataWarehouseViewLink> {
return await new ApiRequest().dataWarehouseViewLink(viewId).update({ data })
Expand Down
36 changes: 23 additions & 13 deletions frontend/src/lib/components/Errors/ErrorDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,8 @@ function StackTrace({
showAllFrames: boolean
}): JSX.Element | null {
const { stackFrameRecords } = useValues(stackFrameLogic)
const { loadFromRawIds } = useActions(stackFrameLogic)
const displayFrames = showAllFrames ? frames : frames.filter((f) => f.in_app)

useEffect(() => {
loadFromRawIds(frames.map(({ raw_id }) => raw_id))
}, [frames, loadFromRawIds])

const panels = displayFrames.map(
({ raw_id, source, line, column, resolved_name, lang, resolved, resolve_failure }, index) => {
const record = stackFrameRecords[raw_id]
Expand Down Expand Up @@ -121,19 +116,34 @@ function FrameContextLine({
)
}
function ChainedStackTraces({ exceptionList }: { exceptionList: ErrorTrackingException[] }): JSX.Element {
const [showAllFrames, setShowAllFrames] = useState(false)
const hasAnyInApp = exceptionList.some(({ stacktrace }) => stacktrace?.frames?.some(({ in_app }) => in_app))
const [showAllFrames, setShowAllFrames] = useState(!hasAnyInApp)
const { loadFromRawIds } = useActions(stackFrameLogic)

useEffect(() => {
const frames: ErrorTrackingStackFrame[] = exceptionList.flatMap((e) => {
const trace = e.stacktrace
if (trace?.type === 'resolved') {
return trace.frames
}
return []
})
loadFromRawIds(frames.map(({ raw_id }) => raw_id))
}, [exceptionList, loadFromRawIds])

return (
<>
<div className="flex gap-1 mt-6 justify-between items-center">
<h3 className="mb-0">Stack Trace</h3>
<LemonSwitch
checked={showAllFrames}
label="Show entire stack trace"
onChange={() => {
setShowAllFrames(!showAllFrames)
}}
/>
{hasAnyInApp ? (
<LemonSwitch
checked={showAllFrames}
label="Show entire stack trace"
onChange={() => {
setShowAllFrames(!showAllFrames)
}}
/>
) : null}
</div>
{exceptionList.map(({ stacktrace, value }, index) => {
if (stacktrace && stacktrace.type === 'resolved') {
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lib/components/Errors/stackFrameLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ export const stackFrameLogic = kea<stackFrameLogicType>([
loadFromRawIds: async ({ rawIds }) => {
const loadedRawIds = Object.keys(values.stackFrameRecords)
rawIds = rawIds.filter((rawId) => !loadedRawIds.includes(rawId))
if (rawIds.length === 0) {
return values.stackFrameRecords
}
const { results } = await api.errorTracking.stackFrames(rawIds)
return mapStackFrameRecords(results, values.stackFrameRecords)
},
Expand Down
Loading

0 comments on commit 83a0ce2

Please sign in to comment.