qontract-reconcile 0.10.2.dev129__py3-none-any.whl → 0.10.2.dev130__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qontract-reconcile
3
- Version: 0.10.2.dev129
3
+ Version: 0.10.2.dev130
4
4
  Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
5
5
  Project-URL: homepage, https://github.com/app-sre/qontract-reconcile
6
6
  Project-URL: repository, https://github.com/app-sre/qontract-reconcile
@@ -220,14 +220,14 @@ reconcile/fleet_labeler/validate.py,sha256=Ch4fe7jXQZKl4pnvl5IxWS-dKSIuuiwdH2B7m
220
220
  reconcile/fleet_labeler/vcs.py,sha256=6UHUQ08AGAHXF7629I6X-T_E1pvx96LxjS66EeOzve4,1108
221
221
  reconcile/glitchtip/README.md,sha256=rfXT6jNP9khJW65jL7I2PgoxvxgcGGuJF8NpbzufEQ4,4335
222
222
  reconcile/glitchtip/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
223
- reconcile/glitchtip/integration.py,sha256=vCyg8W4ZUGxjU8tB1Gkre_auSpzo83n05mmO8_-7al0,8263
223
+ reconcile/glitchtip/integration.py,sha256=HWc0cz3vNpZg8T5nVMPyD5QnlgsUHwLpJsGRbr_hvIM,7937
224
224
  reconcile/glitchtip/reconciler.py,sha256=nUvDv7qG1ly0cA16MmlL6NV71yl1mJYLT2mui7lmi0Y,12402
225
225
  reconcile/glitchtip_project_alerts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
226
226
  reconcile/glitchtip_project_alerts/integration.py,sha256=BgMx-NyV9mTuv7Sotb2OioCO-iwVrN9-HCViVLU771c,13755
227
227
  reconcile/glitchtip_project_dsn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
228
228
  reconcile/glitchtip_project_dsn/integration.py,sha256=2iugub-kHYkHNK33n0v9_TeWonuxCPah_VkoTPvaajE,8077
229
229
  reconcile/gql_definitions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
230
- reconcile/gql_definitions/introspection.json,sha256=AK7hGfvHjHL6Nqg3KNL4dmOiy4rOPkDSZ8zAWeA5kqU,2283098
230
+ reconcile/gql_definitions/introspection.json,sha256=0begChFQIAwAkSL-tmYKbh3fJmtvgxoyHm0FRP3HPGY,2283286
231
231
  reconcile/gql_definitions/acs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
232
232
  reconcile/gql_definitions/acs/acs_instances.py,sha256=L91WW9LbhJbBSrECqShQpFtjoBOsmNIYLRpMbx1io5o,2181
233
233
  reconcile/gql_definitions/acs/acs_policies.py,sha256=bN5i4mks10Z23KJSj7jqp966Osq2dps4d-sPH9gjxEA,7008
@@ -352,7 +352,7 @@ reconcile/gql_definitions/gitlab_members/gitlab_instances.py,sha256=oYPvfiOsPTGH
352
352
  reconcile/gql_definitions/gitlab_members/permissions.py,sha256=Qzj3Fpv7xj8v9eygeP312nHRNg8er8XMRBveynPIyQM,3302
353
353
  reconcile/gql_definitions/glitchtip/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
354
354
  reconcile/gql_definitions/glitchtip/glitchtip_instance.py,sha256=QUfLhRkdE_-SorWgLBB8LHFD6kihbFwEoVByCLax3yM,2781
355
- reconcile/gql_definitions/glitchtip/glitchtip_project.py,sha256=AojrkCDGbVjY0TOkfookz-9tqLA9txY7_xNdsWe174c,6004
355
+ reconcile/gql_definitions/glitchtip/glitchtip_project.py,sha256=3hS6ZbWr-KMWeVHYbP3HAK7uHd2e6KsXOr87gba7Uqc,6578
356
356
  reconcile/gql_definitions/glitchtip_project_alerts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
357
357
  reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py,sha256=Pv6RcuIzpNmGc43eEq64nnKG0Dr7H0wjy5Xg1_oRltM,5197
358
358
  reconcile/gql_definitions/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -639,7 +639,7 @@ reconcile/utils/promtool.py,sha256=xmPBWEApkk0L2qZBAvTxakNXxfTz-tVLPFxGnpsxXnM,2
639
639
  reconcile/utils/quay_api.py,sha256=uE_jxcdy3ViHtYFAfwDQuFDaO7Pr6AAPoVnmORbyHio,7822
640
640
  reconcile/utils/raw_github_api.py,sha256=2WKtE8ABYYB9UGOAh9N_kLkksBWL3320Z2_scteZddI,2805
641
641
  reconcile/utils/repo_owners.py,sha256=BHrAXxKyvn4qWJwFPWYGTtfgnLmYnWtYFEJGFeD__FE,6573
642
- reconcile/utils/rest_api_base.py,sha256=Ep3wmabbzpWzcvZsfJHN3R1XgNtQXHmiYKOMAgtLspk,4277
642
+ reconcile/utils/rest_api_base.py,sha256=MT7tp6CQO2S5aKfVOzw_hipWg7wAGoOqkm4qurI1hEU,4342
643
643
  reconcile/utils/ruamel.py,sha256=FzL4_L0FnMOUZmgThrZSMJs5MTdXwiy-E9MZWfk8bh8,397
644
644
  reconcile/utils/secret_reader.py,sha256=MaP56KZaAE35EyYbgAitdm6fUSxdzWeGFSOym9qiZkw,10206
645
645
  reconcile/utils/semver_helper.py,sha256=-WfPOMSA2v1h7hT3PwVf-Htg7wOsoKlQC1JdmDX2Ars,1268
@@ -680,8 +680,8 @@ reconcile/utils/clusterhealth/telemeter.py,sha256=PllSLsJXvGNatmTF4mxCNPVbDrpr_M
680
680
  reconcile/utils/dynatrace/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
681
681
  reconcile/utils/dynatrace/client.py,sha256=RUk6KH-3CJyfJ1jolrdGQR4Hhz-tIWWJo9dsZ1IgJVw,3736
682
682
  reconcile/utils/glitchtip/__init__.py,sha256=FT6iBhGqoe7KExFdbgL8AYUb64iW_4snF5__Dcl7yt0,258
683
- reconcile/utils/glitchtip/client.py,sha256=ovh4tx-ajlihjvcq6nyY4chulbuMJYvzDPv9j9CuAKM,7867
684
- reconcile/utils/glitchtip/models.py,sha256=AJuGq4_A6G_T7asBKIw69-fOZLmT8HFrTKBEys7Tp00,6481
683
+ reconcile/utils/glitchtip/client.py,sha256=F_x18QMHfeNoS3vS_VvjVm1IKnwGOsXvopjR9iwj4aY,8948
684
+ reconcile/utils/glitchtip/models.py,sha256=FyNt9EA9IS6tlsvqz-j7SkL9MoT_zIUTun0EiC9No6U,6684
685
685
  reconcile/utils/internal_groups/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
686
686
  reconcile/utils/internal_groups/client.py,sha256=DVWSEOsdP0trHANM-xw9WJjKSywrA9x_SFSCgUjvEV8,4665
687
687
  reconcile/utils/internal_groups/models.py,sha256=y_IqBVqfGqNXiu0VudvBWFrm_-uafVm5KgLG-ca8XAs,2281
@@ -763,8 +763,8 @@ tools/app_interface_reporter.py,sha256=0_oq1-mL0UOh1x5G8CoKaEVJqK-XTKE7PX7IbRTov
763
763
  tools/app_sre_tekton_access_reporter.py,sha256=o9prLUgQpwO3msRWc2as1xT1y9OB3znkpgvLr0Ys8_M,3146
764
764
  tools/app_sre_tekton_access_revalidation.py,sha256=66nHEaY-bIqxIhpcmwN8AvQZu6ZXenfkg4Fut0pVZRM,2726
765
765
  tools/glitchtip_access_reporter.py,sha256=o01A6b88t3Wie6tj_tJWWVo2J01LxQ_a9giGm4UzEaU,2901
766
- tools/glitchtip_access_revalidation.py,sha256=8kbBJk04mkq28kWoRDDkfCGIF3GRg3pJrFAh1sW0dbk,2821
767
- tools/qontract_cli.py,sha256=_KHwJIC0Keyy2cmhUVruOYtmp1CrTZDzEZkEe8lf81I,152941
766
+ tools/glitchtip_access_revalidation.py,sha256=PXN5wxl6OX8sxddPaakDF3X79nFLvpm-lz0mWLVelw0,2806
767
+ tools/qontract_cli.py,sha256=S_DGw0RXy0UZYBhuTqV8lJis1ARukmqT6SNr6Gl-tS4,157625
768
768
  tools/sd_app_sre_alert_report.py,sha256=jQpJdXVID68bSNtJNOGDh0-ei1CfEUS4Itr4MAaBNFA,5062
769
769
  tools/template_validation.py,sha256=qpKYaTgk0GOPGa2Ct5_5sKdwIHtCAKIBGzsMPuJU5fw,3371
770
770
  tools/cli_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -791,7 +791,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
791
791
  tools/saas_promotion_state/saas_promotion_state.py,sha256=UfwwRLS5Ya4_Nh1w5n1dvoYtchQvYE9yj1VANt2IKqI,3925
792
792
  tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
793
793
  tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
794
- qontract_reconcile-0.10.2.dev129.dist-info/METADATA,sha256=ndVEjngQXOngXONCWSpU3_GkfSDokcOhYYTdbWuucW0,24566
795
- qontract_reconcile-0.10.2.dev129.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
796
- qontract_reconcile-0.10.2.dev129.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
797
- qontract_reconcile-0.10.2.dev129.dist-info/RECORD,,
794
+ qontract_reconcile-0.10.2.dev130.dist-info/METADATA,sha256=2CCwcz__a7QKk0BgiDB2-tPBCrscTGVp8QHheIuF5Fo,24566
795
+ qontract_reconcile-0.10.2.dev130.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
796
+ qontract_reconcile-0.10.2.dev130.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
797
+ qontract_reconcile-0.10.2.dev130.dist-info/RECORD,,
@@ -144,13 +144,6 @@ def get_glitchtip_projects(query_func: Callable) -> list[GlitchtipProjectV1]:
144
144
  glitchtip_projects = (
145
145
  glitchtip_project_query(query_func=query_func).glitchtip_projects or []
146
146
  )
147
- for project in glitchtip_projects:
148
- # either org.owners or project.app must be set
149
- if not project.organization.owners and not project.app:
150
- raise ValueError(
151
- f"Either owners in organization {project.organization.name} or app must be set for project {project.name}"
152
- )
153
-
154
147
  return glitchtip_projects
155
148
 
156
149
 
@@ -98,9 +98,17 @@ query Projects {
98
98
  }
99
99
  }
100
100
  }
101
- # for glitchtip access revalidation
102
101
  app {
102
+ # for glitchtip access revalidation
103
103
  path
104
+ # for the qontract-cli glitchtip subcommands
105
+ escalationPolicy {
106
+ channels {
107
+ jiraBoard {
108
+ name
109
+ }
110
+ }
111
+ }
104
112
  }
105
113
  }
106
114
  }
@@ -176,8 +184,21 @@ class NamespaceV1(ConfiguredBaseModel):
176
184
  cluster: ClusterV1 = Field(..., alias="cluster")
177
185
 
178
186
 
187
+ class JiraBoardV1(ConfiguredBaseModel):
188
+ name: str = Field(..., alias="name")
189
+
190
+
191
+ class AppEscalationPolicyChannelsV1(ConfiguredBaseModel):
192
+ jira_board: list[JiraBoardV1] = Field(..., alias="jiraBoard")
193
+
194
+
195
+ class AppEscalationPolicyV1(ConfiguredBaseModel):
196
+ channels: AppEscalationPolicyChannelsV1 = Field(..., alias="channels")
197
+
198
+
179
199
  class AppV1(ConfiguredBaseModel):
180
200
  path: str = Field(..., alias="path")
201
+ escalation_policy: AppEscalationPolicyV1 = Field(..., alias="escalationPolicy")
181
202
 
182
203
 
183
204
  class GlitchtipProjectV1(ConfiguredBaseModel):
@@ -188,7 +209,7 @@ class GlitchtipProjectV1(ConfiguredBaseModel):
188
209
  teams: list[GlitchtipTeamV1] = Field(..., alias="teams")
189
210
  organization: GlitchtipProjectV1_GlitchtipOrganizationV1 = Field(..., alias="organization")
190
211
  namespaces: list[NamespaceV1] = Field(..., alias="namespaces")
191
- app: Optional[AppV1] = Field(..., alias="app")
212
+ app: AppV1 = Field(..., alias="app")
192
213
 
193
214
 
194
215
  class ProjectsQueryData(ConfiguredBaseModel):
@@ -23719,9 +23719,13 @@
23719
23719
  "description": null,
23720
23720
  "args": [],
23721
23721
  "type": {
23722
- "kind": "OBJECT",
23723
- "name": "App_v1",
23724
- "ofType": null
23722
+ "kind": "NON_NULL",
23723
+ "name": null,
23724
+ "ofType": {
23725
+ "kind": "OBJECT",
23726
+ "name": "App_v1",
23727
+ "ofType": null
23728
+ }
23725
23729
  },
23726
23730
  "isDeprecated": false,
23727
23731
  "deprecationReason": null
@@ -1,8 +1,11 @@
1
+ from datetime import datetime
2
+
1
3
  from reconcile.utils.glitchtip.models import (
2
4
  Organization,
3
5
  Project,
4
6
  ProjectAlert,
5
7
  ProjectKey,
8
+ ProjectStatistics,
6
9
  Team,
7
10
  User,
8
11
  )
@@ -57,6 +60,40 @@ class GlitchtipClient(ApiBase):
57
60
  )
58
61
  ]
59
62
 
63
+ def all_projects(self) -> list[Project]:
64
+ """List all projects."""
65
+ return [
66
+ Project(**r) for r in self._list("/api/0/projects/", params={"limit": 100})
67
+ ]
68
+
69
+ def project_statistics(
70
+ self,
71
+ organization_slug: str,
72
+ project_pk: int,
73
+ start: datetime,
74
+ end: datetime,
75
+ ) -> ProjectStatistics:
76
+ """Get project statistics."""
77
+ field = "sum(quantity)"
78
+ timeseries = self._get(
79
+ f"/api/0/organizations/{organization_slug}/stats_v2/",
80
+ params={
81
+ "category": "error",
82
+ "interval": "1h",
83
+ "field": field,
84
+ "start": start.isoformat(),
85
+ "end": end.isoformat(),
86
+ "project": [project_pk],
87
+ },
88
+ )
89
+ return ProjectStatistics(
90
+ start=start,
91
+ end=end,
92
+ events=sum(
93
+ i for i in timeseries["groups"][0]["series"][field] if i is not None
94
+ ),
95
+ )
96
+
60
97
  def create_project(
61
98
  self, organization_slug: str, team_slug: str, name: str, platform: str | None
62
99
  ) -> Project:
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import re
4
4
  from collections.abc import MutableMapping
5
+ from datetime import datetime
5
6
  from enum import Enum
6
7
  from typing import Any
7
8
 
@@ -152,6 +153,7 @@ class Project(BaseModel):
152
153
  teams: list[Team] = []
153
154
  alerts: list[ProjectAlert] = []
154
155
  event_throttle_rate: int = Field(0, alias="eventThrottleRate")
156
+ organization: Organization | None = None
155
157
 
156
158
  class Config:
157
159
  allow_population_by_field_name = True
@@ -185,6 +187,12 @@ class Project(BaseModel):
185
187
  return hash(self.slug)
186
188
 
187
189
 
190
+ class ProjectStatistics(BaseModel):
191
+ start: datetime
192
+ end: datetime
193
+ events: int = 0
194
+
195
+
188
196
  class Organization(BaseModel):
189
197
  pk: int | None = Field(None, alias="id")
190
198
  name: str
@@ -210,3 +218,6 @@ class Organization(BaseModel):
210
218
 
211
219
  def __hash__(self) -> int:
212
220
  return hash(self.name)
221
+
222
+
223
+ Project.update_forward_refs()
@@ -74,8 +74,10 @@ class ApiBase:
74
74
  def cleanup(self) -> None:
75
75
  self.session.close()
76
76
 
77
- def _get(self, url: str) -> dict[str, Any]:
78
- response = self.session.get(urljoin(self.host, url), timeout=self.read_timeout)
77
+ def _get(self, url: str, params: dict | None = None) -> dict[str, Any]:
78
+ response = self.session.get(
79
+ urljoin(self.host, url), params=params, timeout=self.read_timeout
80
+ )
79
81
  response.raise_for_status()
80
82
  try:
81
83
  return response.json()
@@ -64,7 +64,7 @@ def main(
64
64
  glitchtip_project_query(query_func=gql.get_api().query).glitchtip_projects or []
65
65
  )
66
66
 
67
- apps = {project.app.path for project in glitchtip_projects if project.app}
67
+ apps = {project.app.path for project in glitchtip_projects}
68
68
 
69
69
  notification = Notification(
70
70
  notification_type="Action Required",
tools/qontract_cli.py CHANGED
@@ -10,7 +10,7 @@ import sys
10
10
  import tempfile
11
11
  import textwrap
12
12
  from collections import defaultdict
13
- from collections.abc import Callable, Mapping
13
+ from collections.abc import Callable, Iterable, Mapping
14
14
  from datetime import (
15
15
  UTC,
16
16
  datetime,
@@ -83,6 +83,12 @@ from reconcile.gql_definitions.common.app_interface_vault_settings import (
83
83
  )
84
84
  from reconcile.gql_definitions.common.clusters import ClusterSpecROSAV1
85
85
  from reconcile.gql_definitions.fragments.aus_organization import AUSOCMOrganization
86
+ from reconcile.gql_definitions.glitchtip.glitchtip_instance import (
87
+ query as glitchtip_instance_query,
88
+ )
89
+ from reconcile.gql_definitions.glitchtip.glitchtip_project import (
90
+ query as glitchtip_project_query,
91
+ )
86
92
  from reconcile.gql_definitions.integrations import integrations as integrations_gql
87
93
  from reconcile.gql_definitions.maintenance import maintenances as maintenances_gql
88
94
  from reconcile.jenkins_job_builder import init_jjb
@@ -133,6 +139,8 @@ from reconcile.utils.gitlab_api import (
133
139
  MRState,
134
140
  MRStatus,
135
141
  )
142
+ from reconcile.utils.glitchtip.client import GlitchtipClient
143
+ from reconcile.utils.glitchtip.models import Project, ProjectStatistics
136
144
  from reconcile.utils.gql import GqlApiSingleton
137
145
  from reconcile.utils.jjb_client import JJB
138
146
  from reconcile.utils.keycloak import (
@@ -156,6 +164,7 @@ from reconcile.utils.ocm import OCM_PRODUCT_ROSA, OCMMap
156
164
  from reconcile.utils.ocm.upgrades import get_upgrade_policies
157
165
  from reconcile.utils.ocm_base_client import init_ocm_base_client
158
166
  from reconcile.utils.output import print_output
167
+ from reconcile.utils.rest_api_base import BearerTokenAuth
159
168
  from reconcile.utils.saasherder.models import TargetSpec
160
169
  from reconcile.utils.saasherder.saasherder import SaasHerder
161
170
  from reconcile.utils.secret_reader import (
@@ -4635,5 +4644,140 @@ def log_group_usage(ctx: click.Context, aws_account: str) -> None:
4635
4644
  print_output(ctx.obj["options"], results, columns)
4636
4645
 
4637
4646
 
4647
+ @root.group()
4648
+ @click.option(
4649
+ "--instance",
4650
+ required=True,
4651
+ help="Glitchtip Instance Name",
4652
+ default="glitchtip-production",
4653
+ )
4654
+ @click.pass_context
4655
+ def glitchtip(ctx: click.Context, instance: str) -> None:
4656
+ """Glitchtip commands"""
4657
+
4658
+ gqlapi = gql.get_api()
4659
+ vault_settings = get_app_interface_vault_settings()
4660
+ secret_reader = create_secret_reader(use_vault=vault_settings.vault)
4661
+ for glitchtip_instance in (
4662
+ glitchtip_instance_query(query_func=gqlapi.query).instances or []
4663
+ ):
4664
+ if glitchtip_instance.name.lower() == instance.lower():
4665
+ ctx.obj["client"] = GlitchtipClient(
4666
+ host=glitchtip_instance.console_url,
4667
+ auth=BearerTokenAuth(
4668
+ secret_reader.read_secret(glitchtip_instance.automation_token)
4669
+ ),
4670
+ read_timeout=glitchtip_instance.read_timeout,
4671
+ max_retries=glitchtip_instance.max_retries,
4672
+ )
4673
+ if "client" not in ctx.obj:
4674
+ print(f"Glitchtip instance {instance} not found")
4675
+ sys.exit(1)
4676
+
4677
+
4678
+ @glitchtip.command()
4679
+ @click.option(
4680
+ "--id",
4681
+ "ids",
4682
+ type=int,
4683
+ help="Glitchtip Project ID. Can be specified multiple times.",
4684
+ multiple=True,
4685
+ )
4686
+ @click.option(
4687
+ "--name",
4688
+ "names",
4689
+ help="Glitchtip Project Name. Can be specified multiple times.",
4690
+ multiple=True,
4691
+ )
4692
+ @click.pass_context
4693
+ def projects(ctx: click.Context, ids: Iterable[int], names: Iterable[str]) -> None:
4694
+ """Display Glitchtip project information
4695
+
4696
+ This command will display the project metadata for the specified project IDs or names.
4697
+ If no IDs or names are provided, all projects will be displayed.
4698
+ """
4699
+ console = Console()
4700
+ table = Table("ID", "Organization", "Name", "Throttle Rate")
4701
+ glitchtip_client: GlitchtipClient = ctx.obj["client"]
4702
+ with glitchtip_client as client:
4703
+ for project in client.all_projects():
4704
+ if (
4705
+ (ids or names)
4706
+ and (project.pk and project.pk not in ids)
4707
+ and (project.name not in names)
4708
+ ):
4709
+ continue
4710
+
4711
+ assert project.organization # make mypy happy
4712
+
4713
+ table.add_row(
4714
+ str(project.pk),
4715
+ project.organization.name,
4716
+ project.name,
4717
+ str(project.event_throttle_rate),
4718
+ )
4719
+ console.print(table)
4720
+
4721
+
4722
+ @glitchtip.command()
4723
+ @click.option(
4724
+ "--top",
4725
+ type=int,
4726
+ help="Display the top N projects with the most events in the last 24 hours",
4727
+ default=10,
4728
+ )
4729
+ @click.pass_context
4730
+ def top_talkers(ctx: click.Context, top: int) -> None:
4731
+ """Get the projects with the most events in the last 24 hours."""
4732
+ console = Console()
4733
+ table = Table("ID", "Organization", "Name", "Jira Board", "# Events in last 24h")
4734
+
4735
+ glitchtip_client: GlitchtipClient = ctx.obj["client"]
4736
+ stats: list[tuple[Project, ProjectStatistics]] = []
4737
+ app_interface_glitchtip_projects = {
4738
+ p.name: p
4739
+ for p in glitchtip_project_query(
4740
+ query_func=gql.get_api().query
4741
+ ).glitchtip_projects
4742
+ or []
4743
+ }
4744
+
4745
+ with glitchtip_client as client:
4746
+ for project in client.all_projects():
4747
+ assert project.organization # make mypy happy
4748
+ assert project.pk # make mypy happy
4749
+
4750
+ stat = client.project_statistics(
4751
+ organization_slug=project.organization.slug,
4752
+ project_pk=project.pk,
4753
+ start=datetime.now(tz=UTC) - timedelta(hours=24),
4754
+ end=datetime.now(tz=UTC),
4755
+ )
4756
+ stats.append((project, stat))
4757
+
4758
+ # sort by event count
4759
+ stats.sort(key=lambda x: x[1].events, reverse=True)
4760
+
4761
+ for project, stat in stats[:top]:
4762
+ assert project.organization # make mypy happy
4763
+ assert project.pk # make mypy happy
4764
+
4765
+ jira_boards = [
4766
+ board.name
4767
+ for board in app_interface_glitchtip_projects[
4768
+ project.name
4769
+ ].app.escalation_policy.channels.jira_board
4770
+ ]
4771
+
4772
+ table.add_row(
4773
+ str(project.pk),
4774
+ project.organization.name,
4775
+ project.name,
4776
+ ", ".join(jira_boards),
4777
+ str(stat.events),
4778
+ )
4779
+ console.print(table)
4780
+
4781
+
4638
4782
  if __name__ == "__main__":
4639
4783
  root() # pylint: disable=no-value-for-parameter