Skip to content

Commit

Permalink
[U-3554]: allow changing navigation links (#126)
Browse files Browse the repository at this point in the history
  • Loading branch information
luki215 authored Dec 3, 2024
1 parent 3fbe5d4 commit a061422
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 31 deletions.
9 changes: 9 additions & 0 deletions docs/resources/betteruptime_status_page.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ https://betterstack.com/docs/uptime/api/status-pages/
- **layout** (String) Choose usual vertical layout or space-saving horizontal layout. Only applicable when design: v2. Possible values: 'vertical', 'horizontal'.
- **logo_url** (String) A direct link to your company's logo. The image should be under 20MB in size.
- **min_incident_length** (Number) If you don't want to display short incidents on your status page, this attribute is for you.
- **navigation_links** (Block List) Adjust the navigation links on your status page. Only applicable when design: v2. Only first 4 links considered. (see [below for nested schema](#nestedblock--navigation_links))
- **password** (String, Sensitive) Set a password of your status page (we won't store it as plaintext, promise). Required when password_enabled: true. We will set password_enabled: false automatically when you send us an empty password.
- **password_enabled** (Boolean) Do you want to enable password protection on your status page?
- **status_page_group_id** (Number) Set this attribute if you want to add this status page to a status page group.
Expand All @@ -53,4 +54,12 @@ https://betterstack.com/docs/uptime/api/status-pages/
- **id** (String) The ID of this Status Page.
- **updated_at** (String) The time when this status page was updated.

<a id="nestedblock--navigation_links"></a>
### Nested Schema for `navigation_links`

Required:

- **href** (String) Href of the link. Use full URL for external links. Use `/`, `/maintenance` and `/incidents` for built-in links.
- **text** (String) Label of the link.


159 changes: 128 additions & 31 deletions internal/provider/resource_status_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,25 @@ var statusPageSchema = map[string]*schema.Schema{
Optional: true,
Computed: true,
},
"navigation_links": {
Description: "Adjust the navigation links on your status page. Only applicable when design: v2. Only first 4 links considered.",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"text": {
Description: "Label of the link.",
Type: schema.TypeString,
Required: true,
},
"href": {
Description: "Href of the link. Use full URL for external links. Use `/`, `/maintenance` and `/incidents` for built-in links.",
Type: schema.TypeString,
Required: true,
},
},
},
},
}

func newStatusPageResource() *schema.Resource {
Expand All @@ -206,35 +225,41 @@ func newStatusPageResource() *schema.Resource {
}
}

type navigationLink struct {
Text *string `json:"text,omitempty"`
Href *string `json:"href,omitempty"`
}

type statusPage struct {
History *int `json:"history,omitempty"`
CompanyName *string `json:"company_name,omitempty"`
CompanyURL *string `json:"company_url,omitempty"`
ContactURL *string `json:"contact_url,omitempty"`
LogoURL *string `json:"logo_remote_url,omitempty"`
Timezone *string `json:"timezone,omitempty"`
Subdomain *string `json:"subdomain,omitempty"`
CustomDomain *string `json:"custom_domain,omitempty"`
MinIncidentLength *int `json:"min_incident_length,omitempty"`
Subscribable *bool `json:"subscribable,omitempty"`
HideFromSearchEngines *bool `json:"hide_from_search_engines,omitempty"`
CustomCSS *string `json:"custom_css,omitempty"`
CustomJavaScript *string `json:"custom_javascript,omitempty"`
GoogleAnalyticsID *string `json:"google_analytics_id,omitempty"`
Announcement *string `json:"announcement,omitempty"`
AnnouncementEmbedVisible *bool `json:"announcement_embed_visible,omitempty"`
AnnouncementEmbedLink *string `json:"announcement_embed_link,omitempty"`
AnnouncementEmbedCSS *string `json:"announcement_embed_css,omitempty"`
PasswordEnabled *bool `json:"password_enabled,omitempty"`
Password *string `json:"password,omitempty"`
AggregateState *string `json:"aggregate_state,omitempty"`
CreatedAt *string `json:"created_at,omitempty"`
UpdatedAt *string `json:"updated_at,omitempty"`
Design *string `json:"design,omitempty"`
Theme *string `json:"theme,omitempty"`
Layout *string `json:"layout,omitempty"`
AutomaticReports *bool `json:"automatic_reports,omitempty"`
StatusPageGroupID *int `json:"status_page_group_id,omitempty"`
History *int `json:"history,omitempty"`
CompanyName *string `json:"company_name,omitempty"`
CompanyURL *string `json:"company_url,omitempty"`
ContactURL *string `json:"contact_url,omitempty"`
LogoURL *string `json:"logo_remote_url,omitempty"`
Timezone *string `json:"timezone,omitempty"`
Subdomain *string `json:"subdomain,omitempty"`
CustomDomain *string `json:"custom_domain,omitempty"`
MinIncidentLength *int `json:"min_incident_length,omitempty"`
Subscribable *bool `json:"subscribable,omitempty"`
HideFromSearchEngines *bool `json:"hide_from_search_engines,omitempty"`
CustomCSS *string `json:"custom_css,omitempty"`
CustomJavaScript *string `json:"custom_javascript,omitempty"`
GoogleAnalyticsID *string `json:"google_analytics_id,omitempty"`
Announcement *string `json:"announcement,omitempty"`
AnnouncementEmbedVisible *bool `json:"announcement_embed_visible,omitempty"`
AnnouncementEmbedLink *string `json:"announcement_embed_link,omitempty"`
AnnouncementEmbedCSS *string `json:"announcement_embed_css,omitempty"`
PasswordEnabled *bool `json:"password_enabled,omitempty"`
Password *string `json:"password,omitempty"`
AggregateState *string `json:"aggregate_state,omitempty"`
CreatedAt *string `json:"created_at,omitempty"`
UpdatedAt *string `json:"updated_at,omitempty"`
Design *string `json:"design,omitempty"`
Theme *string `json:"theme,omitempty"`
Layout *string `json:"layout,omitempty"`
AutomaticReports *bool `json:"automatic_reports,omitempty"`
StatusPageGroupID *int `json:"status_page_group_id,omitempty"`
NavigationLinks *[]navigationLink `json:"navigation_links,omitempty"`
}

type statusPageHTTPResponse struct {
Expand Down Expand Up @@ -281,12 +306,19 @@ func statusPageRef(in *statusPage) []struct {
{k: "layout", v: &in.Layout},
{k: "automatic_reports", v: &in.AutomaticReports},
{k: "status_page_group_id", v: &in.StatusPageGroupID},
{k: "navigation_links", v: &in.NavigationLinks},
}
}
func statusPageCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
var in statusPage
for _, e := range statusPageRef(&in) {
load(d, e.k, e.v)
if e.k == "navigation_links" {
if err := loadNavigationLinks(d, e.v.(**[]navigationLink)); err != nil {
return diag.FromErr(err)
}
} else {
load(d, e.k, e.v)
}
}
var out statusPageHTTPResponse
if err := resourceCreate(ctx, meta, "/api/v2/status-pages", &in, &out); err != nil {
Expand All @@ -309,6 +341,13 @@ func statusPageRead(ctx context.Context, d *schema.ResourceData, meta interface{
d.SetId("") // Force "create" on 404.
return nil
}

if out.Data.Attributes.NavigationLinks != nil {
if err := d.Set("navigation_links", flattenNavigationLinks(out.Data.Attributes.NavigationLinks)); err != nil {
return diag.FromErr(err)
}
}

return statusPageCopyAttrs(d, &out.Data.Attributes, nil)
}

Expand All @@ -318,6 +357,12 @@ func statusPageCopyAttrs(d *schema.ResourceData, in *statusPage, derr diag.Diagn
// Skip copying password as it's never returned from the API
continue
}
if e.k == "navigation_links" {
if err := d.Set("navigation_links", flattenNavigationLinks(in.NavigationLinks)); err != nil {
derr = append(derr, diag.FromErr(err)[0])
}
continue
}
if err := d.Set(e.k, reflect.Indirect(reflect.ValueOf(e.v)).Interface()); err != nil {
derr = append(derr, diag.FromErr(err)[0])
}
Expand All @@ -329,8 +374,14 @@ func statusPageUpdate(ctx context.Context, d *schema.ResourceData, meta interfac
var in statusPage
var out policyHTTPResponse
for _, e := range statusPageRef(&in) {
if d.HasChange(e.k) {
load(d, e.k, e.v)
if e.k == "navigation_links" {
if err := loadNavigationLinks(d, e.v.(**[]navigationLink)); err != nil {
return diag.FromErr(err)
}
} else {
if d.HasChange(e.k) {
load(d, e.k, e.v)
}
}
}
return resourceUpdate(ctx, meta, fmt.Sprintf("/api/v2/status-pages/%s", url.PathEscape(d.Id())), &in, &out)
Expand All @@ -339,3 +390,49 @@ func statusPageUpdate(ctx context.Context, d *schema.ResourceData, meta interfac
func statusPageDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
return resourceDelete(ctx, meta, fmt.Sprintf("/api/v2/status-pages/%s", url.PathEscape(d.Id())))
}

func loadNavigationLinks(d *schema.ResourceData, target **[]navigationLink) error {
v, ok := d.GetOk("navigation_links")
if !ok {
return nil
}

links := v.([]interface{})
result := make([]navigationLink, len(links))

for i, link := range links {
linkMap := link.(map[string]interface{})

text := linkMap["text"].(string)
href := linkMap["href"].(string)

result[i] = navigationLink{
Text: &text,
Href: &href,
}
}

*target = &result
return nil
}

func flattenNavigationLinks(links *[]navigationLink) []interface{} {
if links == nil {
return nil
}

result := make([]interface{}, len(*links))
for i, link := range *links {
m := make(map[string]interface{})

if link.Text != nil {
m["text"] = *link.Text
}
if link.Href != nil {
m["href"] = *link.Href
}

result[i] = m
}
return result
}
33 changes: 33 additions & 0 deletions internal/provider/resource_status_page_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ func TestResourceStatusPage(t *testing.T) {
subdomain = "%s"
password = "secret123"
automatic_reports = true
navigation_links {
text = "Example"
href = "https://example.com"
}
navigation_links {
text = "Status"
href = "/status"
}
}
`, subdomain),
Check: resource.ComposeTestCheckFunc(
Expand All @@ -45,6 +54,10 @@ func TestResourceStatusPage(t *testing.T) {
resource.TestCheckResourceAttr("betteruptime_status_page.this", "timezone", "UTC"),
resource.TestCheckResourceAttr("betteruptime_status_page.this", "password", "secret123"),
resource.TestCheckResourceAttr("betteruptime_status_page.this", "automatic_reports", "true"),
resource.TestCheckResourceAttr("betteruptime_status_page.this", "navigation_links.0.text", "Example"),
resource.TestCheckResourceAttr("betteruptime_status_page.this", "navigation_links.0.href", "https://example.com"),
resource.TestCheckResourceAttr("betteruptime_status_page.this", "navigation_links.1.text", "Status"),
resource.TestCheckResourceAttr("betteruptime_status_page.this", "navigation_links.1.href", "/status"),
),
},
// Step 2 - update.
Expand All @@ -60,13 +73,25 @@ func TestResourceStatusPage(t *testing.T) {
timezone = "America/Los_Angeles"
subdomain = "%s"
password = "secret1234"
navigation_links {
text = "Example2"
href = "https://example.com/test"
}
navigation_links {
text = "Status"
href = "/status"
}
}
`, subdomain),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("betteruptime_status_page.this", "id"),
resource.TestCheckResourceAttr("betteruptime_status_page.this", "subdomain", subdomain),
resource.TestCheckResourceAttr("betteruptime_status_page.this", "timezone", "America/Los_Angeles"),
resource.TestCheckResourceAttr("betteruptime_status_page.this", "password", "secret1234"),
resource.TestCheckResourceAttr("betteruptime_status_page.this", "navigation_links.0.text", "Example2"),
resource.TestCheckResourceAttr("betteruptime_status_page.this", "navigation_links.0.href", "https://example.com/test"),
resource.TestCheckResourceAttr("betteruptime_status_page.this", "navigation_links.1.text", "Status"),
resource.TestCheckResourceAttr("betteruptime_status_page.this", "navigation_links.1.href", "/status"),
),
},
// Step 3 - make no changes, check plan is empty.
Expand All @@ -82,6 +107,14 @@ func TestResourceStatusPage(t *testing.T) {
timezone = "America/Los_Angeles"
subdomain = "%s"
password = "secret1234"
navigation_links {
text = "Example2"
href = "https://example.com/test"
}
navigation_links {
text = "Status"
href = "/status"
}
}
`, subdomain),
PlanOnly: true,
Expand Down

0 comments on commit a061422

Please sign in to comment.