Skip to content

Commit

Permalink
Merge pull request #1401 from broswen/bswenson/observatory
Browse files Browse the repository at this point in the history
add observatory support
  • Loading branch information
jacobbednarz committed Sep 15, 2023
2 parents 8bfcfe5 + 7f7802c commit 1f9672e
Show file tree
Hide file tree
Showing 3 changed files with 843 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/1401.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
observatory: add support for observatory API
```
388 changes: 388 additions & 0 deletions observatory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,388 @@
package cloudflare

import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"time"

"github.com/goccy/go-json"
)

var (
ErrMissingObservatoryUrl = errors.New("missing required page url")
ErrMissingObservatoryTestID = errors.New("missing required test id")
)

// ObservatoryPage describes all the tests for a web page.
type ObservatoryPage struct {
URL string `json:"url"`
Region labeledRegion `json:"region"`
ScheduleFrequency string `json:"scheduleFrequency"`
Tests []ObservatoryPageTest `json:"tests"`
}

// ObservatoryPageTest describes a single test for a web page.
type ObservatoryPageTest struct {
ID string `json:"id"`
Date *time.Time `json:"date"`
URL string `json:"url"`
Region labeledRegion `json:"region"`
ScheduleFrequency *string `json:"scheduleFrequency"`
MobileReport ObservatoryLighthouseReport `json:"mobileReport"`
DesktopReport ObservatoryLighthouseReport `json:"desktopReport"`
}

// labeledRegion describes a region the test was run in.
type labeledRegion struct {
Value string `json:"value"`
Label string `json:"label"`
}

// ObservatorySchedule describe a test schedule.
type ObservatorySchedule struct {
URL string `json:"url"`
Region string `json:"region"`
Frequency string `json:"frequency"`
}

// ObservatoryLighthouseReport describes the web vital metrics result.
type ObservatoryLighthouseReport struct {
PerformanceScore int `json:"performanceScore"`
State string `json:"state"`
DeviceType string `json:"deviceType"`
// TTFB is time to first byte
TTFB int `json:"ttfb"`
// FCP is first contentful paint
FCP int `json:"fcp"`
// LCP is largest contentful pain
LCP int `json:"lcp"`
// TTI is time to interactive
TTI int `json:"tti"`
// TBT is total blocking time
TBT int `json:"tbt"`
// SI is speed index
SI int `json:"si"`
// CLS is cumulative layout shift
CLS float64 `json:"cls"`
Error *lighthouseError `json:"error,omitempty"`
}

// lighthouseError describes the test error.
type lighthouseError struct {
Code string `json:"code"`
Detail string `json:"detail"`
FinalDisplayedURL string `json:"finalDisplayedUrl"`
}

// ObservatoryPageTrend describes the web vital metrics trend.
type ObservatoryPageTrend struct {
PerformanceScore []*int `json:"performanceScore"`
TTFB []*int `json:"ttfb"`
FCP []*int `json:"fcp"`
LCP []*int `json:"lcp"`
TTI []*int `json:"tti"`
TBT []*int `json:"tbt"`
SI []*int `json:"si"`
CLS []*float64 `json:"cls"`
}

type ListObservatoryPagesParams struct {
}

// ObservatoryPagesResponse is the API response, containing a list of ObservatoryPage.
type ObservatoryPagesResponse struct {
Response
Result []ObservatoryPage `json:"result"`
}

// ListObservatoryPages returns a list of pages which have been tested.
//
// API reference: https://api.cloudflare.com/#speed-list-pages
func (api *API) ListObservatoryPages(ctx context.Context, rc *ResourceContainer, params ListObservatoryPagesParams) ([]ObservatoryPage, error) {
uri := fmt.Sprintf("/zones/%s/speed_api/pages", rc.Identifier)
res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return nil, err
}
var r ObservatoryPagesResponse
err = json.Unmarshal(res, &r)
if err != nil {
return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
}
return r.Result, nil
}

type GetObservatoryPageTrendParams struct {
URL string `url:"-"`
Region string `url:"region"`
DeviceType string `url:"deviceType"`
Start *time.Time `url:"start"`
End *time.Time `url:"end,omitempty"`
Timezone string `url:"tz"`
Metrics []string `url:"metrics"`
}

type ObservatoryPageTrendResponse struct {
Response
Result ObservatoryPageTrend `json:"result"`
}

// GetObservatoryPageTrend returns a the trend of web vital metrics for a page in a specific region.
//
// API reference: https://api.cloudflare.com/#speed-list-page-trend
func (api *API) GetObservatoryPageTrend(ctx context.Context, rc *ResourceContainer, params GetObservatoryPageTrendParams) (*ObservatoryPageTrend, error) {
if params.URL == "" {
return nil, ErrMissingObservatoryUrl
}
uri := buildURI(fmt.Sprintf("/zones/%s/speed_api/pages/%s/trend", rc.Identifier, url.PathEscape(params.URL)), params)
res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return nil, err
}
var r ObservatoryPageTrendResponse
err = json.Unmarshal(res, &r)
if err != nil {
return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
}
return &r.Result, nil
}

var listObservatoryPageTestDefaultPageSize = 20

type ListObservatoryPageTestParams struct {
URL string `url:"-"`
Region string `url:"region"`
ResultInfo
}

type ObservatoryPageTestsResponse struct {
Response
Result []ObservatoryPageTest `json:"result"`
ResultInfo `json:"result_info"`
}

// ListObservatoryPageTests returns a list of tests for a page in a specific region.
//
// API reference: https://api.cloudflare.com/#speed-list-test-history
func (api *API) ListObservatoryPageTests(ctx context.Context, rc *ResourceContainer, params ListObservatoryPageTestParams) ([]ObservatoryPageTest, *ResultInfo, error) {
if params.URL == "" {
return nil, nil, ErrMissingObservatoryUrl
}
autoPaginate := true
if params.PerPage >= 1 || params.Page >= 1 {
autoPaginate = false
}
if params.PerPage < 1 {
params.PerPage = listObservatoryPageTestDefaultPageSize
}
if params.Page < 1 {
params.Page = 1
}
var tests []ObservatoryPageTest
var lastResultInfo ResultInfo
for {
uri := buildURI(fmt.Sprintf("/zones/%s/speed_api/pages/%s/tests", rc.Identifier, url.PathEscape(params.URL)), params)
res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return nil, nil, err
}
var r ObservatoryPageTestsResponse
err = json.Unmarshal(res, &r)
if err != nil {
return nil, nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
}
tests = append(tests, r.Result...)
lastResultInfo = r.ResultInfo.Next()
if params.ResultInfo.Done() || !autoPaginate {
break
}
}
return tests, &lastResultInfo, nil
}

type CreateObservatoryPageTestParams struct {
URL string
Settings CreateObservatoryPageTestSettings
}
type CreateObservatoryPageTestSettings struct {
Region string `json:"region"`
}

type ObservatoryPageTestResponse struct {
Response
Result ObservatoryPageTest `json:"result"`
}

// CreateObservatoryPageTest starts a test for a page in a specific region.
//
// API reference: https://api.cloudflare.com/#speed-create-test
func (api *API) CreateObservatoryPageTest(ctx context.Context, rc *ResourceContainer, params CreateObservatoryPageTestParams) (*ObservatoryPageTest, error) {
if params.URL == "" {
return nil, ErrMissingObservatoryUrl
}
uri := fmt.Sprintf("/zones/%s/speed_api/pages/%s/tests", rc.Identifier, url.PathEscape(params.URL))
res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params.Settings)
if err != nil {
return nil, err
}
var r ObservatoryPageTestResponse
err = json.Unmarshal(res, &r)
if err != nil {
return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
}
return &r.Result, nil
}

type DeleteObservatoryPageTestsParams struct {
URL string `url:"-"`
Region string `url:"region"`
}

type ObservatoryCountResponse struct {
Response
Result struct {
Count int `json:"count"`
} `json:"result"`
}

// DeleteObservatoryPageTests deletes all tests for a page in a specific region.
//
// API reference: https://api.cloudflare.com/#speed-delete-tests
func (api *API) DeleteObservatoryPageTests(ctx context.Context, rc *ResourceContainer, params DeleteObservatoryPageTestsParams) (*int, error) {
if params.URL == "" {
return nil, ErrMissingObservatoryUrl
}
uri := buildURI(fmt.Sprintf("/zones/%s/speed_api/pages/%s/tests", rc.Identifier, url.PathEscape(params.URL)), params)
res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
if err != nil {
return nil, err
}
var r ObservatoryCountResponse
err = json.Unmarshal(res, &r)
if err != nil {
return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
}
return &r.Result.Count, nil
}

type GetObservatoryPageTestParams struct {
URL string
TestID string
}

// GetObservatoryPageTest returns a specific test for a page.
//
// API reference: https://api.cloudflare.com/#speed-get-test
func (api *API) GetObservatoryPageTest(ctx context.Context, rc *ResourceContainer, params GetObservatoryPageTestParams) (*ObservatoryPageTest, error) {
if params.URL == "" {
return nil, ErrMissingObservatoryUrl
}
if params.TestID == "" {
return nil, ErrMissingObservatoryTestID
}
uri := fmt.Sprintf("/zones/%s/speed_api/pages/%s/tests/%s", rc.Identifier, url.PathEscape(params.URL), params.TestID)
res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return nil, err
}
var r ObservatoryPageTestResponse
err = json.Unmarshal(res, &r)
if err != nil {
return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
}
return &r.Result, nil
}

type CreateObservatoryScheduledPageTestParams struct {
URL string `url:"-" json:"-"`
Region string `url:"region" json:"region"`
Frequency string `url:"frequency" json:"-"`
}

type ObservatoryScheduledPageTest struct {
Schedule ObservatorySchedule `json:"schedule"`
Test ObservatoryPageTest `json:"test"`
}

type CreateObservatoryScheduledPageTestResponse struct {
Response
Result ObservatoryScheduledPageTest `json:"result"`
}

// CreateObservatoryScheduledPageTest creates a scheduled test for a page in a specific region.
//
// API reference: https://api.cloudflare.com/#speed-create-scheduled-test
func (api *API) CreateObservatoryScheduledPageTest(ctx context.Context, rc *ResourceContainer, params CreateObservatoryScheduledPageTestParams) (*ObservatoryScheduledPageTest, error) {
if params.URL == "" {
return nil, ErrMissingObservatoryUrl
}
uri := buildURI(fmt.Sprintf("/zones/%s/speed_api/schedule/%s", rc.Identifier, url.PathEscape(params.URL)), params)
res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params)
if err != nil {
return nil, err
}
var r CreateObservatoryScheduledPageTestResponse
err = json.Unmarshal(res, &r)
if err != nil {
return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
}
return &r.Result, nil
}

type GetObservatoryScheduledPageTestParams struct {
URL string `url:"-"`
Region string `url:"region"`
}

type ObservatoryScheduleResponse struct {
Response
Result ObservatorySchedule `json:"result"`
}

// GetObservatoryScheduledPageTest returns the test schedule for a page in a specific region.
//
// API reference: https://api.cloudflare.com/#speed-get-scheduled-test
func (api *API) GetObservatoryScheduledPageTest(ctx context.Context, rc *ResourceContainer, params GetObservatoryScheduledPageTestParams) (*ObservatorySchedule, error) {
if params.URL == "" {
return nil, ErrMissingObservatoryUrl
}
uri := buildURI(fmt.Sprintf("/zones/%s/speed_api/schedule/%s", rc.Identifier, url.PathEscape(params.URL)), params)
res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return nil, err
}
var r ObservatoryScheduleResponse
err = json.Unmarshal(res, &r)
if err != nil {
return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
}
return &r.Result, nil
}

type DeleteObservatoryScheduledPageTestParams struct {
URL string `url:"-"`
Region string `url:"region"`
}

// DeleteObservatoryScheduledPageTest deletes the test schedule for a page in a specific region.
//
// API reference: https://api.cloudflare.com/#speed-delete-scheduled-test
func (api *API) DeleteObservatoryScheduledPageTest(ctx context.Context, rc *ResourceContainer, params DeleteObservatoryScheduledPageTestParams) (*int, error) {
if params.URL == "" {
return nil, ErrMissingObservatoryUrl
}
uri := buildURI(fmt.Sprintf("/zones/%s/speed_api/schedule/%s", rc.Identifier, url.PathEscape(params.URL)), params)
res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
if err != nil {
return nil, err
}
var r ObservatoryCountResponse
err = json.Unmarshal(res, &r)
if err != nil {
return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
}
return &r.Result.Count, nil
}

0 comments on commit 1f9672e

Please sign in to comment.