Merge pull request #878 from Freika/feature/tiles-usage-stats

Feature/tiles usage stats
This commit is contained in:
Evgenii Burmakin 2025-02-13 21:12:03 +01:00 committed by GitHub
commit 2fe142bdf1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 94 additions and 57 deletions

View file

@ -16,6 +16,7 @@ To set a custom tile URL, go to the user settings and set the `Maps` section to
- Safe settings for user with default values.
- In the user settings, you can now set a custom tile URL for the map. #429 #715
- In the user map settings, you can now see a chart of map tiles usage.
- If you have Prometheus exporter enabled, you can now see a `ruby_dawarich_map_tiles` metric in Prometheus, which shows the total number of map tiles loaded. Example:
```

View file

@ -2,7 +2,7 @@
class Api::V1::Maps::TileUsageController < ApiController
def create
Maps::TileUsage::Track.new(tile_usage_params[:count].to_i).call
Maps::TileUsage::Track.new(current_api_user.id, tile_usage_params[:count].to_i).call
head :ok
end

View file

@ -5,6 +5,13 @@ class Settings::MapsController < ApplicationController
def index
@maps = current_user.safe_settings.maps
@tile_usage = 7.days.ago.to_date.upto(Time.zone.today).map do |date|
[
date.to_s,
Rails.cache.read("dawarich_map_tiles_usage:#{current_user.id}:#{date}") || 0
]
end
end
def update

View file

@ -34,9 +34,6 @@ import { TileMonitor } from "../maps/tile_monitor";
export default class extends Controller {
static targets = ["container"];
static values = {
monitoringEnabled: Boolean
}
settingsButtonAdded = false;
layerControl = null;
@ -249,7 +246,7 @@ export default class extends Controller {
}
// Initialize tile monitor
this.tileMonitor = new TileMonitor(this.monitoringEnabledValue, this.apiKey);
this.tileMonitor = new TileMonitor(this.apiKey);
// Add tile load event handlers to each base layer
Object.entries(this.baseMaps()).forEach(([name, layer]) => {

View file

@ -1,15 +1,11 @@
export class TileMonitor {
constructor(monitoringEnabled, apiKey) {
this.monitoringEnabled = monitoringEnabled;
constructor(apiKey) {
this.apiKey = apiKey;
this.tileQueue = 0;
this.tileUpdateInterval = null;
}
startMonitoring() {
// Only start the interval if monitoring is enabled
if (!this.monitoringEnabled) return;
// Clear any existing interval
if (this.tileUpdateInterval) {
clearInterval(this.tileUpdateInterval);
@ -29,13 +25,11 @@ export class TileMonitor {
}
recordTileLoad() {
if (!this.monitoringEnabled) return;
this.tileQueue += 1;
}
sendTileUsage() {
// Don't send if monitoring is disabled or queue is empty
if (!this.monitoringEnabled || this.tileQueue === 0) return;
if (this.tileQueue === 0) return;
const currentCount = this.tileQueue;
console.log('Sending tile usage batch:', currentCount);

View file

@ -1,11 +1,23 @@
# frozen_string_literal: true
class Maps::TileUsage::Track
def initialize(count = 1)
def initialize(user_id, count = 1)
@user_id = user_id
@count = count
end
def call
report_to_prometheus
report_to_cache
rescue StandardError => e
Rails.logger.error("Failed to send tile usage metric: #{e.message}")
end
private
def report_to_prometheus
return unless DawarichSettings.prometheus_exporter_enabled?
metric_data = {
type: 'counter',
name: 'dawarich_map_tiles_usage',
@ -13,7 +25,12 @@ class Maps::TileUsage::Track
}
PrometheusExporter::Client.default.send_json(metric_data)
rescue StandardError => e
Rails.logger.error("Failed to send tile usage metric: #{e.message}")
end
def report_to_cache
today_key = "dawarich_map_tiles_usage:#{@user_id}:#{Time.zone.today}"
current_value = (Rails.cache.read(today_key) || 0).to_i
Rails.cache.write(today_key, current_value + @count, expires_in: 7.days)
end
end

View file

@ -96,7 +96,13 @@ class ReverseGeocoding::Places::FetchData
end
def reverse_geocoded_places
data = Geocoder.search([place.latitude, place.longitude], limit: 10, distance_sort: true, radius: 10)
data = Geocoder.search(
[place.latitude, place.longitude],
limit: 10,
distance_sort: true,
radius: 1,
units: DISTANCE_UNITS
)
data.reject do |place|
place.data['properties']['osm_value'].in?(IGNORED_OSM_VALUES) ||

View file

@ -53,7 +53,6 @@
data-coordinates="<%= @coordinates %>"
data-distance="<%= @distance %>"
data-points_number="<%= @points_number %>"
data-maps-monitoring-enabled-value="<%= DawarichSettings.prometheus_exporter_enabled? %>"
data-timezone="<%= Rails.configuration.time_zone %>">
<div data-maps-target="container" class="h-[25rem] rounded-lg w-full min-h-screen">
<div id="fog" class="fog"></div>

View file

@ -22,35 +22,46 @@
<span>Please remember, that using a custom tile URL may result in extra costs. Check your map tile provider's terms of service for more information.</span>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-5" data-controller="map-preview">
<%= form_for :maps,
url: settings_maps_path,
method: :patch,
autocomplete: "off",
data: { turbo_method: :patch, turbo: false },
class: "lg:col-span-1" do |f| %>
<div class="form-control my-2">
<%= f.label :name %>
<%= f.text_field :name, value: @maps['name'], placeholder: 'Example: OpenStreetMap', class: "input input-bordered" %>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-5" data-controller="map-preview">
<div class="flex flex-col gap-4">
<%= form_for :maps,
url: settings_maps_path,
method: :patch,
autocomplete: "off",
data: { turbo_method: :patch, turbo: false } do |f| %>
<div class="form-control my-2">
<%= f.label :name %>
<%= f.text_field :name, value: @maps['name'], placeholder: 'Example: OpenStreetMap', class: "input input-bordered" %>
</div>
<div class="form-control my-2">
<%= f.label :url, 'URL' %>
<%= f.text_field :url,
value: @maps['url'],
autocomplete: "off",
placeholder: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
class: "input input-bordered",
data: {
map_preview_target: "urlInput",
action: "input->map-preview#updatePreview"
} %>
</div>
<div class="form-control my-2">
<%= f.label :url, 'URL' %>
<%= f.text_field :url,
value: @maps['url'],
autocomplete: "off",
placeholder: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
class: "input input-bordered",
data: {
map_preview_target: "urlInput",
action: "input->map-preview#updatePreview"
} %>
</div>
<%= f.submit 'Save', class: "btn btn-primary", data: { map_preview_target: "saveButton" } %>
<% end %>
<%= f.submit 'Save', class: "btn btn-primary", data: { map_preview_target: "saveButton" } %>
<% end %>
<div class="lg:col-span-2" style="height: 500px;">
<h2 class="text-lg font-bold">Tile usage</h2>
<%= line_chart(
@tile_usage,
height: '200px',
xtitle: 'Days',
ytitle: 'Tiles',
suffix: ' tiles loaded'
) %>
</div>
<div style="height: 500px;">
<div
data-map-preview-target="mapContainer"
class="w-full h-full rounded-lg border"

View file

@ -1,7 +0,0 @@
# frozen_string_literal: true
module Reddis
def self.client
@client ||= Redis.new(url: ENV['REDIS_URL'])
end
end

View file

@ -6,21 +6,20 @@ RSpec.describe 'Api::V1::Maps::TileUsage', type: :request do
describe 'POST /api/v1/maps/tile_usage' do
let(:tile_count) { 5 }
let(:track_service) { instance_double(Maps::TileUsage::Track) }
let(:user) { create(:user) }
before do
allow(Maps::TileUsage::Track).to receive(:new).with(tile_count).and_return(track_service)
allow(Maps::TileUsage::Track).to receive(:new).with(user.id, tile_count).and_return(track_service)
allow(track_service).to receive(:call)
end
context 'when user is authenticated' do
let(:user) { create(:user) }
it 'tracks tile usage' do
post '/api/v1/maps/tile_usage',
params: { tile_usage: { count: tile_count } },
headers: { 'Authorization' => "Bearer #{user.api_key}" }
expect(Maps::TileUsage::Track).to have_received(:new).with(tile_count)
expect(Maps::TileUsage::Track).to have_received(:new).with(user.id, tile_count)
expect(track_service).to have_received(:call)
expect(response).to have_http_status(:ok)
end

View file

@ -5,16 +5,19 @@ require 'prometheus_exporter/client'
RSpec.describe Maps::TileUsage::Track do
describe '#call' do
subject(:track) { described_class.new(tile_count).call }
subject(:track) { described_class.new(user_id, tile_count).call }
let(:user_id) { 1 }
let(:tile_count) { 5 }
let(:prometheus_client) { instance_double(PrometheusExporter::Client) }
before do
allow(PrometheusExporter::Client).to receive(:default).and_return(prometheus_client)
allow(prometheus_client).to receive(:send_json)
allow(DawarichSettings).to receive(:prometheus_exporter_enabled?).and_return(true)
end
it 'tracks tile usage' do
it 'tracks tile usage in prometheus' do
expect(prometheus_client).to receive(:send_json).with(
{
type: 'counter',
@ -25,5 +28,15 @@ RSpec.describe Maps::TileUsage::Track do
track
end
it 'tracks tile usage in cache' do
expect(Rails.cache).to receive(:write).with(
"dawarich_map_tiles_usage:#{user_id}:#{Time.zone.today}",
tile_count,
expires_in: 7.days
)
track
end
end
end