1
1
import logging
2
2
from collections import defaultdict
3
3
from datetime import datetime , timedelta , timezone
4
- from typing import Any , Optional , TypeAlias , TypeVar , cast
4
+ from typing import Annotated , Any , Optional , TypeAlias , TypeVar , cast
5
5
6
6
from django .conf import settings
7
7
from django .db .models import QuerySet
10
10
from django .utils import translation
11
11
from django .utils .timezone import now as timezone_now
12
12
from django .utils .translation import gettext as _
13
+ from pydantic import BeforeValidator , Json , NonNegativeInt
13
14
14
15
from analytics .lib .counts import COUNT_STATS , CountStat
15
16
from analytics .lib .time_utils import time_range
30
31
)
31
32
from zerver .lib .exceptions import JsonableError
32
33
from zerver .lib .i18n import get_and_set_request_language , get_language_translation_data
33
- from zerver .lib .request import REQ , has_request_variables
34
34
from zerver .lib .response import json_success
35
35
from zerver .lib .streams import access_stream_by_id
36
36
from zerver .lib .timestamp import convert_to_UTC
37
- from zerver .lib .validator import to_non_negative_int
37
+ from zerver .lib .typed_endpoint import PathOnly , typed_endpoint
38
38
from zerver .models import Client , Realm , Stream , UserProfile
39
39
from zerver .models .realms import get_realm
40
40
@@ -111,8 +111,8 @@ def stats(request: HttpRequest) -> HttpResponse:
111
111
112
112
113
113
@require_server_admin
114
- @has_request_variables
115
- def stats_for_realm (request : HttpRequest , realm_str : str ) -> HttpResponse :
114
+ @typed_endpoint
115
+ def stats_for_realm (request : HttpRequest , * , realm_str : PathOnly [ str ] ) -> HttpResponse :
116
116
try :
117
117
realm = get_realm (realm_str )
118
118
except Realm .DoesNotExist :
@@ -127,9 +127,9 @@ def stats_for_realm(request: HttpRequest, realm_str: str) -> HttpResponse:
127
127
128
128
129
129
@require_server_admin
130
- @has_request_variables
130
+ @typed_endpoint
131
131
def stats_for_remote_realm (
132
- request : HttpRequest , remote_server_id : int , remote_realm_id : int
132
+ request : HttpRequest , * , remote_server_id : PathOnly [ int ] , remote_realm_id : PathOnly [ int ]
133
133
) -> HttpResponse :
134
134
assert settings .ZILENCER_ENABLED
135
135
server = RemoteZulipServer .objects .get (id = remote_server_id )
@@ -142,22 +142,45 @@ def stats_for_remote_realm(
142
142
143
143
144
144
@require_server_admin_api
145
- @has_request_variables
145
+ @typed_endpoint
146
146
def get_chart_data_for_realm (
147
- request : HttpRequest , / , user_profile : UserProfile , realm_str : str , ** kwargs : Any
147
+ request : HttpRequest ,
148
+ user_profile : UserProfile ,
149
+ / ,
150
+ * ,
151
+ realm_str : PathOnly [str ],
152
+ chart_name : str ,
153
+ min_length : Json [NonNegativeInt ] | None = None ,
154
+ start : Annotated [datetime | None , BeforeValidator (to_utc_datetime )] = None ,
155
+ end : Annotated [datetime | None , BeforeValidator (to_utc_datetime )] = None ,
148
156
) -> HttpResponse :
149
157
try :
150
158
realm = get_realm (realm_str )
151
159
except Realm .DoesNotExist :
152
160
raise JsonableError (_ ("Invalid organization" ))
153
161
154
- return get_chart_data (request , user_profile , realm = realm , ** kwargs )
162
+ return do_get_chart_data (
163
+ request ,
164
+ user_profile ,
165
+ realm = realm ,
166
+ chart_name = chart_name ,
167
+ min_length = min_length ,
168
+ start = start ,
169
+ end = end ,
170
+ )
155
171
156
172
157
173
@require_non_guest_user
158
- @has_request_variables
174
+ @typed_endpoint
159
175
def get_chart_data_for_stream (
160
- request : HttpRequest , / , user_profile : UserProfile , stream_id : int
176
+ request : HttpRequest ,
177
+ user_profile : UserProfile ,
178
+ * ,
179
+ stream_id : PathOnly [int ],
180
+ chart_name : str ,
181
+ min_length : Json [NonNegativeInt ] | None = None ,
182
+ start : Annotated [datetime | None , BeforeValidator (to_utc_datetime )] = None ,
183
+ end : Annotated [datetime | None , BeforeValidator (to_utc_datetime )] = None ,
161
184
) -> HttpResponse :
162
185
stream , ignored_sub = access_stream_by_id (
163
186
user_profile ,
@@ -166,28 +189,43 @@ def get_chart_data_for_stream(
166
189
allow_realm_admin = True ,
167
190
)
168
191
169
- return get_chart_data (request , user_profile , stream = stream )
192
+ return do_get_chart_data (
193
+ request ,
194
+ user_profile ,
195
+ stream = stream ,
196
+ chart_name = chart_name ,
197
+ min_length = min_length ,
198
+ start = start ,
199
+ end = end ,
200
+ )
170
201
171
202
172
203
@require_server_admin_api
173
- @has_request_variables
204
+ @typed_endpoint
174
205
def get_chart_data_for_remote_realm (
175
206
request : HttpRequest ,
176
- / ,
177
207
user_profile : UserProfile ,
178
- remote_server_id : int ,
179
- remote_realm_id : int ,
180
- ** kwargs : Any ,
208
+ / ,
209
+ * ,
210
+ remote_server_id : PathOnly [int ],
211
+ remote_realm_id : PathOnly [int ],
212
+ chart_name : str ,
213
+ min_length : Json [NonNegativeInt ] | None = None ,
214
+ start : Annotated [datetime | None , BeforeValidator (to_utc_datetime )] = None ,
215
+ end : Annotated [datetime | None , BeforeValidator (to_utc_datetime )] = None ,
181
216
) -> HttpResponse :
182
217
assert settings .ZILENCER_ENABLED
183
218
server = RemoteZulipServer .objects .get (id = remote_server_id )
184
- return get_chart_data (
219
+ return do_get_chart_data (
185
220
request ,
186
221
user_profile ,
187
222
server = server ,
188
223
remote = True ,
189
- remote_realm_id = int (remote_realm_id ),
190
- ** kwargs ,
224
+ remote_realm_id = remote_realm_id ,
225
+ chart_name = chart_name ,
226
+ min_length = min_length ,
227
+ start = start ,
228
+ end = end ,
191
229
)
192
230
193
231
@@ -210,47 +248,89 @@ def stats_for_remote_installation(request: HttpRequest, remote_server_id: int) -
210
248
211
249
212
250
@require_server_admin_api
213
- @has_request_variables
251
+ @typed_endpoint
214
252
def get_chart_data_for_installation (
215
- request : HttpRequest , / , user_profile : UserProfile , chart_name : str = REQ (), ** kwargs : Any
253
+ request : HttpRequest ,
254
+ user_profile : UserProfile ,
255
+ / ,
256
+ * ,
257
+ chart_name : str ,
258
+ min_length : Json [NonNegativeInt ] | None = None ,
259
+ start : Annotated [datetime | None , BeforeValidator (to_utc_datetime )] = None ,
260
+ end : Annotated [datetime | None , BeforeValidator (to_utc_datetime )] = None ,
216
261
) -> HttpResponse :
217
- return get_chart_data (request , user_profile , for_installation = True , ** kwargs )
262
+ return do_get_chart_data (
263
+ request ,
264
+ user_profile ,
265
+ for_installation = True ,
266
+ chart_name = chart_name ,
267
+ min_length = min_length ,
268
+ start = start ,
269
+ end = end ,
270
+ )
218
271
219
272
220
273
@require_server_admin_api
221
- @has_request_variables
274
+ @typed_endpoint
222
275
def get_chart_data_for_remote_installation (
223
276
request : HttpRequest ,
224
- / ,
225
277
user_profile : UserProfile ,
226
- remote_server_id : int ,
227
- chart_name : str = REQ (),
228
- ** kwargs : Any ,
278
+ / ,
279
+ * ,
280
+ remote_server_id : PathOnly [int ],
281
+ chart_name : str ,
282
+ min_length : Json [NonNegativeInt ] | None = None ,
283
+ start : Annotated [datetime | None , BeforeValidator (to_utc_datetime )] = None ,
284
+ end : Annotated [datetime | None , BeforeValidator (to_utc_datetime )] = None ,
229
285
) -> HttpResponse :
230
286
assert settings .ZILENCER_ENABLED
231
287
server = RemoteZulipServer .objects .get (id = remote_server_id )
232
- return get_chart_data (
288
+ return do_get_chart_data (
233
289
request ,
234
290
user_profile ,
235
291
for_installation = True ,
236
292
remote = True ,
237
293
server = server ,
238
- ** kwargs ,
294
+ chart_name = chart_name ,
295
+ min_length = min_length ,
296
+ start = start ,
297
+ end = end ,
239
298
)
240
299
241
300
242
301
@require_non_guest_user
243
- @has_request_variables
302
+ @typed_endpoint
244
303
def get_chart_data (
245
304
request : HttpRequest ,
246
305
user_profile : UserProfile ,
247
- chart_name : str = REQ (),
248
- min_length : int | None = REQ (converter = to_non_negative_int , default = None ),
249
- start : datetime | None = REQ (converter = to_utc_datetime , default = None ),
250
- end : datetime | None = REQ (converter = to_utc_datetime , default = None ),
251
- # These last several parameters are only used by functions
252
- # wrapping get_chart_data; the callers are responsible for
253
- # parsing/validation/authorization for them.
306
+ * ,
307
+ chart_name : str ,
308
+ min_length : Json [NonNegativeInt ] | None = None ,
309
+ start : Annotated [datetime | None , BeforeValidator (to_utc_datetime )] = None ,
310
+ end : Annotated [datetime | None , BeforeValidator (to_utc_datetime )] = None ,
311
+ ) -> HttpResponse :
312
+ return do_get_chart_data (
313
+ request ,
314
+ user_profile ,
315
+ chart_name = chart_name ,
316
+ min_length = min_length ,
317
+ start = start ,
318
+ end = end ,
319
+ )
320
+
321
+
322
+ @require_non_guest_user
323
+ def do_get_chart_data (
324
+ request : HttpRequest ,
325
+ user_profile : UserProfile ,
326
+ * ,
327
+ # Common parameters supported by all stats endpoints.
328
+ chart_name : str ,
329
+ min_length : NonNegativeInt | None = None ,
330
+ start : datetime | None = None ,
331
+ end : datetime | None = None ,
332
+ # The following parameters are only used by wrapping functions for
333
+ # various contexts; the callers are responsible for validating them.
254
334
realm : Realm | None = None ,
255
335
for_installation : bool = False ,
256
336
remote : bool = False ,
0 commit comments