zilencer: Make BaseRemoteCount.remote_id field nullable.

This commit is contained in:
Mateusz Mandera 2023-10-23 22:29:42 +02:00 committed by Tim Abbott
parent c4fbb6319b
commit 2ecd7abc0d
4 changed files with 61 additions and 2 deletions

View File

@ -74,6 +74,8 @@ from zerver.models import (
get_user,
is_cross_realm_bot_email,
)
from zilencer.models import RemoteInstallationCount, RemoteZulipServer
from zilencer.views import get_last_id_from_server
class AnalyticsTestCase(ZulipTestCase):
@ -1852,3 +1854,26 @@ class TestRealmActiveHumans(AnalyticsTestCase):
1,
)
self.assertEqual(RealmCount.objects.filter(property="realm_active_humans::day").count(), 1)
class GetLastIdFromServerTest(ZulipTestCase):
def test_get_last_id_from_server_ignores_null(self) -> None:
"""
Verifies that get_last_id_from_server ignores null remote_ids, since this goes
against the default Postgres ordering behavior, which treats nulls as the largest value.
"""
self.server_uuid = "6cde5f7a-1f7e-4978-9716-49f69ebfc9fe"
self.server = RemoteZulipServer.objects.create(
uuid=self.server_uuid,
api_key="magic_secret_api_key",
hostname="demo.example.com",
last_updated=timezone_now(),
)
first = RemoteInstallationCount.objects.create(
end_time=timezone_now(), server=self.server, property="test", value=1, remote_id=1
)
RemoteInstallationCount.objects.create(
end_time=timezone_now(), server=self.server, property="test2", value=1, remote_id=None
)
result = get_last_id_from_server(self.server, RemoteInstallationCount)
self.assertEqual(result, first.remote_id)

View File

@ -0,0 +1,22 @@
# Generated by Django 4.2.6 on 2023-10-23 20:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("zilencer", "0030_alter_remoteinstallationcount_remote_id"),
]
operations = [
migrations.AlterField(
model_name="remoteinstallationcount",
name="remote_id",
field=models.IntegerField(null=True),
),
migrations.AlterField(
model_name="remoterealmcount",
name="remote_id",
field=models.IntegerField(null=True),
),
]

View File

@ -135,7 +135,7 @@ class BaseRemoteCount(BaseCount):
# The remote_id field is the id value of the corresponding *Count object
# on the remote server.
# It lets us deduplicate data from the remote server.
remote_id = models.IntegerField()
remote_id = models.IntegerField(null=True)
class Meta:
abstract = True

View File

@ -444,6 +444,10 @@ def validate_incoming_table_data(
for row in rows:
if is_count_stat and row["property"] not in COUNT_STATS:
raise JsonableError(_("Invalid property {property}").format(property=row["property"]))
if row.get("id") is None:
# This shouldn't be possible, as submitting data like this should be
# prevented by our param validators.
raise AssertionError(f"Missing id field in row {row}")
if row["id"] <= last_id:
raise JsonableError(_("Data is out of order."))
last_id = row["id"]
@ -592,7 +596,15 @@ def remote_server_post_analytics(
def get_last_id_from_server(server: RemoteZulipServer, model: Any) -> int:
last_count = model.objects.filter(server=server).order_by("remote_id").only("remote_id").last()
last_count = (
model.objects.filter(server=server)
# Rows with remote_id=None are managed by the bouncer service itself,
# and thus aren't meant for syncing and should be ignored here.
.exclude(remote_id=None)
.order_by("remote_id")
.only("remote_id")
.last()
)
if last_count is not None:
return last_count.remote_id
return 0