Class: Seo::CloudflareApiClient

Inherits:
Object
  • Object
show all
Defined in:
app/services/seo/cloudflare_api_client.rb

Overview

Client for the Cloudflare GraphQL Analytics API.

Queries the httpRequestsAdaptiveGroups dataset for per-path request counts.
Unlike the JavaScript-based Visit tracking, this captures every request at
the edge — bots, cache hits, and clients with JS disabled or blocked — so it
is a ground-truth traffic measure.

Cloudflare caps adaptive-analytics groups per query, so #path_request_counts
returns the top MAX_LIMIT paths by request volume for the date range; the
low-traffic long tail is omitted.

Auth reuses the Cloudflare API token from credentials. The token must carry
the Analytics: Read permission; a purge-only token will fail and the
client raises AuthError so callers can degrade gracefully.

Examples:

client = Seo::CloudflareApiClient.new
rows = client.path_request_counts(start_date: Date.yesterday, end_date: Date.yesterday)
# => [{ path: '/en-US/products/floor-heating', requests: 4120 }, ...]

Defined Under Namespace

Classes: ApiError, AuthError, TruncatedResponseError

Constant Summary collapse

GRAPHQL_URL =

GraphQL analytics endpoint.

'https://api.cloudflare.com/client/v4/graphql'
MAX_LIMIT =

Max groups Cloudflare returns per adaptive-groups query.

10_000
MIN_LIMIT =

Floor for the limit-backoff: once a query truncates even at this size,
the client gives up rather than shrinking the result set any further.

1_000
MAX_ATTEMPTS =

HTTP attempts per query before falling back to a smaller limit.

3

Instance Method Summary collapse

Constructor Details

#initialize(environment: Rails.env.to_sym) ⇒ CloudflareApiClient

Returns a new instance of CloudflareApiClient.

Parameters:

  • environment (Symbol) (defaults to: Rails.env.to_sym)

    which zone to query (defaults to current env);
    only :production carries real site traffic.

Raises:

  • (ArgumentError)


51
52
53
54
# File 'app/services/seo/cloudflare_api_client.rb', line 51

def initialize(environment: Rails.env.to_sym)
  @zone_tag = Cache::EdgeCacheUtility.instance.zone_id(environment)
  raise ArgumentError, "Unknown Cloudflare environment: #{environment}" unless @zone_tag
end

Instance Method Details

#path_request_counts(start_date:, end_date:, limit: MAX_LIMIT) ⇒ Array<Hash>

Note:

If Cloudflare keeps returning a truncated body even after retries,
+limit+ is progressively halved (down to MIN_LIMIT) until the response
parses — trading long-tail coverage for a usable result on busy days.

Request counts grouped by request path for a date range — the top paths by
request volume, capped at +limit+.

Parameters:

  • start_date (Date)

    inclusive start

  • end_date (Date)

    inclusive end

  • limit (Integer) (defaults to: MAX_LIMIT)

    max paths to return (Cloudflare caps this at MAX_LIMIT)

Returns:

  • (Array<Hash>)

    [{ path: String, requests: Integer }, ...]



66
67
68
69
70
71
72
73
74
75
76
# File 'app/services/seo/cloudflare_api_client.rb', line 66

def path_request_counts(start_date:, end_date:, limit: MAX_LIMIT)
  data = fetch_groups(start_date:, end_date:, limit: [limit, MAX_LIMIT].min)

  groups = data.dig('viewer', 'zones', 0, 'httpRequestsAdaptiveGroups') || []
  groups.map do |group|
    {
      path: group.dig('dimensions', 'clientRequestPath'),
      requests: group['count'].to_i
    }
  end
end