-
Notifications
You must be signed in to change notification settings - Fork 41
feat(scouts): link scout findings to their inbox report #2686
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import { seedInboxReportDetailCache } from "@posthog/core/inbox/inboxQuery"; | ||
| import { useOptionalAuthenticatedClient } from "@posthog/ui/features/auth/authClient"; | ||
| import { AUTH_SCOPED_QUERY_META } from "@posthog/ui/features/auth/useCurrentUser"; | ||
| import { reportKeys } from "@posthog/ui/features/inbox/hooks/useInboxReports"; | ||
| import { useInboxSignalsFilterStore } from "@posthog/ui/features/inbox/stores/inboxSignalsFilterStore"; | ||
| import { | ||
| navigateToInboxPullRequestDetail, | ||
| navigateToInboxReportDetail, | ||
| } from "@posthog/ui/router/navigationBridge"; | ||
| import { logger } from "@posthog/ui/shell/logger"; | ||
| import { useQueryClient } from "@tanstack/react-query"; | ||
| import { useCallback } from "react"; | ||
| import { toast } from "sonner"; | ||
|
|
||
| const log = logger.scope("open-inbox-report"); | ||
|
|
||
| /** | ||
| * Returns a callback that opens an inbox report by id: fetch it directly | ||
| * (bypassing the paginated list), seed the detail cache, reset inbox-local | ||
| * filters so it isn't hidden, then navigate to the right tab – Pulls when it | ||
| * has an implementation PR, otherwise Reports. | ||
| * | ||
| * Shared by the deep-link handler ({@link useInboxDeepLink}) and any in-app | ||
| * surface that links to a report it only knows by id (e.g. the scout finding | ||
| * → linked report chip). On 404/403 (wrong team / deleted / suppressed) it | ||
| * toasts and leaves the current view untouched. | ||
| */ | ||
| export function useOpenInboxReport() { | ||
| const queryClient = useQueryClient(); | ||
| const client = useOptionalAuthenticatedClient(); | ||
| const resetFilters = useInboxSignalsFilterStore((s) => s.resetFilters); | ||
|
|
||
| return useCallback( | ||
| async (reportId: string) => { | ||
| if (!client) { | ||
| log.warn("Ignoring open-report request – not authenticated"); | ||
| return; | ||
| } | ||
|
|
||
| log.info(`Opening report: ${reportId}`); | ||
|
|
||
| try { | ||
| const report = await queryClient.fetchQuery({ | ||
| queryKey: reportKeys.detail(reportId), | ||
| queryFn: () => client.getSignalReport(reportId), | ||
| meta: AUTH_SCOPED_QUERY_META, | ||
| }); | ||
|
|
||
| if (!report) { | ||
| log.warn(`Report not found or not accessible: ${reportId}`); | ||
| toast.error("Report not found in the current team"); | ||
| return; | ||
| } | ||
|
|
||
| resetFilters(); | ||
| seedInboxReportDetailCache(queryClient, report); | ||
| if (report.implementation_pr_url) { | ||
| navigateToInboxPullRequestDetail(report.id); | ||
| } else { | ||
| navigateToInboxReportDetail(report.id); | ||
|
Comment on lines
+57
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the fetched report has a run-only status ( Useful? React with 👍 / 👎. |
||
| } | ||
| log.info(`Successfully opened report: ${report.id}`); | ||
| } catch (error) { | ||
| log.error("Unexpected error opening report:", error); | ||
| toast.error("Failed to open report"); | ||
| } | ||
| }, | ||
| [client, queryClient, resetFilters], | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import { TrayIcon } from "@phosphor-icons/react"; | ||
| import type { LinkedSignalReport } from "@posthog/api-client/posthog-client"; | ||
| import { ANALYTICS_EVENTS } from "@posthog/shared"; | ||
| import { useOpenInboxReport } from "@posthog/ui/features/inbox/hooks/useOpenInboxReport"; | ||
| import { track } from "@posthog/ui/shell/analytics"; | ||
| import { Text } from "@radix-ui/themes"; | ||
| import { useState } from "react"; | ||
|
|
||
| /** | ||
| * Footer chip on a scout emission card linking to the inbox report this finding | ||
| * grouped into. Best effort: only rendered when the reverse lookup resolved a | ||
| * report. Clicking opens the report in the inbox via the shared open-report | ||
| * flow (fetch by id, seed cache, navigate to the right tab). | ||
| */ | ||
| export function ScoutLinkedReportChip({ | ||
| report, | ||
| skillName, | ||
| }: { | ||
| report: LinkedSignalReport; | ||
| /** The emitting scout, attached to analytics when known. */ | ||
| skillName?: string; | ||
| }) { | ||
| const openReport = useOpenInboxReport(); | ||
| const [opening, setOpening] = useState(false); | ||
|
|
||
| return ( | ||
| <button | ||
| type="button" | ||
| disabled={opening} | ||
| onClick={async () => { | ||
| track(ANALYTICS_EVENTS.SCOUT_ACTION, { | ||
| action_type: "open_linked_report", | ||
| surface: "scout_detail", | ||
| skill_name: skillName, | ||
| report_status: report.status, | ||
| }); | ||
| setOpening(true); | ||
| try { | ||
| await openReport(report.id); | ||
| } finally { | ||
| setOpening(false); | ||
| } | ||
| }} | ||
| className="flex max-w-[16rem] items-center gap-1 rounded-full bg-(--iris-3) px-2 py-0.5 text-(--iris-11) transition-colors hover:bg-(--iris-4) disabled:opacity-60" | ||
| title={report.title ?? "View linked report"} | ||
| > | ||
| <TrayIcon size={12} className="shrink-0" /> | ||
| <Text className="truncate text-[11px]"> | ||
| {report.title ? `In report: ${report.title}` : "View linked report"} | ||
| </Text> | ||
| </button> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When this client is deployed against a backend before the new
emissions/reports/action exists,scoutGetuses the shared fetcher, which throws on any non-2xx inpackages/api-client/src/fetcher.ts, so this call rejects with a 404 instead of producing an empty link list. SinceuseScoutEmissionReportsmounts one query per visible emitted run, opening a scout page on that backend will generate failing API requests rather than the intended quiet fallback; catch 404 here and return[]if the endpoint is optional.Useful? React with 👍 / 👎.