mirror of
https://github.com/hanzoai/insights.git
synced 2026-11-03 00:52:32 +00:00
Every first-party Python caller and test now names the mount the API actually answers on. Mechanical: URL literals only, and only where the /api/ was ours -- a preceding word character means it was a module path like insights/api/ or a third-party host like slack.com/api/, and those are untouched. Left saying /api/, each for a reason rather than for safety: - products/warehouse_sources/.../sources/** is a catalogue of vendor endpoints - services/llm-gateway deploys separately and is under a code freeze, so moving its two calls would break it during the skew - the mcp_store and Slack/Teams redirect_uris are registered with third parties and must byte-match what was registered - the Wizard and Raycast client-metadata paths are OAuth client_ids - /api/surveys and /api/feature_flag/local_evaluation are SDK aliases the ingress routes to the Rust fleets, held by released SDK versions Test URLconfs moved with the requests that call them, and the Datastore user-selection fixtures now carry the leading slash that request.path has.
271 lines
11 KiB
TypeScript
271 lines
11 KiB
TypeScript
import { MakeLogicType, actions, connect, kea, listeners, path, reducers } from 'kea'
|
|
import { loaders } from 'kea-loaders'
|
|
import { router } from 'kea-router'
|
|
|
|
import api from 'lib/api'
|
|
import { toast } from 'lib/elements/Toast/Toast'
|
|
import { insightsApi } from 'scenes/insights/utils/api'
|
|
import { teamLogic } from 'scenes/teamLogic'
|
|
import { urls } from 'scenes/urls'
|
|
|
|
import { NodeKind } from '~/queries/schema/schema-general'
|
|
import type { MetricsQuery, MetricsQueryClause } from '~/queries/schema/schema-general'
|
|
import type { DashboardType } from '~/types'
|
|
|
|
import { metricsAttributeValuesRetrieve, metricsValuesRetrieve } from 'products/metrics/frontend/generated/api'
|
|
import type { _MetricNameApi } from 'products/metrics/frontend/generated/api.schemas'
|
|
|
|
import type { InsightModel } from '../../../../frontend/src/types'
|
|
import { RECOMMENDED_AGGREGATION_BY_TYPE, toKnownMetricType } from './metricsViewerLogic'
|
|
|
|
// A metric name can appear under more than one OTel type, so option identity
|
|
// must include both. \0 can't occur in ingest strings, so the key is collision-free.
|
|
export const metricOptionKey = (name: string, metricType: string): string => `${name}\0${metricType}`
|
|
|
|
// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
|
|
export interface metricsStarterDashboardLogicValues {
|
|
currentTeamId: number | null // teamLogic
|
|
creating: boolean
|
|
dashboardName: string
|
|
isModalOpen: boolean
|
|
metricOptions: _MetricNameApi[]
|
|
metricOptionsLoading: boolean
|
|
selectedMetrics: string[]
|
|
serviceName: string
|
|
services: string[]
|
|
servicesLoading: boolean
|
|
}
|
|
|
|
// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
|
|
export interface metricsStarterDashboardLogicActions {
|
|
closeModal: () => {
|
|
value: true
|
|
}
|
|
createDashboard: () => {
|
|
value: true
|
|
}
|
|
createDashboardFailure: (message: string) => {
|
|
message: string
|
|
}
|
|
createDashboardSuccess: (
|
|
createdDashboard: DashboardType,
|
|
warning?: string
|
|
) => {
|
|
createdDashboard: DashboardType<InsightModel>
|
|
warning: string | undefined
|
|
}
|
|
loadMetricOptions: (_: any) => any
|
|
loadMetricOptionsFailure: (
|
|
error: string,
|
|
errorObject?: any
|
|
) => {
|
|
error: string
|
|
errorObject?: any
|
|
}
|
|
loadMetricOptionsSuccess: (
|
|
metricOptions: _MetricNameApi[],
|
|
payload?: any
|
|
) => {
|
|
metricOptions: _MetricNameApi[]
|
|
payload?: any
|
|
}
|
|
loadServices: (_: any) => any
|
|
loadServicesFailure: (
|
|
error: string,
|
|
errorObject?: any
|
|
) => {
|
|
error: string
|
|
errorObject?: any
|
|
}
|
|
loadServicesSuccess: (
|
|
services: string[],
|
|
payload?: any
|
|
) => {
|
|
services: string[]
|
|
payload?: any
|
|
}
|
|
openModal: () => {
|
|
value: true
|
|
}
|
|
setDashboardName: (dashboardName: string) => {
|
|
dashboardName: string
|
|
}
|
|
setSelectedMetrics: (selectedMetrics: string[]) => {
|
|
selectedMetrics: string[]
|
|
}
|
|
setServiceName: (serviceName: string) => {
|
|
serviceName: string
|
|
}
|
|
}
|
|
|
|
export type metricsStarterDashboardLogicType = MakeLogicType<
|
|
metricsStarterDashboardLogicValues,
|
|
metricsStarterDashboardLogicActions
|
|
>
|
|
|
|
// One dashboard, one insight per picked metric, each charted with the
|
|
// aggregation its type recommends and scoped to the picked service — the
|
|
// onboarding doc's starter set, productized.
|
|
export const metricsStarterDashboardLogic = kea<metricsStarterDashboardLogicType>([
|
|
path(['products', 'metrics', 'frontend', 'components', 'metricsStarterDashboardLogic']),
|
|
connect(() => ({
|
|
values: [teamLogic, ['currentTeamId']],
|
|
})),
|
|
actions({
|
|
openModal: true,
|
|
closeModal: true,
|
|
setDashboardName: (dashboardName: string) => ({ dashboardName }),
|
|
setServiceName: (serviceName: string) => ({ serviceName }),
|
|
// selectedMetrics holds metricOptionKey() composites, not bare names.
|
|
setSelectedMetrics: (selectedMetrics: string[]) => ({ selectedMetrics }),
|
|
createDashboard: true,
|
|
createDashboardSuccess: (createdDashboard: DashboardType, warning?: string) => ({ createdDashboard, warning }),
|
|
createDashboardFailure: (message: string) => ({ message }),
|
|
}),
|
|
reducers({
|
|
isModalOpen: [false, { openModal: () => true, closeModal: () => false }],
|
|
// The whole form resets on close so a reopen can't silently mint a near-duplicate.
|
|
dashboardName: [
|
|
'' as string,
|
|
{ setDashboardName: (_, { dashboardName }) => dashboardName, closeModal: () => '' },
|
|
],
|
|
// '' = all services: the insights get no service filter.
|
|
serviceName: ['' as string, { setServiceName: (_, { serviceName }) => serviceName, closeModal: () => '' }],
|
|
selectedMetrics: [
|
|
[] as string[],
|
|
{ setSelectedMetrics: (_, { selectedMetrics }) => selectedMetrics, closeModal: () => [] },
|
|
],
|
|
// Drives the create button's loading/disabled state so a double click can't
|
|
// fire twice; the listener's cache flag is the authoritative in-flight guard.
|
|
creating: [
|
|
false,
|
|
{
|
|
createDashboard: () => true,
|
|
createDashboardSuccess: () => false,
|
|
createDashboardFailure: () => false,
|
|
},
|
|
],
|
|
}),
|
|
loaders(({ values }) => ({
|
|
services: [
|
|
[] as string[],
|
|
{
|
|
loadServices: async (_, breakpoint) => {
|
|
await breakpoint(100)
|
|
const response = await metricsAttributeValuesRetrieve(String(values.currentTeamId), {
|
|
key: 'service.name',
|
|
})
|
|
breakpoint()
|
|
return response.results.map((value) => value.name)
|
|
},
|
|
},
|
|
],
|
|
metricOptions: [
|
|
[] as _MetricNameApi[],
|
|
{
|
|
loadMetricOptions: async (_, breakpoint) => {
|
|
await breakpoint(100)
|
|
const response = await metricsValuesRetrieve(String(values.currentTeamId), {})
|
|
breakpoint()
|
|
return response.results
|
|
},
|
|
},
|
|
],
|
|
})),
|
|
listeners(({ actions, values, cache }) => ({
|
|
openModal: () => {
|
|
actions.loadServices({})
|
|
actions.loadMetricOptions({})
|
|
},
|
|
createDashboard: async () => {
|
|
// kea-loaders doesn't dedupe concurrent runs, so the create is a plain
|
|
// listener with an explicit in-flight guard.
|
|
if (cache.creatingDashboard) {
|
|
return
|
|
}
|
|
cache.creatingDashboard = true
|
|
// Snapshot the form before the first await: a modal dismissed or edited
|
|
// mid-create must not change (or empty) what gets created.
|
|
const name = values.dashboardName.trim()
|
|
const selectedMetrics = [...values.selectedMetrics]
|
|
const serviceName = values.serviceName
|
|
const optionByKey: Record<string, _MetricNameApi> = Object.fromEntries(
|
|
values.metricOptions.map((option) => [metricOptionKey(option.name, option.metric_type), option])
|
|
)
|
|
// Resolve keys to options up front so a name shared by two OTel types
|
|
// keeps the type the user actually picked.
|
|
const pickedMetrics: _MetricNameApi[] = selectedMetrics.map(
|
|
(key) => optionByKey[key] ?? { name: key, metric_type: '' }
|
|
)
|
|
let dashboard: DashboardType | null = null
|
|
let created = 0
|
|
try {
|
|
if (!name || !selectedMetrics.length) {
|
|
actions.createDashboardFailure('Pick a dashboard name and at least one metric')
|
|
return
|
|
}
|
|
// The core dashboards create endpoint has no generated client yet
|
|
// (only its sharing sub-resources are in the codegen surface), so this
|
|
// stays a manual call until the Dashboard viewset is tagged.
|
|
// nosemgrep: prefer-codegen-api
|
|
dashboard = await api.create<DashboardType>(`v1/projects/${values.currentTeamId}/dashboards/`, {
|
|
name,
|
|
})
|
|
for (const { name: metricName, metric_type: rawType } of pickedMetrics) {
|
|
// The names endpoint reports raw ingest strings; only enum members
|
|
// may reach the API — mirrors the viewer's metricsQueryNode.
|
|
const metricType = toKnownMetricType(rawType)
|
|
const recommended = (rawType && RECOMMENDED_AGGREGATION_BY_TYPE[rawType]) || 'avg'
|
|
const clause: MetricsQueryClause = {
|
|
name: 'a',
|
|
metricName,
|
|
// The REST viewer's 'p95' shorthand maps to the node's quantile aggregation.
|
|
aggregation: recommended === 'p95' ? 'quantile' : recommended,
|
|
...(recommended === 'p95' ? { quantile: 0.95 } : {}),
|
|
...(metricType ? { metricType } : {}),
|
|
...(serviceName
|
|
? { filters: [{ key: 'service.name', op: 'eq' as const, value: serviceName }] }
|
|
: {}),
|
|
}
|
|
const query: MetricsQuery = { kind: NodeKind.MetricsQuery, clauses: [clause] }
|
|
await insightsApi.create({
|
|
name: `${metricName} (${recommended})`,
|
|
saved: true,
|
|
dashboards: [dashboard.id],
|
|
query,
|
|
})
|
|
created++
|
|
}
|
|
actions.createDashboardSuccess(dashboard)
|
|
} catch (error: any) {
|
|
if (dashboard) {
|
|
// The dashboard exists with a partial insight set — say so and take
|
|
// the user there rather than implying nothing happened (a retry
|
|
// from the modal would mint a duplicate dashboard). The warning
|
|
// replaces the success toast so only one shows.
|
|
actions.createDashboardSuccess(
|
|
dashboard,
|
|
`Dashboard "${dashboard.name}" created, but only ${created} of ${selectedMetrics.length} insights could be added`
|
|
)
|
|
} else {
|
|
actions.createDashboardFailure(error?.detail || error?.message || 'Failed to create the dashboard')
|
|
}
|
|
} finally {
|
|
cache.creatingDashboard = false
|
|
}
|
|
},
|
|
createDashboardSuccess: ({ createdDashboard, warning }) => {
|
|
actions.closeModal()
|
|
if (warning) {
|
|
toast.warning(warning)
|
|
} else {
|
|
toast.success(`Dashboard "${createdDashboard.name}" created`)
|
|
}
|
|
router.actions.push(urls.dashboard(createdDashboard.id))
|
|
},
|
|
createDashboardFailure: ({ message }) => {
|
|
toast.error(message)
|
|
},
|
|
})),
|
|
])
|