feat(tenant): dedup article publish status by latest record per target
Aggregate publish status from the latest record per publishing target using a shared target-identity expression, so retries and multi-record targets no longer inflate the status buckets. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -110,24 +110,18 @@ const articlePublishStatusAggregateJoin = `
|
|||||||
BOOL_OR(latest.status_bucket = 'success') AS has_success,
|
BOOL_OR(latest.status_bucket = 'success') AS has_success,
|
||||||
BOOL_OR(latest.status_bucket = 'failed') AS has_failed
|
BOOL_OR(latest.status_bucket = 'failed') AS has_failed
|
||||||
FROM (
|
FROM (
|
||||||
SELECT DISTINCT ON (
|
SELECT DISTINCT ON (` + publishRecordTargetIdentityExprSQL + `)
|
||||||
COALESCE(pr.target_type, 'platform_account'),
|
|
||||||
COALESCE(pr.platform_account_id, pr.target_connection_id)
|
|
||||||
)
|
|
||||||
CASE
|
CASE
|
||||||
WHEN pr.status IN ('success', 'published', 'publish_success') THEN 'success'
|
WHEN pr.status IN ('success', 'published', 'publish_success') THEN 'success'
|
||||||
WHEN pr.status IN ('publishing', 'pending', 'queued', 'running') THEN 'publishing'
|
WHEN pr.status IN ('publishing', 'pending', 'queued', 'running') THEN 'publishing'
|
||||||
ELSE 'failed'
|
ELSE 'failed'
|
||||||
END AS status_bucket
|
END AS status_bucket
|
||||||
FROM publish_records pr
|
FROM publish_records pr
|
||||||
|
LEFT JOIN platform_accounts pa ON pa.id = pr.platform_account_id
|
||||||
WHERE pr.tenant_id = a.tenant_id
|
WHERE pr.tenant_id = a.tenant_id
|
||||||
AND pr.article_id = a.id
|
AND pr.article_id = a.id
|
||||||
AND pr.deleted_at IS NULL
|
AND pr.deleted_at IS NULL
|
||||||
ORDER BY
|
ORDER BY ` + publishRecordTargetIdentityExprSQL + `, pr.created_at DESC, pr.id DESC
|
||||||
COALESCE(pr.target_type, 'platform_account'),
|
|
||||||
COALESCE(pr.platform_account_id, pr.target_connection_id),
|
|
||||||
pr.created_at DESC,
|
|
||||||
pr.id DESC
|
|
||||||
) latest
|
) latest
|
||||||
) publish_status_summary ON true`
|
) publish_status_summary ON true`
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ type monitoringArticleAliasInput struct {
|
|||||||
ExternalManageURL *string
|
ExternalManageURL *string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const publishRecordTargetIdentityExprSQL = `CASE
|
||||||
|
WHEN COALESCE(pr.target_type, 'platform_account') = 'enterprise_site'
|
||||||
|
THEN 'enterprise_site:' || COALESCE(pr.target_connection_id::TEXT, '')
|
||||||
|
ELSE 'platform_account:' || pr.platform_id || ':' || COALESCE(NULLIF(pa.platform_uid, ''), pr.platform_account_id::TEXT, '')
|
||||||
|
END`
|
||||||
|
|
||||||
func loadPublishableArticleMeta(ctx context.Context, tx pgx.Tx, tenantID, brandID, articleID int64) (*publishableArticleMeta, error) {
|
func loadPublishableArticleMeta(ctx context.Context, tx pgx.Tx, tenantID, brandID, articleID int64) (*publishableArticleMeta, error) {
|
||||||
var generateStatus string
|
var generateStatus string
|
||||||
var wizardStateJSON []byte
|
var wizardStateJSON []byte
|
||||||
@@ -664,9 +670,14 @@ func recalculatePublishBatchStatus(ctx context.Context, tx pgx.Tx, publishBatchI
|
|||||||
func recalculateArticlePublishStatus(ctx context.Context, tx pgx.Tx, tenantID, articleID int64) (string, error) {
|
func recalculateArticlePublishStatus(ctx context.Context, tx pgx.Tx, tenantID, articleID int64) (string, error) {
|
||||||
rows, err := tx.Query(ctx, `
|
rows, err := tx.Query(ctx, `
|
||||||
SELECT status
|
SELECT status
|
||||||
FROM publish_records
|
FROM (
|
||||||
WHERE tenant_id = $1 AND article_id = $2
|
SELECT DISTINCT ON (`+publishRecordTargetIdentityExprSQL+`) pr.status
|
||||||
AND deleted_at IS NULL
|
FROM publish_records pr
|
||||||
|
LEFT JOIN platform_accounts pa ON pa.id = pr.platform_account_id
|
||||||
|
WHERE pr.tenant_id = $1 AND pr.article_id = $2
|
||||||
|
AND pr.deleted_at IS NULL
|
||||||
|
ORDER BY `+publishRecordTargetIdentityExprSQL+`, pr.created_at DESC, pr.id DESC
|
||||||
|
) latest
|
||||||
`, tenantID, articleID)
|
`, tenantID, articleID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", response.ErrInternal(50055, "article_publish_status_query_failed", "failed to inspect article publish status")
|
return "", response.ErrInternal(50055, "article_publish_status_query_failed", "failed to inspect article publish status")
|
||||||
|
|||||||
@@ -231,3 +231,40 @@ func TestRecalculateArticlePublishStatusDefaultsToUnpublished(t *testing.T) {
|
|||||||
t.Fatalf("recalculateArticlePublishStatus() = %q, want unpublished", got)
|
t.Fatalf("recalculateArticlePublishStatus() = %q, want unpublished", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRecalculateArticlePublishStatusUsesLatestRecordPerPublishTarget(t *testing.T) {
|
||||||
|
tx := &capturePublishDedupLookupTx{rows: emptyPublishDedupRows{}}
|
||||||
|
|
||||||
|
_, err := recalculateArticlePublishStatus(context.Background(), tx, 1, 2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("recalculateArticlePublishStatus() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized := normalizeSQLWhitespace(tx.sql)
|
||||||
|
for _, want := range []string{
|
||||||
|
"SELECT DISTINCT ON",
|
||||||
|
"LEFT JOIN platform_accounts pa ON pa.id = pr.platform_account_id",
|
||||||
|
"pa.platform_uid",
|
||||||
|
"pr.platform_id",
|
||||||
|
"pr.created_at DESC, pr.id DESC",
|
||||||
|
} {
|
||||||
|
if !strings.Contains(normalized, want) {
|
||||||
|
t.Fatalf("recalculate article publish status SQL missing %q: %s", want, normalized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArticlePublishStatusAggregateUsesLatestRecordPerPublishTarget(t *testing.T) {
|
||||||
|
normalized := normalizeSQLWhitespace(articlePublishStatusAggregateJoin)
|
||||||
|
for _, want := range []string{
|
||||||
|
"SELECT DISTINCT ON",
|
||||||
|
"LEFT JOIN platform_accounts pa ON pa.id = pr.platform_account_id",
|
||||||
|
"pa.platform_uid",
|
||||||
|
"pr.platform_id",
|
||||||
|
"pr.created_at DESC, pr.id DESC",
|
||||||
|
} {
|
||||||
|
if !strings.Contains(normalized, want) {
|
||||||
|
t.Fatalf("article publish aggregate SQL missing %q: %s", want, normalized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user