mirror of
https://github.com/Dvorinka/Primora.git
synced 2026-10-28 15:51:23 +00:00
feat: phase 3 observability — IMS telemetry merge
Port IMS telemetry into Primora conventions: - core.components/core.events (migration 00004 + sqlc) scoped to projects - POST /api/v1/ingest — single or <=500-event batches via project prm_ keys (X-Primora-Key alias), auto-registering components, permissive CORS - Issues: fingerprint-grouped errors with count/severity/first+last seen - Reads: events w/ filters + before-cursor, components, stats (1h/24h/7d/30d bucketed series + component health + totals + metric names), metric series (avg/p50/p95/max) - GET .../telemetry/stream — project-scoped SSE through an in-process hub (buffered subs, drop-on-slow, 25s keepalives); ?api_key=/?token= auth for EventSource - Telemetry page: health/issues/events/metrics/integrate tabs, live badge, 390px-clean; demo stubs - @primora/client SDK (createPrimoraClient: capture*/heartbeat/installAuto, queued keepalive flushes) - MCP: primora_list_issues/list_events/telemetry_stats/metric_series - IMS README deprecation note pointing here Also fixes workspace pins to 0.3.0 — npm ci in the frontend image was resolving the unpublished 0.2.0 spec from the registry. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
6507eee606
commit
8cfddf9184
60 files changed
+3694
-30
No files matched your search
@@ -200,6 +200,15 @@ type CoreCollection struct {
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
type CoreComponent struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
ProjectID uuid.UUID `json:"project_id"`
|
||||
Name string `json:"name"`
|
||||
Kind string `json:"kind"`
|
||||
Meta []byte `json:"meta"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
}
|
||||
|
||||
type CoreDbConnection struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
ProjectID uuid.UUID `json:"project_id"`
|
||||
@@ -221,6 +230,18 @@ type CoreDocument struct {
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
type CoreEvent struct {
|
||||
ID int64 `json:"id"`
|
||||
ProjectID uuid.UUID `json:"project_id"`
|
||||
ComponentID pgtype.UUID `json:"component_id"`
|
||||
Type string `json:"type"`
|
||||
Severity string `json:"severity"`
|
||||
Message string `json:"message"`
|
||||
Payload []byte `json:"payload"`
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
Ts pgtype.Timestamptz `json:"ts"`
|
||||
}
|
||||
|
||||
type CoreOrganization struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Slug string `json:"slug"`
|
||||
|
||||
@@ -14,6 +14,7 @@ type Querier interface {
|
||||
AddOrganizationMember(ctx context.Context, arg AddOrganizationMemberParams) (CoreOrganizationMember, error)
|
||||
AddProjectMember(ctx context.Context, arg AddProjectMemberParams) (CoreProjectMember, error)
|
||||
BootstrapOrganization(ctx context.Context, arg BootstrapOrganizationParams) (BootstrapOrganizationRow, error)
|
||||
ComponentHealthRows(ctx context.Context, projectID uuid.UUID) ([]ComponentHealthRowsRow, error)
|
||||
CountAuditLogsForProject(ctx context.Context, arg CountAuditLogsForProjectParams) (int64, error)
|
||||
CountBucketObjects(ctx context.Context, arg CountBucketObjectsParams) (int64, error)
|
||||
CountDocuments(ctx context.Context, collectionID uuid.UUID) (int64, error)
|
||||
@@ -33,17 +34,21 @@ type Querier interface {
|
||||
DeleteBucketByID(ctx context.Context, id uuid.UUID) (CoreBucket, error)
|
||||
DeleteBucketObjectByKey(ctx context.Context, arg DeleteBucketObjectByKeyParams) (CoreBucketObject, error)
|
||||
DeleteCollection(ctx context.Context, arg DeleteCollectionParams) error
|
||||
DeleteComponent(ctx context.Context, arg DeleteComponentParams) error
|
||||
DeleteDBConnection(ctx context.Context, arg DeleteDBConnectionParams) (CoreDbConnection, error)
|
||||
DeleteDocument(ctx context.Context, arg DeleteDocumentParams) error
|
||||
DeleteOrganizationByID(ctx context.Context, id uuid.UUID) (CoreOrganization, error)
|
||||
DeletePendingInvitationByIDForOrganization(ctx context.Context, arg DeletePendingInvitationByIDForOrganizationParams) (CoreProjectInvitation, error)
|
||||
DeleteProjectByID(ctx context.Context, id uuid.UUID) (CoreProject, error)
|
||||
EventTotals(ctx context.Context, arg EventTotalsParams) (EventTotalsRow, error)
|
||||
EventTypeSeries(ctx context.Context, arg EventTypeSeriesParams) ([]EventTypeSeriesRow, error)
|
||||
GetAPIKeyByIDForProject(ctx context.Context, arg GetAPIKeyByIDForProjectParams) (CoreApiKey, error)
|
||||
GetAPIKeyByPrefix(ctx context.Context, prefix string) (GetAPIKeyByPrefixRow, error)
|
||||
GetBucketByID(ctx context.Context, id uuid.UUID) (GetBucketByIDRow, error)
|
||||
GetBucketObjectByKey(ctx context.Context, arg GetBucketObjectByKeyParams) (CoreBucketObject, error)
|
||||
GetCollectionByID(ctx context.Context, id uuid.UUID) (CoreCollection, error)
|
||||
GetCollectionBySlug(ctx context.Context, arg GetCollectionBySlugParams) (CoreCollection, error)
|
||||
GetComponentByName(ctx context.Context, arg GetComponentByNameParams) (CoreComponent, error)
|
||||
GetDBConnectionByID(ctx context.Context, id uuid.UUID) (CoreDbConnection, error)
|
||||
GetDBConnectionByName(ctx context.Context, arg GetDBConnectionByNameParams) (CoreDbConnection, error)
|
||||
GetDocumentByID(ctx context.Context, arg GetDocumentByIDParams) (CoreDocument, error)
|
||||
@@ -55,20 +60,26 @@ type Querier interface {
|
||||
GetProjectOverview(ctx context.Context, id uuid.UUID) (GetProjectOverviewRow, error)
|
||||
GetUserByAuthSubject(ctx context.Context, authSubject string) (CoreUser, error)
|
||||
GetUserByID(ctx context.Context, id uuid.UUID) (CoreUser, error)
|
||||
InsertEvent(ctx context.Context, arg InsertEventParams) (CoreEvent, error)
|
||||
ListAPIKeysForProject(ctx context.Context, projectID uuid.UUID) ([]CoreApiKey, error)
|
||||
ListAuditLogsForProject(ctx context.Context, arg ListAuditLogsForProjectParams) ([]CoreAuditLog, error)
|
||||
ListBucketObjects(ctx context.Context, arg ListBucketObjectsParams) ([]CoreBucketObject, error)
|
||||
ListBucketsForOrganization(ctx context.Context, organizationID uuid.UUID) ([]uuid.UUID, error)
|
||||
ListBucketsForProject(ctx context.Context, arg ListBucketsForProjectParams) ([]CoreBucket, error)
|
||||
ListCollections(ctx context.Context, projectID uuid.UUID) ([]CoreCollection, error)
|
||||
ListComponents(ctx context.Context, projectID uuid.UUID) ([]CoreComponent, error)
|
||||
ListDBConnections(ctx context.Context, projectID uuid.UUID) ([]CoreDbConnection, error)
|
||||
ListDocuments(ctx context.Context, arg ListDocumentsParams) ([]CoreDocument, error)
|
||||
ListErrorGroups(ctx context.Context, arg ListErrorGroupsParams) ([]ListErrorGroupsRow, error)
|
||||
ListEvents(ctx context.Context, arg ListEventsParams) ([]ListEventsRow, error)
|
||||
ListInvitationsForOrganization(ctx context.Context, organizationID uuid.UUID) ([]ListInvitationsForOrganizationRow, error)
|
||||
ListOrganizationMembers(ctx context.Context, organizationID uuid.UUID) ([]ListOrganizationMembersRow, error)
|
||||
ListOrganizationsForUser(ctx context.Context, userID uuid.UUID) ([]ListOrganizationsForUserRow, error)
|
||||
ListProjectMembers(ctx context.Context, projectID uuid.UUID) ([]ListProjectMembersRow, error)
|
||||
ListProjectsForOrganization(ctx context.Context, arg ListProjectsForOrganizationParams) ([]ListProjectsForOrganizationRow, error)
|
||||
MarkInvitationAccepted(ctx context.Context, id uuid.UUID) (CoreProjectInvitation, error)
|
||||
MetricNames(ctx context.Context, projectID uuid.UUID) ([]string, error)
|
||||
MetricSeries(ctx context.Context, arg MetricSeriesParams) ([]MetricSeriesRow, error)
|
||||
MoveBucketObject(ctx context.Context, arg MoveBucketObjectParams) (CoreBucketObject, error)
|
||||
RemoveOrganizationMember(ctx context.Context, arg RemoveOrganizationMemberParams) (CoreOrganizationMember, error)
|
||||
RemoveProjectMember(ctx context.Context, arg RemoveProjectMemberParams) (CoreProjectMember, error)
|
||||
@@ -82,6 +93,7 @@ type Querier interface {
|
||||
UpdateOrganizationMemberRole(ctx context.Context, arg UpdateOrganizationMemberRoleParams) (CoreOrganizationMember, error)
|
||||
UpdateProjectByID(ctx context.Context, arg UpdateProjectByIDParams) (CoreProject, error)
|
||||
UpdateProjectMemberRole(ctx context.Context, arg UpdateProjectMemberRoleParams) (CoreProjectMember, error)
|
||||
UpsertComponent(ctx context.Context, arg UpsertComponentParams) (CoreComponent, error)
|
||||
UpsertUser(ctx context.Context, arg UpsertUserParams) (CoreUser, error)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,504 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// source: telemetry.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const componentHealthRows = `-- name: ComponentHealthRows :many
|
||||
SELECT co.id, co.name, co.kind, co.meta,
|
||||
max(e.ts)::timestamptz AS last_seen_at,
|
||||
coalesce(((array_agg(e.payload->>'status' ORDER BY e.ts DESC)
|
||||
FILTER (WHERE e.type = 'heartbeat'))[1]), '')::text AS last_status,
|
||||
count(*) FILTER (WHERE e.type = 'error' AND e.ts > now() - interval '24 hours') AS errors_24h,
|
||||
count(*) FILTER (WHERE e.ts > now() - interval '24 hours') AS events_24h
|
||||
FROM core.components co
|
||||
LEFT JOIN core.events e ON e.component_id = co.id
|
||||
WHERE co.project_id = $1
|
||||
GROUP BY co.id
|
||||
ORDER BY co.name
|
||||
`
|
||||
|
||||
type ComponentHealthRowsRow struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Kind string `json:"kind"`
|
||||
Meta []byte `json:"meta"`
|
||||
LastSeenAt pgtype.Timestamptz `json:"last_seen_at"`
|
||||
LastStatus string `json:"last_status"`
|
||||
Errors24h int64 `json:"errors_24h"`
|
||||
Events24h int64 `json:"events_24h"`
|
||||
}
|
||||
|
||||
func (q *Queries) ComponentHealthRows(ctx context.Context, projectID uuid.UUID) ([]ComponentHealthRowsRow, error) {
|
||||
rows, err := q.db.Query(ctx, componentHealthRows, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ComponentHealthRowsRow
|
||||
for rows.Next() {
|
||||
var i ComponentHealthRowsRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Kind,
|
||||
&i.Meta,
|
||||
&i.LastSeenAt,
|
||||
&i.LastStatus,
|
||||
&i.Errors24h,
|
||||
&i.Events24h,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const deleteComponent = `-- name: DeleteComponent :exec
|
||||
DELETE FROM core.components
|
||||
WHERE id = $1 AND project_id = $2
|
||||
`
|
||||
|
||||
type DeleteComponentParams struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
ProjectID uuid.UUID `json:"project_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) DeleteComponent(ctx context.Context, arg DeleteComponentParams) error {
|
||||
_, err := q.db.Exec(ctx, deleteComponent, arg.ID, arg.ProjectID)
|
||||
return err
|
||||
}
|
||||
|
||||
const eventTotals = `-- name: EventTotals :one
|
||||
SELECT count(*) FILTER (WHERE type = 'error') AS errors,
|
||||
count(*) AS events,
|
||||
count(*) FILTER (WHERE type = 'metric') AS metrics
|
||||
FROM core.events
|
||||
WHERE project_id = $1 AND ts > now() - make_interval(secs => $2::bigint)
|
||||
`
|
||||
|
||||
type EventTotalsParams struct {
|
||||
ProjectID uuid.UUID `json:"project_id"`
|
||||
WindowSec int64 `json:"window_sec"`
|
||||
}
|
||||
|
||||
type EventTotalsRow struct {
|
||||
Errors int64 `json:"errors"`
|
||||
Events int64 `json:"events"`
|
||||
Metrics int64 `json:"metrics"`
|
||||
}
|
||||
|
||||
func (q *Queries) EventTotals(ctx context.Context, arg EventTotalsParams) (EventTotalsRow, error) {
|
||||
row := q.db.QueryRow(ctx, eventTotals, arg.ProjectID, arg.WindowSec)
|
||||
var i EventTotalsRow
|
||||
err := row.Scan(&i.Errors, &i.Events, &i.Metrics)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const eventTypeSeries = `-- name: EventTypeSeries :many
|
||||
SELECT to_timestamp(floor(extract(epoch FROM e.ts) / $2::bigint) * $2::bigint)::timestamptz AS bucket,
|
||||
e.type,
|
||||
count(*) AS count
|
||||
FROM core.events e
|
||||
WHERE e.project_id = $1 AND e.ts > now() - make_interval(secs => $3::bigint)
|
||||
GROUP BY bucket, e.type
|
||||
ORDER BY bucket
|
||||
`
|
||||
|
||||
type EventTypeSeriesParams struct {
|
||||
ProjectID uuid.UUID `json:"project_id"`
|
||||
BucketSec int64 `json:"bucket_sec"`
|
||||
WindowSec int64 `json:"window_sec"`
|
||||
}
|
||||
|
||||
type EventTypeSeriesRow struct {
|
||||
Bucket pgtype.Timestamptz `json:"bucket"`
|
||||
Type string `json:"type"`
|
||||
Count int64 `json:"count"`
|
||||
}
|
||||
|
||||
func (q *Queries) EventTypeSeries(ctx context.Context, arg EventTypeSeriesParams) ([]EventTypeSeriesRow, error) {
|
||||
rows, err := q.db.Query(ctx, eventTypeSeries, arg.ProjectID, arg.BucketSec, arg.WindowSec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []EventTypeSeriesRow
|
||||
for rows.Next() {
|
||||
var i EventTypeSeriesRow
|
||||
if err := rows.Scan(&i.Bucket, &i.Type, &i.Count); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getComponentByName = `-- name: GetComponentByName :one
|
||||
SELECT id, project_id, name, kind, meta, created_at FROM core.components
|
||||
WHERE project_id = $1 AND name = $2
|
||||
`
|
||||
|
||||
type GetComponentByNameParams struct {
|
||||
ProjectID uuid.UUID `json:"project_id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetComponentByName(ctx context.Context, arg GetComponentByNameParams) (CoreComponent, error) {
|
||||
row := q.db.QueryRow(ctx, getComponentByName, arg.ProjectID, arg.Name)
|
||||
var i CoreComponent
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.ProjectID,
|
||||
&i.Name,
|
||||
&i.Kind,
|
||||
&i.Meta,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertEvent = `-- name: InsertEvent :one
|
||||
INSERT INTO core.events (project_id, component_id, type, severity, message, payload, fingerprint, ts)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING id, project_id, component_id, type, severity, message, payload, fingerprint, ts
|
||||
`
|
||||
|
||||
type InsertEventParams struct {
|
||||
ProjectID uuid.UUID `json:"project_id"`
|
||||
ComponentID pgtype.UUID `json:"component_id"`
|
||||
Type string `json:"type"`
|
||||
Severity string `json:"severity"`
|
||||
Message string `json:"message"`
|
||||
Payload []byte `json:"payload"`
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
Ts pgtype.Timestamptz `json:"ts"`
|
||||
}
|
||||
|
||||
func (q *Queries) InsertEvent(ctx context.Context, arg InsertEventParams) (CoreEvent, error) {
|
||||
row := q.db.QueryRow(ctx, insertEvent,
|
||||
arg.ProjectID,
|
||||
arg.ComponentID,
|
||||
arg.Type,
|
||||
arg.Severity,
|
||||
arg.Message,
|
||||
arg.Payload,
|
||||
arg.Fingerprint,
|
||||
arg.Ts,
|
||||
)
|
||||
var i CoreEvent
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.ProjectID,
|
||||
&i.ComponentID,
|
||||
&i.Type,
|
||||
&i.Severity,
|
||||
&i.Message,
|
||||
&i.Payload,
|
||||
&i.Fingerprint,
|
||||
&i.Ts,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const listComponents = `-- name: ListComponents :many
|
||||
SELECT id, project_id, name, kind, meta, created_at FROM core.components
|
||||
WHERE project_id = $1
|
||||
ORDER BY name
|
||||
`
|
||||
|
||||
func (q *Queries) ListComponents(ctx context.Context, projectID uuid.UUID) ([]CoreComponent, error) {
|
||||
rows, err := q.db.Query(ctx, listComponents, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []CoreComponent
|
||||
for rows.Next() {
|
||||
var i CoreComponent
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.ProjectID,
|
||||
&i.Name,
|
||||
&i.Kind,
|
||||
&i.Meta,
|
||||
&i.CreatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listErrorGroups = `-- name: ListErrorGroups :many
|
||||
SELECT e.fingerprint,
|
||||
(array_agg(e.message ORDER BY e.ts DESC))[1]::text AS message,
|
||||
coalesce((array_agg(e.component_id::text ORDER BY e.ts DESC))[1], '')::text AS component_id,
|
||||
coalesce((array_agg(co.name ORDER BY e.ts DESC))[1], '')::text AS component_name,
|
||||
count(*) AS count,
|
||||
min(e.ts)::timestamptz AS first_seen,
|
||||
max(e.ts)::timestamptz AS last_seen,
|
||||
(array_agg(e.severity ORDER BY e.ts DESC))[1]::text AS severity
|
||||
FROM core.events e
|
||||
LEFT JOIN core.components co ON co.id = e.component_id
|
||||
WHERE e.project_id = $1 AND e.type = 'error' AND e.fingerprint <> ''
|
||||
AND e.ts > now() - ($2::text || ' days')::interval
|
||||
GROUP BY e.fingerprint
|
||||
ORDER BY max(e.ts) DESC
|
||||
LIMIT 200
|
||||
`
|
||||
|
||||
type ListErrorGroupsParams struct {
|
||||
ProjectID uuid.UUID `json:"project_id"`
|
||||
Days *string `json:"days"`
|
||||
}
|
||||
|
||||
type ListErrorGroupsRow struct {
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
Message string `json:"message"`
|
||||
ComponentID string `json:"component_id"`
|
||||
ComponentName string `json:"component_name"`
|
||||
Count int64 `json:"count"`
|
||||
FirstSeen pgtype.Timestamptz `json:"first_seen"`
|
||||
LastSeen pgtype.Timestamptz `json:"last_seen"`
|
||||
Severity string `json:"severity"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListErrorGroups(ctx context.Context, arg ListErrorGroupsParams) ([]ListErrorGroupsRow, error) {
|
||||
rows, err := q.db.Query(ctx, listErrorGroups, arg.ProjectID, arg.Days)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ListErrorGroupsRow
|
||||
for rows.Next() {
|
||||
var i ListErrorGroupsRow
|
||||
if err := rows.Scan(
|
||||
&i.Fingerprint,
|
||||
&i.Message,
|
||||
&i.ComponentID,
|
||||
&i.ComponentName,
|
||||
&i.Count,
|
||||
&i.FirstSeen,
|
||||
&i.LastSeen,
|
||||
&i.Severity,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listEvents = `-- name: ListEvents :many
|
||||
SELECT e.id, e.project_id, e.component_id, e.type, e.severity, e.message, e.payload, e.fingerprint, e.ts, co.name AS component_name
|
||||
FROM core.events e
|
||||
LEFT JOIN core.components co ON co.id = e.component_id
|
||||
WHERE e.project_id = $1
|
||||
AND ($2::text IS NULL OR e.type = $2)
|
||||
AND ($3::text IS NULL OR co.name = $3)
|
||||
AND ($4::text IS NULL OR e.fingerprint = $4)
|
||||
AND ($5::bigint IS NULL OR e.id < $5)
|
||||
ORDER BY e.id DESC
|
||||
LIMIT $6
|
||||
`
|
||||
|
||||
type ListEventsParams struct {
|
||||
ProjectID uuid.UUID `json:"project_id"`
|
||||
Type *string `json:"type"`
|
||||
Component *string `json:"component"`
|
||||
Fingerprint *string `json:"fingerprint"`
|
||||
Before *int64 `json:"before"`
|
||||
Limit int32 `json:"limit"`
|
||||
}
|
||||
|
||||
type ListEventsRow struct {
|
||||
ID int64 `json:"id"`
|
||||
ProjectID uuid.UUID `json:"project_id"`
|
||||
ComponentID pgtype.UUID `json:"component_id"`
|
||||
Type string `json:"type"`
|
||||
Severity string `json:"severity"`
|
||||
Message string `json:"message"`
|
||||
Payload []byte `json:"payload"`
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
Ts pgtype.Timestamptz `json:"ts"`
|
||||
ComponentName *string `json:"component_name"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListEvents(ctx context.Context, arg ListEventsParams) ([]ListEventsRow, error) {
|
||||
rows, err := q.db.Query(ctx, listEvents,
|
||||
arg.ProjectID,
|
||||
arg.Type,
|
||||
arg.Component,
|
||||
arg.Fingerprint,
|
||||
arg.Before,
|
||||
arg.Limit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ListEventsRow
|
||||
for rows.Next() {
|
||||
var i ListEventsRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.ProjectID,
|
||||
&i.ComponentID,
|
||||
&i.Type,
|
||||
&i.Severity,
|
||||
&i.Message,
|
||||
&i.Payload,
|
||||
&i.Fingerprint,
|
||||
&i.Ts,
|
||||
&i.ComponentName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const metricNames = `-- name: MetricNames :many
|
||||
SELECT DISTINCT (payload->>'name')::text AS name
|
||||
FROM core.events
|
||||
WHERE project_id = $1 AND type = 'metric' AND payload ? 'name'
|
||||
ORDER BY 1
|
||||
LIMIT 50
|
||||
`
|
||||
|
||||
func (q *Queries) MetricNames(ctx context.Context, projectID uuid.UUID) ([]string, error) {
|
||||
rows, err := q.db.Query(ctx, metricNames, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []string
|
||||
for rows.Next() {
|
||||
var name string
|
||||
if err := rows.Scan(&name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, name)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const metricSeries = `-- name: MetricSeries :many
|
||||
SELECT to_timestamp(floor(extract(epoch FROM ts) / $2::bigint) * $2::bigint)::timestamptz AS bucket,
|
||||
avg((payload->>'value')::float8)::float8 AS avg,
|
||||
percentile_cont(0.5) WITHIN GROUP (ORDER BY (payload->>'value')::float8)::float8 AS p50,
|
||||
percentile_cont(0.95) WITHIN GROUP (ORDER BY (payload->>'value')::float8)::float8 AS p95,
|
||||
max((payload->>'value')::float8)::float8 AS max,
|
||||
count(*) AS count
|
||||
FROM core.events
|
||||
WHERE project_id = $1 AND type = 'metric' AND payload->>'name' = $3::text
|
||||
AND ts > now() - make_interval(secs => $4::bigint)
|
||||
AND jsonb_typeof(payload->'value') = 'number'
|
||||
GROUP BY bucket
|
||||
ORDER BY bucket
|
||||
`
|
||||
|
||||
type MetricSeriesParams struct {
|
||||
ProjectID uuid.UUID `json:"project_id"`
|
||||
BucketSec int64 `json:"bucket_sec"`
|
||||
Name string `json:"name"`
|
||||
WindowSec int64 `json:"window_sec"`
|
||||
}
|
||||
|
||||
type MetricSeriesRow struct {
|
||||
Bucket pgtype.Timestamptz `json:"bucket"`
|
||||
Avg float64 `json:"avg"`
|
||||
P50 float64 `json:"p50"`
|
||||
P95 float64 `json:"p95"`
|
||||
Max float64 `json:"max"`
|
||||
Count int64 `json:"count"`
|
||||
}
|
||||
|
||||
func (q *Queries) MetricSeries(ctx context.Context, arg MetricSeriesParams) ([]MetricSeriesRow, error) {
|
||||
rows, err := q.db.Query(ctx, metricSeries,
|
||||
arg.ProjectID,
|
||||
arg.BucketSec,
|
||||
arg.Name,
|
||||
arg.WindowSec,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []MetricSeriesRow
|
||||
for rows.Next() {
|
||||
var i MetricSeriesRow
|
||||
if err := rows.Scan(
|
||||
&i.Bucket,
|
||||
&i.Avg,
|
||||
&i.P50,
|
||||
&i.P95,
|
||||
&i.Max,
|
||||
&i.Count,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const upsertComponent = `-- name: UpsertComponent :one
|
||||
INSERT INTO core.components (project_id, name, kind)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (project_id, name) DO UPDATE SET name = EXCLUDED.name
|
||||
RETURNING id, project_id, name, kind, meta, created_at
|
||||
`
|
||||
|
||||
type UpsertComponentParams struct {
|
||||
ProjectID uuid.UUID `json:"project_id"`
|
||||
Name string `json:"name"`
|
||||
Kind string `json:"kind"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpsertComponent(ctx context.Context, arg UpsertComponentParams) (CoreComponent, error) {
|
||||
row := q.db.QueryRow(ctx, upsertComponent, arg.ProjectID, arg.Name, arg.Kind)
|
||||
var i CoreComponent
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.ProjectID,
|
||||
&i.Name,
|
||||
&i.Kind,
|
||||
&i.Meta,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
Reference in new issue
Block a user