feat(monitoring): generate daily tasks and optimize heartbeat writes
Adds a MonitoringDailyTaskWorker that materializes and dispatches daily collect tasks from active subscription/desktop-client state, with per-run atomic metrics and a Prometheus collector. Dashboard and question-detail APIs now surface a platform_authorization_status so the UI can distinguish no-desktop-client, no-authorized-platforms, and authorized states; quota/access-state lookups resolve by request workspace instead of tenant. Hardens heartbeat: a desktop_client_primary_leases table gives per-(tenant, workspace) sticky primary selection, read-mostly lease resolution avoids a per-heartbeat transaction, and unchanged platform access reports skip snapshot upserts outside a 10-minute refresh window. Adds heartbeat primary/snapshot metrics exposed via token-protected tenant-api /api/internal/metrics endpoints plus Prometheus. Monitoring quota seed is removed from dev-seed since daily plans now derive from desktop bindings, and the projection uses the canonical six-platform catalog so platforms with zero samples still render. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,10 @@ const (
|
||||
monitoringCollectorType = "desktop"
|
||||
monitoringOnlineDuration = 20 * time.Minute
|
||||
monitoringCollectNowQuestionLimit = 5
|
||||
|
||||
monitoringPlatformAuthorizationAuthorized = "authorized"
|
||||
monitoringPlatformAuthorizationNoDesktopClient = "no_desktop_client"
|
||||
monitoringPlatformAuthorizationNoAuthorizedPlatforms = "no_authorized_platforms"
|
||||
)
|
||||
|
||||
type MonitoringService struct {
|
||||
@@ -38,12 +42,14 @@ type MonitoringService struct {
|
||||
rabbitMQ *rabbitmq.Client
|
||||
logger *zap.Logger
|
||||
desktopTasksRolloutPercentage int
|
||||
brandLibraryConfig config.BrandLibraryConfig
|
||||
}
|
||||
|
||||
func NewMonitoringService(
|
||||
businessPool, monitoringPool *pgxpool.Pool,
|
||||
rabbitMQClient *rabbitmq.Client,
|
||||
dispatchCfg config.MonitoringDispatchConfig,
|
||||
brandLibraryCfg config.BrandLibraryConfig,
|
||||
logger *zap.Logger,
|
||||
) *MonitoringService {
|
||||
return &MonitoringService{
|
||||
@@ -52,6 +58,7 @@ func NewMonitoringService(
|
||||
rabbitMQ: rabbitMQClient,
|
||||
logger: logger,
|
||||
desktopTasksRolloutPercentage: dispatchCfg.DesktopTasksRolloutPercentage,
|
||||
brandLibraryConfig: brandLibraryCfg,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +72,10 @@ type MonitoringDashboardCompositeResponse struct {
|
||||
}
|
||||
|
||||
type MonitoringDashboardRuntime struct {
|
||||
CurrentUserClientOnline bool `json:"current_user_client_online"`
|
||||
CurrentUserClientOnline bool `json:"current_user_client_online"`
|
||||
PlatformAuthorizationStatus string `json:"platform_authorization_status"`
|
||||
AuthorizedMonitoringPlatformIDs []string `json:"authorized_monitoring_platform_ids"`
|
||||
AuthorizedMonitoringPlatformCount int `json:"authorized_monitoring_platform_count"`
|
||||
}
|
||||
|
||||
type MonitoringOverview struct {
|
||||
@@ -124,13 +134,14 @@ type MonitoringCitedArticle struct {
|
||||
}
|
||||
|
||||
type MonitoringQuestionDetailResponse struct {
|
||||
QuestionID int64 `json:"question_id"`
|
||||
QuestionHash string `json:"question_hash"`
|
||||
QuestionText string `json:"question_text"`
|
||||
TimeWindow MonitoringQuestionDetailTimeWindow `json:"time_window"`
|
||||
Platforms []MonitoringQuestionDetailPlatform `json:"platforms"`
|
||||
CitationAnalysis []MonitoringQuestionCitationStats `json:"citation_analysis"`
|
||||
ContentCitations []MonitoringQuestionContentCitation `json:"content_citations"`
|
||||
QuestionID int64 `json:"question_id"`
|
||||
QuestionHash string `json:"question_hash"`
|
||||
QuestionText string `json:"question_text"`
|
||||
PlatformAuthorizationStatus string `json:"platform_authorization_status"`
|
||||
TimeWindow MonitoringQuestionDetailTimeWindow `json:"time_window"`
|
||||
Platforms []MonitoringQuestionDetailPlatform `json:"platforms"`
|
||||
CitationAnalysis []MonitoringQuestionCitationStats `json:"citation_analysis"`
|
||||
ContentCitations []MonitoringQuestionContentCitation `json:"content_citations"`
|
||||
}
|
||||
|
||||
type MonitoringQuestionDetailTimeWindow struct {
|
||||
@@ -216,6 +227,16 @@ type monitoringQuotaConfig struct {
|
||||
EnabledPlatforms []string
|
||||
}
|
||||
|
||||
func monitoringPlatformAuthorizationStatus(quota monitoringQuotaConfig) string {
|
||||
if quota.PrimaryClientID == nil {
|
||||
return monitoringPlatformAuthorizationNoDesktopClient
|
||||
}
|
||||
if len(quota.EnabledPlatforms) == 0 {
|
||||
return monitoringPlatformAuthorizationNoAuthorizedPlatforms
|
||||
}
|
||||
return monitoringPlatformAuthorizationAuthorized
|
||||
}
|
||||
|
||||
type monitoringBrandIdentity struct {
|
||||
ID int64
|
||||
Name string
|
||||
@@ -290,6 +311,7 @@ func (s *MonitoringService) DashboardComposite(
|
||||
aiPlatformID *string,
|
||||
) (*MonitoringDashboardCompositeResponse, error) {
|
||||
actor := auth.MustActor(ctx)
|
||||
workspaceID := auth.CurrentWorkspaceID(ctx)
|
||||
|
||||
brand, err := s.resolveBrand(ctx, actor.TenantID, brandID)
|
||||
if err != nil {
|
||||
@@ -302,12 +324,12 @@ func (s *MonitoringService) DashboardComposite(
|
||||
}
|
||||
}
|
||||
|
||||
quota, err := s.loadQuota(ctx, actor.TenantID)
|
||||
quota, err := s.loadQuota(ctx, actor, workspaceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
runtime, err := s.loadDashboardRuntime(ctx, actor)
|
||||
runtime, err := s.loadDashboardRuntime(ctx, actor, workspaceID, quota)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -318,7 +340,7 @@ func (s *MonitoringService) DashboardComposite(
|
||||
}
|
||||
questionIDs := configuredQuestionIDs(configuredQuestions)
|
||||
|
||||
platforms := resolveMonitoringPlatforms(quota.EnabledPlatforms)
|
||||
platforms := defaultMonitoringPlatformMetadata()
|
||||
selectedPlatformID := normalizedOptionalMonitoringPlatformPointer(aiPlatformID)
|
||||
filteredPlatforms := filterMonitoringPlatforms(platforms, selectedPlatformID)
|
||||
startDate, endDate, err := resolveDashboardDateWindow(days, businessDate)
|
||||
@@ -334,7 +356,7 @@ func (s *MonitoringService) DashboardComposite(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accessStates, err := s.loadAccessStates(ctx, actor.TenantID, quota.PrimaryClientID, endDate)
|
||||
accessStates, err := s.loadAccessStates(ctx, actor.TenantID, workspaceID, quota.PrimaryClientID, endDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -369,19 +391,27 @@ func (s *MonitoringService) DashboardComposite(
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *MonitoringService) loadDashboardRuntime(ctx context.Context, actor auth.Actor) (MonitoringDashboardRuntime, error) {
|
||||
online, err := s.hasOnlineClientForUser(ctx, actor.TenantID, actor.PrimaryWorkspaceID, actor.UserID)
|
||||
if err != nil {
|
||||
return MonitoringDashboardRuntime{}, err
|
||||
func (s *MonitoringService) loadDashboardRuntime(ctx context.Context, actor auth.Actor, workspaceID int64, quota monitoringQuotaConfig) (MonitoringDashboardRuntime, error) {
|
||||
online := false
|
||||
if quota.PrimaryClientID != nil {
|
||||
var err error
|
||||
online, err = s.isClientOnline(ctx, actor.TenantID, workspaceID, *quota.PrimaryClientID)
|
||||
if err != nil {
|
||||
return MonitoringDashboardRuntime{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return MonitoringDashboardRuntime{
|
||||
CurrentUserClientOnline: online,
|
||||
CurrentUserClientOnline: online,
|
||||
PlatformAuthorizationStatus: monitoringPlatformAuthorizationStatus(quota),
|
||||
AuthorizedMonitoringPlatformIDs: append([]string(nil), quota.EnabledPlatforms...),
|
||||
AuthorizedMonitoringPlatformCount: len(quota.EnabledPlatforms),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *MonitoringService) QuestionDetail(ctx context.Context, brandID, questionID int64, dateFrom, dateTo string, questionHash string, aiPlatformID *string) (*MonitoringQuestionDetailResponse, error) {
|
||||
actor := auth.MustActor(ctx)
|
||||
workspaceID := auth.CurrentWorkspaceID(ctx)
|
||||
|
||||
brand, err := s.resolveBrand(ctx, actor.TenantID, brandID)
|
||||
if err != nil {
|
||||
@@ -393,12 +423,12 @@ func (s *MonitoringService) QuestionDetail(ctx context.Context, brandID, questio
|
||||
return nil, err
|
||||
}
|
||||
|
||||
quota, err := s.loadQuota(ctx, actor.TenantID)
|
||||
quota, err := s.loadQuota(ctx, actor, workspaceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
platforms := resolveMonitoringPlatforms(quota.EnabledPlatforms)
|
||||
selectedPlatformID := normalizedOptionalMonitoringPlatformPointer(aiPlatformID)
|
||||
platforms := filterMonitoringPlatforms(defaultMonitoringPlatformMetadata(), selectedPlatformID)
|
||||
|
||||
questionText, err := s.loadQuestionText(ctx, actor.TenantID, brand.ID, questionID)
|
||||
if err != nil {
|
||||
@@ -418,7 +448,7 @@ func (s *MonitoringService) QuestionDetail(ctx context.Context, brandID, questio
|
||||
}
|
||||
}
|
||||
|
||||
accessStates, err := s.loadAccessStates(ctx, actor.TenantID, quota.PrimaryClientID, toDate)
|
||||
accessStates, err := s.loadAccessStates(ctx, actor.TenantID, workspaceID, quota.PrimaryClientID, toDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -481,9 +511,10 @@ func (s *MonitoringService) QuestionDetail(ctx context.Context, brandID, questio
|
||||
}
|
||||
|
||||
return &MonitoringQuestionDetailResponse{
|
||||
QuestionID: questionID,
|
||||
QuestionHash: encodeQuestionHash(hashBytes),
|
||||
QuestionText: questionText,
|
||||
QuestionID: questionID,
|
||||
QuestionHash: encodeQuestionHash(hashBytes),
|
||||
QuestionText: questionText,
|
||||
PlatformAuthorizationStatus: monitoringPlatformAuthorizationStatus(quota),
|
||||
TimeWindow: MonitoringQuestionDetailTimeWindow{
|
||||
DateFrom: fromDate.Format("2006-01-02"),
|
||||
DateTo: toDate.Format("2006-01-02"),
|
||||
@@ -501,6 +532,7 @@ func (s *MonitoringService) CollectNow(
|
||||
options MonitoringCollectNowOptions,
|
||||
) (*MonitoringCollectNowResponse, error) {
|
||||
actor := auth.MustActor(ctx)
|
||||
workspaceID := auth.CurrentWorkspaceID(ctx)
|
||||
|
||||
brand, err := s.resolveBrand(ctx, actor.TenantID, brandID)
|
||||
if err != nil {
|
||||
@@ -513,7 +545,7 @@ func (s *MonitoringService) CollectNow(
|
||||
}
|
||||
}
|
||||
|
||||
quota, err := s.loadQuota(ctx, actor.TenantID)
|
||||
quota, err := s.loadQuota(ctx, actor, workspaceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -540,19 +572,7 @@ func (s *MonitoringService) CollectNow(
|
||||
}, nil
|
||||
}
|
||||
|
||||
platforms := resolveMonitoringPlatforms(quota.EnabledPlatforms)
|
||||
platforms, err = intersectMonitoringPlatforms(platforms, options.PlatformIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
questionIDs := configuredQuestionIDs(configuredQuestions)
|
||||
platformIDs := monitoringPlatformIDs(platforms)
|
||||
executionOwner := "legacy"
|
||||
if s.shouldUseDesktopTasksExecution(actor.TenantID) {
|
||||
executionOwner = "desktop_tasks"
|
||||
}
|
||||
|
||||
clientID, err := s.resolveCollectNowClientID(ctx, actor.TenantID, actor.PrimaryWorkspaceID, actor.UserID, options.TargetClientID)
|
||||
clientID, err := s.resolveCollectNowClientID(ctx, actor.TenantID, workspaceID, actor.UserID, quota.PrimaryClientID, options.TargetClientID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -560,10 +580,37 @@ func (s *MonitoringService) CollectNow(
|
||||
return nil, response.ErrConflict(40941, "desktop_client_offline", "current user's monitoring desktop client is offline")
|
||||
}
|
||||
|
||||
if err := s.ensureClientOnline(ctx, actor.TenantID, actor.PrimaryWorkspaceID, *clientID); err != nil {
|
||||
if err := s.ensureClientOnline(ctx, actor.TenantID, workspaceID, *clientID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
targetPlatformIDs, err := s.loadMonitoringClientPlatformIDs(ctx, actor.TenantID, workspaceID, *clientID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
platforms := resolveMonitoringPlatforms(targetPlatformIDs)
|
||||
platforms, err = intersectMonitoringPlatforms(platforms, options.PlatformIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(platforms) == 0 {
|
||||
return &MonitoringCollectNowResponse{
|
||||
CollectionMode: quota.CollectionMode,
|
||||
RefreshedTaskCount: 0,
|
||||
CreatedTaskCount: 0,
|
||||
LeasedTaskCount: 0,
|
||||
CompletedTaskCount: 0,
|
||||
HasEffectiveSnapshot: true,
|
||||
Message: "当前桌面端暂无已授权的 AI 平台,未创建采集任务",
|
||||
}, nil
|
||||
}
|
||||
questionIDs := configuredQuestionIDs(configuredQuestions)
|
||||
platformIDs := monitoringPlatformIDs(platforms)
|
||||
executionOwner := "legacy"
|
||||
if s.shouldUseDesktopTasksExecution(actor.TenantID) {
|
||||
executionOwner = "desktop_tasks"
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
todayTime, _ := monitoringBusinessDayAndDateAt(now)
|
||||
requestID := uuid.New()
|
||||
@@ -584,7 +631,7 @@ func (s *MonitoringService) CollectNow(
|
||||
ctx,
|
||||
tx,
|
||||
actor.TenantID,
|
||||
actor.PrimaryWorkspaceID,
|
||||
workspaceID,
|
||||
brand.ID,
|
||||
actor.UserID,
|
||||
scopeHash,
|
||||
@@ -663,7 +710,7 @@ func (s *MonitoringService) CollectNow(
|
||||
ctx,
|
||||
tx,
|
||||
actor.TenantID,
|
||||
actor.PrimaryWorkspaceID,
|
||||
workspaceID,
|
||||
todayTime,
|
||||
questionIDs,
|
||||
platformIDs,
|
||||
@@ -722,7 +769,7 @@ func (s *MonitoringService) CollectNow(
|
||||
$12, $13, $14,
|
||||
$15, $16
|
||||
)
|
||||
`, requestID, actor.TenantID, actor.PrimaryWorkspaceID, brand.ID, nullableInt64(keywordID), actor.UserID, *clientID, scopeHash, requestScopeJSON, affectedTaskCount, affectedTaskCount, abortedQueuedCount, int64(len(interruptTaskIDs)), interruptGeneration, message, ttlExpiresAt); err != nil {
|
||||
`, requestID, actor.TenantID, workspaceID, brand.ID, nullableInt64(keywordID), actor.UserID, *clientID, scopeHash, requestScopeJSON, affectedTaskCount, affectedTaskCount, abortedQueuedCount, int64(len(interruptTaskIDs)), interruptGeneration, message, ttlExpiresAt); err != nil {
|
||||
return nil, response.ErrInternal(50041, "request_create_failed", "failed to persist monitoring collect-now request")
|
||||
}
|
||||
|
||||
@@ -730,7 +777,7 @@ func (s *MonitoringService) CollectNow(
|
||||
ctx,
|
||||
tx,
|
||||
requestID,
|
||||
actor.PrimaryWorkspaceID,
|
||||
workspaceID,
|
||||
*clientID,
|
||||
affectedTaskCount,
|
||||
interruptGeneration,
|
||||
@@ -748,8 +795,6 @@ func (s *MonitoringService) CollectNow(
|
||||
return nil, response.ErrInternal(50041, "commit_failed", "failed to commit monitoring collect-now")
|
||||
}
|
||||
|
||||
_ = s.persistPrimaryClientID(ctx, actor.TenantID, actor.PrimaryWorkspaceID, *clientID)
|
||||
|
||||
phase2PublishedTasks := make([]*repository.DesktopTask, 0)
|
||||
phase2DispatchReady := executionOwner == "desktop_tasks"
|
||||
if len(phase2TaskSpecs) > 0 {
|
||||
@@ -877,6 +922,7 @@ func (s *MonitoringService) CollectNow(
|
||||
func (s *MonitoringService) resolveCollectNowClientID(
|
||||
ctx context.Context,
|
||||
tenantID, workspaceID, userID int64,
|
||||
defaultClientID *uuid.UUID,
|
||||
preferredClientID *uuid.UUID,
|
||||
) (*uuid.UUID, error) {
|
||||
if preferredClientID != nil {
|
||||
@@ -885,7 +931,7 @@ func (s *MonitoringService) resolveCollectNowClientID(
|
||||
}
|
||||
return preferredClientID, nil
|
||||
}
|
||||
return s.findLatestOnlineClientForUser(ctx, tenantID, workspaceID, userID)
|
||||
return defaultClientID, nil
|
||||
}
|
||||
|
||||
func (s *MonitoringService) hasOnlineClientForUser(ctx context.Context, tenantID, workspaceID, userID int64) (bool, error) {
|
||||
@@ -968,6 +1014,10 @@ func monitoringPlatformIDs(items []monitoringPlatformMetadata) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
func defaultMonitoringPlatformMetadata() []monitoringPlatformMetadata {
|
||||
return append([]monitoringPlatformMetadata(nil), defaultMonitoringPlatforms...)
|
||||
}
|
||||
|
||||
func normalizeMonitoringPlatformID(platformID string) string {
|
||||
return strings.ToLower(strings.TrimSpace(platformID))
|
||||
}
|
||||
@@ -996,10 +1046,30 @@ func monitoringPlatformQueryIDs(value *string) []string {
|
||||
}
|
||||
|
||||
func reconcileEnabledMonitoringPlatforms(enabled []string) []string {
|
||||
// Old quota rows may still contain partial or aliased platform ids. Admin monitoring
|
||||
// should always expose the full canonical desktop-supported set for the tenant.
|
||||
_ = enabled
|
||||
return monitoringPlatformIDs(defaultMonitoringPlatforms)
|
||||
if len(enabled) == 0 {
|
||||
return nil
|
||||
}
|
||||
requested := make(map[string]struct{}, len(enabled))
|
||||
for _, item := range enabled {
|
||||
id := normalizeMonitoringPlatformID(item)
|
||||
if id == "" {
|
||||
continue
|
||||
}
|
||||
if _, supported := monitoringPlatformNames[id]; !supported {
|
||||
continue
|
||||
}
|
||||
requested[id] = struct{}{}
|
||||
}
|
||||
if len(requested) == 0 {
|
||||
return nil
|
||||
}
|
||||
result := make([]string, 0, len(requested))
|
||||
for _, platform := range defaultMonitoringPlatforms {
|
||||
if _, ok := requested[platform.ID]; ok {
|
||||
result = append(result, platform.ID)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func monitoringPlatformSortIndex(platformID string) int {
|
||||
@@ -1054,26 +1124,6 @@ func (s *MonitoringService) findLatestOnlineClientForUser(ctx context.Context, t
|
||||
return &clientID, nil
|
||||
}
|
||||
|
||||
func (s *MonitoringService) persistPrimaryClientID(ctx context.Context, tenantID, workspaceID int64, clientID uuid.UUID) error {
|
||||
if _, err := s.businessPool.Exec(ctx, `
|
||||
INSERT INTO tenant_monitoring_quotas (
|
||||
tenant_id, workspace_id, primary_client_id
|
||||
)
|
||||
VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3
|
||||
)
|
||||
ON CONFLICT (tenant_id) DO UPDATE
|
||||
SET workspace_id = EXCLUDED.workspace_id,
|
||||
primary_client_id = EXCLUDED.primary_client_id,
|
||||
updated_at = NOW()
|
||||
`, tenantID, workspaceID, clientID); err != nil {
|
||||
return response.ErrInternal(50041, "update_failed", "failed to persist primary monitoring desktop client")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatPgInterval(duration time.Duration) string {
|
||||
return fmt.Sprintf("%d seconds", int64(duration/time.Second))
|
||||
}
|
||||
@@ -1431,40 +1481,117 @@ func (s *MonitoringService) deferQueuedNormalTasks(
|
||||
return tag.RowsAffected(), nil
|
||||
}
|
||||
|
||||
func (s *MonitoringService) loadQuota(ctx context.Context, tenantID int64) (monitoringQuotaConfig, error) {
|
||||
func (s *MonitoringService) loadQuota(ctx context.Context, actor auth.Actor, workspaceID int64) (monitoringQuotaConfig, error) {
|
||||
cfg := monitoringQuotaConfig{
|
||||
CollectionMode: monitoringCollectorType,
|
||||
EnabledPlatforms: reconcileEnabledMonitoringPlatforms(nil),
|
||||
CollectionMode: monitoringCollectorType,
|
||||
}
|
||||
|
||||
var enabledJSON []byte
|
||||
var clientID uuid.NullUUID
|
||||
clientID, err := s.findLatestRegisteredClientWithMonitoringAccountForUser(ctx, actor.TenantID, workspaceID, actor.UserID)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
if clientID == nil {
|
||||
clientID, err = s.findLatestRegisteredClientForUser(ctx, actor.TenantID, workspaceID, actor.UserID)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
}
|
||||
cfg.PrimaryClientID = clientID
|
||||
if clientID == nil {
|
||||
return cfg, nil
|
||||
}
|
||||
platforms, err := s.loadMonitoringClientPlatformIDs(ctx, actor.TenantID, workspaceID, *clientID)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
cfg.EnabledPlatforms = platforms
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// findLatestRegisteredClientWithMonitoringAccountForUser returns the newest
|
||||
// non-revoked desktop client that has at least one bound monitoring platform
|
||||
// account. Platform account health is not authorization.
|
||||
func (s *MonitoringService) findLatestRegisteredClientWithMonitoringAccountForUser(ctx context.Context, tenantID, workspaceID, userID int64) (*uuid.UUID, error) {
|
||||
var clientID uuid.UUID
|
||||
err := s.businessPool.QueryRow(ctx, `
|
||||
SELECT collection_mode, enabled_platforms, primary_client_id
|
||||
FROM tenant_monitoring_quotas
|
||||
WHERE tenant_id = $1
|
||||
`, tenantID).Scan(&cfg.CollectionMode, &enabledJSON, &clientID)
|
||||
SELECT dc.id
|
||||
FROM desktop_clients dc
|
||||
WHERE dc.tenant_id = $1
|
||||
AND dc.workspace_id = $2
|
||||
AND dc.user_id = $3
|
||||
AND dc.revoked_at IS NULL
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM platform_accounts pa
|
||||
WHERE pa.tenant_id = dc.tenant_id
|
||||
AND pa.workspace_id = dc.workspace_id
|
||||
AND pa.client_id = dc.id
|
||||
AND pa.deleted_at IS NULL
|
||||
AND pa.platform_id = ANY($4::text[])
|
||||
)
|
||||
ORDER BY dc.created_at DESC, dc.id DESC
|
||||
LIMIT 1
|
||||
`, tenantID, workspaceID, userID, monitoringPlatformIDs(defaultMonitoringPlatforms)).Scan(&clientID)
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return cfg, nil
|
||||
return nil, nil
|
||||
}
|
||||
return cfg, response.ErrInternal(50041, "query_failed", "failed to load monitoring quota")
|
||||
return nil, response.ErrInternal(50041, "query_failed", "failed to inspect monitoring desktop client authorization")
|
||||
}
|
||||
return &clientID, nil
|
||||
}
|
||||
|
||||
if len(enabledJSON) > 0 {
|
||||
var enabled []string
|
||||
if jsonErr := json.Unmarshal(enabledJSON, &enabled); jsonErr == nil {
|
||||
cfg.EnabledPlatforms = reconcileEnabledMonitoringPlatforms(enabled)
|
||||
// findLatestRegisteredClientForUser returns the newest non-revoked desktop client
|
||||
// registered by creation order. It intentionally ignores last_seen_at and health;
|
||||
// use findLatestOnlineClientForUser when online presence is required.
|
||||
func (s *MonitoringService) findLatestRegisteredClientForUser(ctx context.Context, tenantID, workspaceID, userID int64) (*uuid.UUID, error) {
|
||||
var clientID uuid.UUID
|
||||
err := s.businessPool.QueryRow(ctx, `
|
||||
SELECT id
|
||||
FROM desktop_clients
|
||||
WHERE tenant_id = $1
|
||||
AND workspace_id = $2
|
||||
AND user_id = $3
|
||||
AND revoked_at IS NULL
|
||||
ORDER BY created_at DESC, id DESC
|
||||
LIMIT 1
|
||||
`, tenantID, workspaceID, userID).Scan(&clientID)
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, response.ErrInternal(50041, "query_failed", "failed to inspect monitoring desktop client")
|
||||
}
|
||||
if clientID.Valid {
|
||||
id := clientID.UUID
|
||||
cfg.PrimaryClientID = &id
|
||||
return &clientID, nil
|
||||
}
|
||||
|
||||
func (s *MonitoringService) loadMonitoringClientPlatformIDs(ctx context.Context, tenantID, workspaceID int64, clientID uuid.UUID) ([]string, error) {
|
||||
rows, err := s.businessPool.Query(ctx, `
|
||||
SELECT DISTINCT platform_id
|
||||
FROM platform_accounts
|
||||
WHERE tenant_id = $1
|
||||
AND workspace_id = $2
|
||||
AND client_id = $3
|
||||
AND deleted_at IS NULL
|
||||
AND platform_id = ANY($4::text[])
|
||||
`, tenantID, workspaceID, clientID, monitoringPlatformIDs(defaultMonitoringPlatforms))
|
||||
if err != nil {
|
||||
return nil, response.ErrInternal(50041, "query_failed", "failed to load monitoring desktop platform accounts")
|
||||
}
|
||||
if strings.TrimSpace(cfg.CollectionMode) == "" {
|
||||
cfg.CollectionMode = monitoringCollectorType
|
||||
defer rows.Close()
|
||||
|
||||
platformIDs := make([]string, 0)
|
||||
for rows.Next() {
|
||||
var platformID string
|
||||
if scanErr := rows.Scan(&platformID); scanErr != nil {
|
||||
return nil, response.ErrInternal(50041, "scan_failed", "failed to parse monitoring desktop platform accounts")
|
||||
}
|
||||
platformIDs = append(platformIDs, platformID)
|
||||
}
|
||||
return cfg, nil
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, response.ErrInternal(50041, "scan_failed", "failed to iterate monitoring desktop platform accounts")
|
||||
}
|
||||
return reconcileEnabledMonitoringPlatforms(platformIDs), nil
|
||||
}
|
||||
|
||||
func newMonitoringDerivedMetrics() monitoringDerivedMetrics {
|
||||
@@ -2021,11 +2148,18 @@ func applyDerivedRatesToOverviewSnapshot(snapshot *monitoringOverviewSnapshot, d
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MonitoringService) loadAccessStates(ctx context.Context, tenantID int64, clientID *uuid.UUID, businessDate time.Time) (map[string]monitoringAccessState, error) {
|
||||
func (s *MonitoringService) loadAccessStates(ctx context.Context, tenantID, workspaceID int64, clientID *uuid.UUID, businessDate time.Time) (map[string]monitoringAccessState, error) {
|
||||
states := map[string]monitoringAccessState{}
|
||||
if clientID == nil {
|
||||
return states, nil
|
||||
}
|
||||
belongs, err := s.monitoringClientBelongsToWorkspace(ctx, tenantID, workspaceID, *clientID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !belongs {
|
||||
return states, nil
|
||||
}
|
||||
|
||||
rows, err := s.monitoringPool.Query(ctx, `
|
||||
SELECT ai_platform_id, access_status
|
||||
@@ -2054,10 +2188,30 @@ func (s *MonitoringService) loadAccessStates(ctx context.Context, tenantID int64
|
||||
}
|
||||
states[platformID] = monitoringAccessState{AccessStatus: accessStatus}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, response.ErrInternal(50041, "scan_failed", "failed to iterate platform access states")
|
||||
}
|
||||
|
||||
return states, nil
|
||||
}
|
||||
|
||||
func (s *MonitoringService) monitoringClientBelongsToWorkspace(ctx context.Context, tenantID, workspaceID int64, clientID uuid.UUID) (bool, error) {
|
||||
var exists bool
|
||||
if err := s.businessPool.QueryRow(ctx, `
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM desktop_clients
|
||||
WHERE id = $1
|
||||
AND tenant_id = $2
|
||||
AND workspace_id = $3
|
||||
AND revoked_at IS NULL
|
||||
)
|
||||
`, clientID, tenantID, workspaceID).Scan(&exists); err != nil {
|
||||
return false, response.ErrInternal(50041, "query_failed", "failed to validate monitoring desktop client workspace")
|
||||
}
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
func (s *MonitoringService) loadPlatformBreakdown(ctx context.Context, tenantID, brandID int64, businessDate time.Time, collectionMode string, platforms []monitoringPlatformMetadata, accessStates map[string]monitoringAccessState, derived monitoringDerivedMetrics) ([]MonitoringPlatformDaily, error) {
|
||||
rows, err := s.monitoringPool.Query(ctx, `
|
||||
SELECT
|
||||
@@ -3117,7 +3271,7 @@ func normalizeTrackingDays(days int) int {
|
||||
|
||||
func resolveMonitoringPlatforms(enabled []string) []monitoringPlatformMetadata {
|
||||
if len(enabled) == 0 {
|
||||
return defaultMonitoringPlatforms
|
||||
return nil
|
||||
}
|
||||
platforms := make([]monitoringPlatformMetadata, 0, len(enabled))
|
||||
seen := make(map[string]struct{}, len(enabled))
|
||||
@@ -3139,7 +3293,7 @@ func resolveMonitoringPlatforms(enabled []string) []monitoringPlatformMetadata {
|
||||
})
|
||||
}
|
||||
if len(platforms) == 0 {
|
||||
return defaultMonitoringPlatforms
|
||||
return nil
|
||||
}
|
||||
return platforms
|
||||
}
|
||||
@@ -3192,12 +3346,7 @@ func filterMonitoringPlatforms(platforms []monitoringPlatformMetadata, aiPlatfor
|
||||
}
|
||||
}
|
||||
|
||||
return []monitoringPlatformMetadata{
|
||||
{
|
||||
ID: platformID,
|
||||
Name: platformDisplayName(platformID),
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func platformDisplayName(platformID string) string {
|
||||
|
||||
Reference in New Issue
Block a user