Skip to content

Commit e663475

Browse files
authored
Merge pull request #45 from PostHog/vdekrijger-add-layout-support-to-dashboards
feat(resource): Add dashboard-layout resources
2 parents a82c395 + 7a5097c commit e663475

File tree

37 files changed

+5301
-0
lines changed

37 files changed

+5301
-0
lines changed

docs/resources/dashboard_layout.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "posthog_dashboard_layout Resource - posthog"
4+
subcategory: ""
5+
description: |-
6+
Manages the tile layout of a PostHog dashboard. This resource is fully authoritative: it manages all tiles on the dashboard. Unmanaged insight tiles will have their layouts cleared; unmanaged text tiles will be deleted. On destroy, all text tiles are removed and insight tile layouts are cleared.
7+
---
8+
9+
# posthog_dashboard_layout (Resource)
10+
11+
Manages the tile layout of a PostHog dashboard. This resource is fully authoritative: it manages all tiles on the dashboard. Unmanaged insight tiles will have their layouts cleared; unmanaged text tiles will be deleted. On destroy, all text tiles are removed and insight tile layouts are cleared.
12+
13+
## Example Usage
14+
15+
```terraform
16+
# Create the dashboard
17+
resource "posthog_dashboard" "engineering" {
18+
name = "Engineering Metrics"
19+
}
20+
21+
# Create insights and attach them to the dashboard
22+
resource "posthog_insight" "pageviews" {
23+
name = "Page Views"
24+
query_json = jsonencode({
25+
kind = "InsightVizNode"
26+
source = {
27+
kind = "TrendsQuery"
28+
series = [{ kind = "EventsNode", event = "$pageview", math = "total" }]
29+
}
30+
})
31+
dashboard_ids = [posthog_dashboard.engineering.id]
32+
depends_on = [posthog_dashboard.engineering]
33+
}
34+
35+
resource "posthog_insight" "signups" {
36+
name = "Sign-ups"
37+
query_json = jsonencode({
38+
kind = "InsightVizNode"
39+
source = {
40+
kind = "TrendsQuery"
41+
series = [{ kind = "EventsNode", event = "user_signed_up", math = "total" }]
42+
}
43+
})
44+
dashboard_ids = [posthog_dashboard.engineering.id]
45+
depends_on = [posthog_dashboard.engineering]
46+
}
47+
48+
# Manage the dashboard layout: insight tiles + a text tile header
49+
resource "posthog_dashboard_layout" "engineering" {
50+
dashboard_id = posthog_dashboard.engineering.id
51+
52+
tiles = [
53+
# Text tile acting as a section header
54+
{
55+
text_body = "## Key Metrics"
56+
layouts_json = jsonencode({ sm = { x = 0, y = 0, w = 12, h = 1 } })
57+
},
58+
# Insight tiles positioned below the header
59+
{
60+
insight_id = posthog_insight.pageviews.id
61+
layouts_json = jsonencode({ sm = { x = 0, y = 1, w = 6, h = 4 } })
62+
color = "blue"
63+
},
64+
{
65+
insight_id = posthog_insight.signups.id
66+
layouts_json = jsonencode({ sm = { x = 6, y = 1, w = 6, h = 4 } })
67+
},
68+
]
69+
70+
depends_on = [posthog_insight.pageviews, posthog_insight.signups]
71+
}
72+
73+
# Using a for expression to generate tiles from a list of objects
74+
variable "insight_tiles" {
75+
description = "List of insight tiles with their layout positions"
76+
type = list(object({
77+
insight_id = number
78+
layouts = map(object({ x = number, y = number, w = number, h = number }))
79+
}))
80+
default = [
81+
{
82+
insight_id = 12345
83+
layouts = { sm = { x = 0, y = 0, w = 6, h = 4 } }
84+
},
85+
{
86+
insight_id = 67890
87+
layouts = { sm = { x = 6, y = 0, w = 6, h = 4 } }
88+
},
89+
]
90+
}
91+
92+
resource "posthog_dashboard_layout" "dynamic" {
93+
dashboard_id = posthog_dashboard.engineering.id
94+
95+
tiles = [for tile in var.insight_tiles : {
96+
insight_id = tile.insight_id
97+
layouts_json = jsonencode(tile.layouts)
98+
}]
99+
}
100+
```
101+
102+
<!-- schema generated by tfplugindocs -->
103+
## Schema
104+
105+
### Required
106+
107+
- `dashboard_id` (Number) PostHog dashboard ID.
108+
- `tiles` (Attributes List) Ordered list of tiles to manage on the dashboard. (see [below for nested schema](#nestedatt--tiles))
109+
110+
### Optional
111+
112+
- `project_id` (String) Project ID (environment) for this resource. Overrides the provider-level project_id.
113+
114+
### Read-Only
115+
116+
- `id` (Number) Resource ID (same value as dashboard_id).
117+
118+
<a id="nestedatt--tiles"></a>
119+
### Nested Schema for `tiles`
120+
121+
Optional:
122+
123+
- `color` (String) Background color of the tile. Valid values are defined by the PostHog API; see [InsightColor in types.ts](https://github.com/PostHog/posthog/blob/master/frontend/src/types.ts#L2154) for the current list.
124+
- `insight_id` (Number) ID of the insight to display. Exactly one of insight_id or text_body must be set.
125+
- `layouts_json` (String) JSON object with breakpoint keys `sm` and/or `xs`, each containing position properties: `x`, `y`, `w`, `h` (required), and optionally `minW`, `minH` (e.g. `{"sm":{"x":0,"y":0,"w":6,"h":5},"xs":{"x":0,"y":0,"w":1,"h":5}}`). Semantic JSON equality is used to suppress phantom diffs.
126+
- `text_body` (String) Markdown body for a text tile (max 4000 characters). Exactly one of insight_id or text_body must be set.
127+
128+
Read-Only:
129+
130+
- `tile_id` (Number) Server-assigned tile ID. Populated after the first apply.
131+
132+
## Import
133+
134+
Import is supported using the following syntax:
135+
136+
The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:
137+
138+
```shell
139+
# Import an existing dashboard layout using project_id/dashboard_id
140+
terraform import posthog_dashboard_layout.engineering 12345/67890
141+
```

examples/dashboard-layouts/main.tf

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
# Dashboard Layout Example
2+
#
3+
# This example demonstrates the dashboard layout resource in PostHog:
4+
# 1. Create a dashboard
5+
# 2. Create insights to display on the dashboard
6+
# 3. Arrange tiles (insights + text) with precise grid positions
7+
# 4. Optionally: add color to highlight specific tiles
8+
# 5. Optionally: update a text tile body in place
9+
# 6. Optionally: add a new tile
10+
# 7. Optionally: remove a tile and reposition
11+
12+
# =============================================================================
13+
# Step 1: Create the dashboard
14+
# =============================================================================
15+
16+
resource "posthog_dashboard" "demo" {
17+
name = "Production Metrics"
18+
description = "Key metrics managed by Terraform"
19+
}
20+
21+
# =============================================================================
22+
# Step 2: Create insights
23+
# =============================================================================
24+
25+
resource "posthog_insight" "pageviews" {
26+
name = "Pageview Trends"
27+
query_json = jsonencode({
28+
kind = "InsightVizNode"
29+
source = {
30+
kind = "TrendsQuery"
31+
series = [{
32+
kind = "EventsNode"
33+
event = "$pageview"
34+
math = "total"
35+
}]
36+
}
37+
})
38+
dashboard_ids = [posthog_dashboard.demo.id]
39+
depends_on = [posthog_dashboard.demo]
40+
}
41+
42+
resource "posthog_insight" "sessions" {
43+
name = "Session Trends"
44+
query_json = jsonencode({
45+
kind = "InsightVizNode"
46+
source = {
47+
kind = "TrendsQuery"
48+
series = [{
49+
kind = "EventsNode"
50+
event = "$session_start"
51+
math = "total"
52+
}]
53+
}
54+
})
55+
dashboard_ids = [posthog_dashboard.demo.id]
56+
depends_on = [posthog_dashboard.demo]
57+
}
58+
59+
# =============================================================================
60+
# Step 3: Arrange tiles on the dashboard
61+
#
62+
# The layout is authoritative: any tiles not listed here will have their
63+
# layouts cleared (insight tiles) or be soft-deleted (text tiles).
64+
#
65+
# Grid: 12 columns wide. Each tile needs x, y, w, h in at least the "sm"
66+
# breakpoint.
67+
# =============================================================================
68+
69+
resource "posthog_dashboard_layout" "demo" {
70+
dashboard_id = posthog_dashboard.demo.id
71+
72+
tiles = [
73+
{
74+
text_body = "## Production Metrics"
75+
layouts_json = jsonencode({ sm = { x = 0, y = 0, w = 12, h = 1 } })
76+
},
77+
{
78+
insight_id = posthog_insight.pageviews.id
79+
layouts_json = jsonencode({ sm = { x = 0, y = 1, w = 6, h = 5 } })
80+
},
81+
{
82+
insight_id = posthog_insight.sessions.id
83+
layouts_json = jsonencode({ sm = { x = 6, y = 1, w = 6, h = 5 } })
84+
},
85+
]
86+
87+
depends_on = [posthog_insight.pageviews, posthog_insight.sessions]
88+
}
89+
90+
# =============================================================================
91+
# Step 4 (Optional): Add color to highlight a tile
92+
# =============================================================================
93+
94+
# Uncomment to use:
95+
96+
# resource "posthog_dashboard_layout" "demo" {
97+
# dashboard_id = posthog_dashboard.demo.id
98+
#
99+
# tiles = [
100+
# {
101+
# text_body = "## Production Metrics"
102+
# layouts_json = jsonencode({ sm = { x = 0, y = 0, w = 12, h = 1 } })
103+
# },
104+
# {
105+
# insight_id = posthog_insight.pageviews.id
106+
# color = "blue"
107+
# layouts_json = jsonencode({ sm = { x = 0, y = 1, w = 6, h = 5 } })
108+
# },
109+
# {
110+
# insight_id = posthog_insight.sessions.id
111+
# layouts_json = jsonencode({ sm = { x = 6, y = 1, w = 6, h = 5 } })
112+
# },
113+
# ]
114+
#
115+
# depends_on = [posthog_insight.pageviews, posthog_insight.sessions]
116+
# }
117+
118+
# =============================================================================
119+
# Step 5 (Optional): Update text tile body in place
120+
#
121+
# Changing a text tile's body updates the existing tile (matched by position).
122+
# The tile_id stays stable — no destroy/recreate.
123+
# =============================================================================
124+
125+
# Uncomment to use (comment out Steps 3-4 first):
126+
127+
# resource "posthog_dashboard_layout" "demo" {
128+
# dashboard_id = posthog_dashboard.demo.id
129+
#
130+
# tiles = [
131+
# {
132+
# text_body = "## Updated Production Metrics"
133+
# layouts_json = jsonencode({ sm = { x = 0, y = 0, w = 12, h = 1 } })
134+
# },
135+
# {
136+
# insight_id = posthog_insight.pageviews.id
137+
# layouts_json = jsonencode({ sm = { x = 0, y = 1, w = 6, h = 5 } })
138+
# },
139+
# {
140+
# insight_id = posthog_insight.sessions.id
141+
# layouts_json = jsonencode({ sm = { x = 6, y = 1, w = 6, h = 5 } })
142+
# },
143+
# ]
144+
#
145+
# depends_on = [posthog_insight.pageviews, posthog_insight.sessions]
146+
# }
147+
148+
# =============================================================================
149+
# Step 6 (Optional): Add a footer text tile
150+
#
151+
# Adding a tile increases the count. Existing tiles keep their tile_ids.
152+
# =============================================================================
153+
154+
# Uncomment to use (comment out Steps 3-5 first):
155+
156+
# resource "posthog_dashboard_layout" "demo" {
157+
# dashboard_id = posthog_dashboard.demo.id
158+
#
159+
# tiles = [
160+
# {
161+
# text_body = "## Updated Production Metrics"
162+
# layouts_json = jsonencode({ sm = { x = 0, y = 0, w = 12, h = 1 } })
163+
# },
164+
# {
165+
# insight_id = posthog_insight.pageviews.id
166+
# layouts_json = jsonencode({ sm = { x = 0, y = 1, w = 6, h = 5 } })
167+
# },
168+
# {
169+
# insight_id = posthog_insight.sessions.id
170+
# layouts_json = jsonencode({ sm = { x = 6, y = 1, w = 6, h = 5 } })
171+
# },
172+
# {
173+
# text_body = "_Managed by Terraform_"
174+
# layouts_json = jsonencode({ sm = { x = 0, y = 6, w = 12, h = 1 } })
175+
# },
176+
# ]
177+
#
178+
# depends_on = [posthog_insight.pageviews, posthog_insight.sessions]
179+
# }
180+
181+
# =============================================================================
182+
# Step 7 (Optional): Remove an insight and reposition
183+
#
184+
# Removing a tile from the list clears its layout (insight tiles) or
185+
# soft-deletes it (text tiles). Remaining tiles can be repositioned freely.
186+
# =============================================================================
187+
188+
# Uncomment to use (comment out Steps 3-6 first):
189+
190+
# resource "posthog_dashboard_layout" "demo" {
191+
# dashboard_id = posthog_dashboard.demo.id
192+
#
193+
# tiles = [
194+
# {
195+
# text_body = "## Updated Production Metrics"
196+
# layouts_json = jsonencode({ sm = { x = 0, y = 0, w = 12, h = 1 } })
197+
# },
198+
# {
199+
# insight_id = posthog_insight.pageviews.id
200+
# layouts_json = jsonencode({ sm = { x = 0, y = 1, w = 12, h = 5 } })
201+
# },
202+
# ]
203+
#
204+
# depends_on = [posthog_insight.pageviews]
205+
# }
206+
207+
# =============================================================================
208+
# Outputs
209+
# =============================================================================
210+
211+
output "dashboard" {
212+
description = "Created dashboard details"
213+
value = {
214+
id = posthog_dashboard.demo.id
215+
name = posthog_dashboard.demo.name
216+
}
217+
}
218+
219+
output "layout" {
220+
description = "Dashboard layout ID"
221+
value = {
222+
id = posthog_dashboard_layout.demo.id
223+
dashboard_id = posthog_dashboard_layout.demo.dashboard_id
224+
tile_count = length(posthog_dashboard_layout.demo.tiles)
225+
}
226+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Import an existing dashboard layout using project_id/dashboard_id
2+
terraform import posthog_dashboard_layout.engineering 12345/67890

0 commit comments

Comments
 (0)