---
apiVersion: data-spec/v1
id: campaign-hierarchy-related-list-20260402
description: "Repair the default Campaign hierarchy related list so child campaign names render first and link correctly"
issue: "#399"
created: 2026-04-02
scope: production-only
status: pending
tables: [_System_PageLayout]
depends_on: []
---

# Campaign Hierarchy Related List

## Summary

Issue #399 reproduces on the deployed Campaign detail page because the persisted
`default_campaign` layout stores the self-related Campaign list with
`label = "Campaigns"` and `fields = []`. The frontend then falls back to the
first queryable Campaign columns, which hides the child campaign name entirely.

This spec repairs the persisted layout config so the Campaign self-related list
is stored as Salesforce-style **Campaign Hierarchy** with the intended columns:

`["Name", "ActualCost", "AmountAllOpportunities", "AmountWonOpportunities"]`

## Changes

### 1. Normalize the `default_campaign` related-list config

```sql
-- Update the persisted default Campaign detail layout in _System_PageLayout.
--
-- Logical target:
--   Id = 'default_campaign'
--   ObjectApiName = 'campaign'
--
-- Logical JSON change inside Config.relatedLists[]:
--   Find the entry where:
--     objectApiName == 'campaign'
--     lookupField   == 'ParentId'
--
--   Then normalize it to:
--     {
--       "id": "campaign-ParentId",
--       "label": "Campaign Hierarchy",
--       "objectApiName": "campaign",
--       "lookupField": "ParentId",
--       "fields": [
--         "Name",
--         "ActualCost",
--         "AmountAllOpportunities",
--         "AmountWonOpportunities"
--       ]
--     }
--
-- Apply procedure:
--   1. Read the current Config JSON from _System_PageLayout where Id = 'default_campaign'.
--   2. Locate the Campaign self-related-list entry (campaign + ParentId).
--   3. If the entry exists, overwrite its label and fields with the values above.
--   4. If the entry is missing, append the normalized object above to relatedLists[].
--   5. Write the updated Config JSON back to the same row and set LastModifiedDate = CURRENT_TIMESTAMP.
```

**Idempotency**: Re-applying produces the same final JSON because the target
entry is fully normalized to one canonical label and field order.

## Rollback

```sql
-- Restore the previous _System_PageLayout row from the backup captured before
-- applying this spec. The backup should include the full default_campaign row,
-- especially the original Config JSON and LastModifiedDate.
```

Rollback note: restoring from the row backup is safer than attempting to
reconstruct the prior broken JSON shape by hand.
