Class: Seo::VisitPageDailyCountsQuery

Inherits:
Object
  • Object
show all
Defined in:
app/queries/seo/visit_page_daily_counts_query.rb

Overview

Builds and reads the bounded daily visit aggregate used by the SEO page
metrics sync. Raw SQL is intentional here: path normalization and a grouped
INSERT are database work, and expressing them as per-record Ruby objects is
the allocation/query pattern this class replaces.

Defined Under Namespace

Classes: RefreshAlreadyRunning

Constant Summary collapse

BACKFILL_DAYS =
90
RECHECK_DAYS =
2
BUSINESS_TIME_ZONE =
'America/Chicago'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(period_end: Date.current) ⇒ VisitPageDailyCountsQuery

Returns a new instance of VisitPageDailyCountsQuery.



17
18
19
# File 'app/queries/seo/visit_page_daily_counts_query.rb', line 17

def initialize(period_end: Date.current)
  @period_end = period_end.to_date
end

Instance Attribute Details

#period_endObject (readonly)

Returns the value of attribute period_end.



15
16
17
# File 'app/queries/seo/visit_page_daily_counts_query.rb', line 15

def period_end
  @period_end
end

Instance Method Details

#coverage_complete?Boolean

Returns whether every day needed by the 90-day window has a
durable completion marker.

Returns:

  • (Boolean)

    whether every day needed by the 90-day window has a
    durable completion marker



35
36
37
# File 'app/queries/seo/visit_page_daily_counts_query.rb', line 35

def coverage_complete?
  VisitPageDailyRefresh.where(visit_date: coverage_range).count == BACKFILL_DAYS
end

#refresh_date!(visit_date) ⇒ Hash

Rebuild one date in a transaction. The non-blocking advisory lock keeps a
duplicate job from interleaving its delete/insert with the active owner;
raising makes Sidekiq retry instead of falsely completing its batch slot.

Parameters:

  • visit_date (Date, String)

Returns:

  • (Hash)

    source/page counts for instrumentation

Raises:



45
46
47
48
49
50
51
52
53
54
55
56
# File 'app/queries/seo/visit_page_daily_counts_query.rb', line 45

def refresh_date!(visit_date)
  visit_date = visit_date.to_date
  lock = VisitPageDailyCount.with_advisory_lock_result(
    "seo|visit_page_daily_counts|#{visit_date}", timeout_seconds: 0
  ) do
    rebuild_date!(visit_date)
  end

  raise RefreshAlreadyRunning, "Visit aggregate refresh already running for #{visit_date}" unless lock.lock_was_acquired?

  lock.result
end

#refresh_datesArray<Date>

Dates needed for a complete rolling window. Missing dates are rebuilt
first; the latest two dates are then recomputed for late-arriving visits.

Returns:

  • (Array<Date>)


25
26
27
28
29
30
31
# File 'app/queries/seo/visit_page_daily_counts_query.rb', line 25

def refresh_dates
  required_dates = coverage_range.to_a
  completed_dates = VisitPageDailyRefresh.where(visit_date: coverage_range).pluck(:visit_date).to_set
  missing_dates = required_dates.reject { completed_dates.include?(it) }

  (missing_dates + required_dates.last(RECHECK_DAYS)).uniq.sort
end

#totals(paths:) ⇒ Hash{String => Hash}

Return rolling counts for the supplied canonical SiteMap paths.

Parameters:

  • paths (Array<String>)

Returns:

  • (Hash{String => Hash})


62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'app/queries/seo/visit_page_daily_counts_query.rb', line 62

def totals(paths:)
  paths = paths.compact_blank.uniq
  return {} if paths.empty?

  thirty_day_start = period_end - 29.days
  quoted_start = VisitPageDailyCount.lease_connection.quote(thirty_day_start)
  rows = VisitPageDailyCount
         .where(path: paths, visit_date: coverage_range)
         .group(:path)
         .pluck(
           :path,
           Arel.sql("SUM(visits_count) FILTER (WHERE visit_date >= #{quoted_start})"),
           Arel.sql('SUM(visits_count)')
         )

  rows.to_h do |path, visits_30d, visits_90d|
    [path, { visits_30d: visits_30d.to_i, visits_90d: visits_90d.to_i }]
  end
end