firefighter-incident 0.0.1__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.
Files changed (429) hide show
  1. firefighter/__init__.py +0 -0
  2. firefighter/_version.py +16 -0
  3. firefighter/api/__init__.py +4 -0
  4. firefighter/api/admin.py +105 -0
  5. firefighter/api/apps.py +9 -0
  6. firefighter/api/authentication.py +20 -0
  7. firefighter/api/migrations/0001_initial.py +49 -0
  8. firefighter/api/migrations/0002_alter_apitokenproxy_options.py +28 -0
  9. firefighter/api/migrations/__init__.py +0 -0
  10. firefighter/api/models.py +45 -0
  11. firefighter/api/permissions.py +18 -0
  12. firefighter/api/renderer.py +115 -0
  13. firefighter/api/serializers.py +291 -0
  14. firefighter/api/urls.py +79 -0
  15. firefighter/api/views/__init__.py +13 -0
  16. firefighter/api/views/_base.py +80 -0
  17. firefighter/api/views/components.py +10 -0
  18. firefighter/api/views/environments.py +10 -0
  19. firefighter/api/views/groups.py +10 -0
  20. firefighter/api/views/incident_cost_types.py +10 -0
  21. firefighter/api/views/incident_costs.py +10 -0
  22. firefighter/api/views/incidents.py +263 -0
  23. firefighter/api/views/severities.py +10 -0
  24. firefighter/components/__init__.py +11 -0
  25. firefighter/components/avatar/__init__.py +0 -0
  26. firefighter/components/avatar/avatar.html +11 -0
  27. firefighter/components/avatar/avatar.py +20 -0
  28. firefighter/components/card/__init__.py +0 -0
  29. firefighter/components/card/card.html +17 -0
  30. firefighter/components/card/card.py +16 -0
  31. firefighter/components/export_button/__init__.py +0 -0
  32. firefighter/components/export_button/export_button.html +33 -0
  33. firefighter/components/export_button/export_button.py +41 -0
  34. firefighter/components/form/__init__.py +0 -0
  35. firefighter/components/form/form.html +18 -0
  36. firefighter/components/form/form.py +23 -0
  37. firefighter/components/form_field/__init__.py +0 -0
  38. firefighter/components/form_field/form_field.html +18 -0
  39. firefighter/components/form_field/form_field.py +26 -0
  40. firefighter/components/messages/__init__.py +0 -0
  41. firefighter/components/messages/messages.html +52 -0
  42. firefighter/components/messages/messages.py +16 -0
  43. firefighter/components/modal/__init__.py +0 -0
  44. firefighter/components/modal/modal.html +64 -0
  45. firefighter/components/modal/modal.py +20 -0
  46. firefighter/confluence/__init__.py +0 -0
  47. firefighter/confluence/admin.py +52 -0
  48. firefighter/confluence/apps.py +15 -0
  49. firefighter/confluence/client.py +192 -0
  50. firefighter/confluence/management/__init__.py +0 -0
  51. firefighter/confluence/management/commands/__init__.py +0 -0
  52. firefighter/confluence/management/commands/sort_postmortems.py +25 -0
  53. firefighter/confluence/management/commands/sort_runbooks.py +23 -0
  54. firefighter/confluence/management/commands/sync_postmortems.py +16 -0
  55. firefighter/confluence/management/commands/sync_runbooks.py +14 -0
  56. firefighter/confluence/migrations/0001_initial_oss.py +104 -0
  57. firefighter/confluence/migrations/__init__.py +0 -0
  58. firefighter/confluence/models.py +189 -0
  59. firefighter/confluence/serializers.py +11 -0
  60. firefighter/confluence/service.py +336 -0
  61. firefighter/confluence/signals/__init__.py +3 -0
  62. firefighter/confluence/signals/incident_updated.py +54 -0
  63. firefighter/confluence/tables.py +22 -0
  64. firefighter/confluence/tasks/__init__.py +9 -0
  65. firefighter/confluence/tasks/archive_postmortems.py +223 -0
  66. firefighter/confluence/tasks/sort_runbooks.py +85 -0
  67. firefighter/confluence/tasks/sync_pages_content.py +34 -0
  68. firefighter/confluence/tasks/sync_postmortems.py +71 -0
  69. firefighter/confluence/tasks/sync_runbooks.py +88 -0
  70. firefighter/confluence/templates/oncall_team.xml +6 -0
  71. firefighter/confluence/templates/pages/runbook_list.html +20 -0
  72. firefighter/confluence/urls.py +22 -0
  73. firefighter/confluence/utils.py +103 -0
  74. firefighter/confluence/views/__init__.py +0 -0
  75. firefighter/confluence/views/api.py +10 -0
  76. firefighter/confluence/views/postmortem/__init__.py +0 -0
  77. firefighter/confluence/views/postmortem/postmortem_detail.py +0 -0
  78. firefighter/confluence/views/runbook/__init__.py +0 -0
  79. firefighter/confluence/views/runbook/runbook_list.py +46 -0
  80. firefighter/firefighter/__init__.py +26 -0
  81. firefighter/firefighter/admin.py +12 -0
  82. firefighter/firefighter/apps.py +50 -0
  83. firefighter/firefighter/asgi.py +17 -0
  84. firefighter/firefighter/celery_client.py +40 -0
  85. firefighter/firefighter/fields_forms_widgets.py +92 -0
  86. firefighter/firefighter/filters.py +144 -0
  87. firefighter/firefighter/formats/__init__.py +1 -0
  88. firefighter/firefighter/formats/en/__init__.py +0 -0
  89. firefighter/firefighter/formats/en/formats.py +5 -0
  90. firefighter/firefighter/http_client.py +56 -0
  91. firefighter/firefighter/management/__init__.py +0 -0
  92. firefighter/firefighter/management/commands/__init__.py +0 -0
  93. firefighter/firefighter/management/commands/task.py +35 -0
  94. firefighter/firefighter/middleware.py +35 -0
  95. firefighter/firefighter/settings/__init__.py +53 -0
  96. firefighter/firefighter/settings/components/__init__.py +0 -0
  97. firefighter/firefighter/settings/components/api.py +173 -0
  98. firefighter/firefighter/settings/components/caches.py +41 -0
  99. firefighter/firefighter/settings/components/celery.py +39 -0
  100. firefighter/firefighter/settings/components/common.py +308 -0
  101. firefighter/firefighter/settings/components/confluence.py +43 -0
  102. firefighter/firefighter/settings/components/jira_app.py +21 -0
  103. firefighter/firefighter/settings/components/logging.py +105 -0
  104. firefighter/firefighter/settings/components/pagerduty.py +17 -0
  105. firefighter/firefighter/settings/components/raid.py +22 -0
  106. firefighter/firefighter/settings/components/slack.py +53 -0
  107. firefighter/firefighter/settings/environments/__init__.py +3 -0
  108. firefighter/firefighter/settings/environments/dev.py +204 -0
  109. firefighter/firefighter/settings/environments/prod.py +61 -0
  110. firefighter/firefighter/settings/settings_builder.py +41 -0
  111. firefighter/firefighter/settings/settings_utils.py +25 -0
  112. firefighter/firefighter/sso.py +44 -0
  113. firefighter/firefighter/tables_utils.py +19 -0
  114. firefighter/firefighter/templates/admin/base.html +106 -0
  115. firefighter/firefighter/templates/admin/login.html +14 -0
  116. firefighter/firefighter/templates/admin/send_message_conversation.html +45 -0
  117. firefighter/firefighter/templates/robots.txt +2 -0
  118. firefighter/firefighter/urls.py +113 -0
  119. firefighter/firefighter/utils.py +92 -0
  120. firefighter/firefighter/views.py +71 -0
  121. firefighter/firefighter/wsgi.py +22 -0
  122. firefighter/incidents/__init__.py +0 -0
  123. firefighter/incidents/admin.py +737 -0
  124. firefighter/incidents/apps.py +13 -0
  125. firefighter/incidents/enums.py +32 -0
  126. firefighter/incidents/factories.py +92 -0
  127. firefighter/incidents/forms/__init__.py +3 -0
  128. firefighter/incidents/forms/close_incident.py +38 -0
  129. firefighter/incidents/forms/create_incident.py +92 -0
  130. firefighter/incidents/forms/select_impact.py +123 -0
  131. firefighter/incidents/forms/update_key_events.py +115 -0
  132. firefighter/incidents/forms/update_roles.py +95 -0
  133. firefighter/incidents/forms/update_status.py +36 -0
  134. firefighter/incidents/forms/utils.py +97 -0
  135. firefighter/incidents/menus.py +162 -0
  136. firefighter/incidents/migrations/0001_initial_oss.py +1617 -0
  137. firefighter/incidents/migrations/__init__.py +0 -0
  138. firefighter/incidents/models/__init__.py +15 -0
  139. firefighter/incidents/models/component.py +226 -0
  140. firefighter/incidents/models/environment.py +26 -0
  141. firefighter/incidents/models/group.py +20 -0
  142. firefighter/incidents/models/impact.py +125 -0
  143. firefighter/incidents/models/incident.py +706 -0
  144. firefighter/incidents/models/incident_cost.py +48 -0
  145. firefighter/incidents/models/incident_cost_type.py +27 -0
  146. firefighter/incidents/models/incident_membership.py +58 -0
  147. firefighter/incidents/models/incident_role_type.py +65 -0
  148. firefighter/incidents/models/incident_update.py +136 -0
  149. firefighter/incidents/models/metric_type.py +89 -0
  150. firefighter/incidents/models/milestone_type.py +65 -0
  151. firefighter/incidents/models/priority.py +65 -0
  152. firefighter/incidents/models/severity.py +56 -0
  153. firefighter/incidents/models/user.py +94 -0
  154. firefighter/incidents/signals.py +78 -0
  155. firefighter/incidents/static/css/incident.css +171 -0
  156. firefighter/incidents/static/css/main.css +2 -0
  157. firefighter/incidents/static/css/main.min.css +1 -0
  158. firefighter/incidents/static/css/tailwind.css +69 -0
  159. firefighter/incidents/static/img/favicon/android-chrome-192x192.png +0 -0
  160. firefighter/incidents/static/img/favicon/android-chrome-512x512.png +0 -0
  161. firefighter/incidents/static/img/favicon/apple-touch-icon.png +0 -0
  162. firefighter/incidents/static/img/favicon/favicon-16x16.png +0 -0
  163. firefighter/incidents/static/img/favicon/favicon-32x32.png +0 -0
  164. firefighter/incidents/static/img/favicon/favicon.ico +0 -0
  165. firefighter/incidents/static/img/favicon/site.webmanifest +1 -0
  166. firefighter/incidents/static/img/gameday.png +0 -0
  167. firefighter/incidents/static/img/logo-firefighter.png +0 -0
  168. firefighter/incidents/static/img/p1.png +0 -0
  169. firefighter/incidents/static/img/p2.png +0 -0
  170. firefighter/incidents/static/img/p3.png +0 -0
  171. firefighter/incidents/static/img/p4.png +0 -0
  172. firefighter/incidents/static/img/p5.png +0 -0
  173. firefighter/incidents/static/js/main.js +24 -0
  174. firefighter/incidents/static/js/main.min.js +15 -0
  175. firefighter/incidents/tables.py +108 -0
  176. firefighter/incidents/tasks/__init__.py +3 -0
  177. firefighter/incidents/tasks/updateoncall.py +115 -0
  178. firefighter/incidents/templates/incidents/errors/base.html +21 -0
  179. firefighter/incidents/templates/incidents/filter.html +80 -0
  180. firefighter/incidents/templates/incidents/table/priority_column.html +8 -0
  181. firefighter/incidents/templates/incidents/table/status_column.html +1 -0
  182. firefighter/incidents/templates/incidents/table.html +57 -0
  183. firefighter/incidents/templates/incidents/widgets/form_container.html +91 -0
  184. firefighter/incidents/templates/incidents/widgets/grouped_checkbox_nested.html +97 -0
  185. firefighter/incidents/templates/incidents/widgets/input_option.html +4 -0
  186. firefighter/incidents/templates/layouts/index.html +25 -0
  187. firefighter/incidents/templates/layouts/partials/created_at_help.html +58 -0
  188. firefighter/incidents/templates/layouts/partials/environment_pill.html +11 -0
  189. firefighter/incidents/templates/layouts/partials/footer.html +34 -0
  190. firefighter/incidents/templates/layouts/partials/header.html +89 -0
  191. firefighter/incidents/templates/layouts/partials/incident_card.html +28 -0
  192. firefighter/incidents/templates/layouts/partials/incident_metrics.html +26 -0
  193. firefighter/incidents/templates/layouts/partials/incident_timeline.html +117 -0
  194. firefighter/incidents/templates/layouts/partials/incident_update_key_events_view.html +13 -0
  195. firefighter/incidents/templates/layouts/partials/incident_update_key_events_view_modal.html +22 -0
  196. firefighter/incidents/templates/layouts/partials/partial_table_list_paginated.html +108 -0
  197. firefighter/incidents/templates/layouts/partials/priority_icon.html +14 -0
  198. firefighter/incidents/templates/layouts/partials/priority_pill.html +4 -0
  199. firefighter/incidents/templates/layouts/partials/status_pill.html +18 -0
  200. firefighter/incidents/templates/layouts/partials/table.html +30 -0
  201. firefighter/incidents/templates/layouts/partials/user_card.html +14 -0
  202. firefighter/incidents/templates/layouts/partials/user_tooltip.html +13 -0
  203. firefighter/incidents/templates/layouts/view_filters.html +30 -0
  204. firefighter/incidents/templates/pages/component_detail.html +92 -0
  205. firefighter/incidents/templates/pages/component_list.html +22 -0
  206. firefighter/incidents/templates/pages/dashboard.html +40 -0
  207. firefighter/incidents/templates/pages/docs_metrics.html +84 -0
  208. firefighter/incidents/templates/pages/incident_create.html +53 -0
  209. firefighter/incidents/templates/pages/incident_detail.html +233 -0
  210. firefighter/incidents/templates/pages/incident_list.html +29 -0
  211. firefighter/incidents/templates/pages/incident_role_types_detail.html +88 -0
  212. firefighter/incidents/templates/pages/incident_role_types_list.html +32 -0
  213. firefighter/incidents/templates/pages/incident_statistics.html +124 -0
  214. firefighter/incidents/templates/pages/incident_statistics_partial.html +121 -0
  215. firefighter/incidents/templates/pages/incident_update_key_events_form.html +16 -0
  216. firefighter/incidents/templates/pages/user_detail.html +110 -0
  217. firefighter/incidents/urls.py +67 -0
  218. firefighter/incidents/views/__init__.py +0 -0
  219. firefighter/incidents/views/components/__init__.py +0 -0
  220. firefighter/incidents/views/components/details.py +33 -0
  221. firefighter/incidents/views/components/list.py +63 -0
  222. firefighter/incidents/views/date_filter.py +166 -0
  223. firefighter/incidents/views/date_utils.py +189 -0
  224. firefighter/incidents/views/docs/__init__.py +0 -0
  225. firefighter/incidents/views/docs/metrics.py +47 -0
  226. firefighter/incidents/views/docs/role_types.py +35 -0
  227. firefighter/incidents/views/errors.py +249 -0
  228. firefighter/incidents/views/reports.py +564 -0
  229. firefighter/incidents/views/users/__init__.py +0 -0
  230. firefighter/incidents/views/users/details.py +60 -0
  231. firefighter/incidents/views/views.py +297 -0
  232. firefighter/jira_app/__init__.py +0 -0
  233. firefighter/jira_app/admin.py +24 -0
  234. firefighter/jira_app/apps.py +15 -0
  235. firefighter/jira_app/client.py +455 -0
  236. firefighter/jira_app/migrations/0001_initial_oss.py +84 -0
  237. firefighter/jira_app/migrations/__init__.py +0 -0
  238. firefighter/jira_app/models.py +61 -0
  239. firefighter/jira_app/tasks/__init__.py +3 -0
  240. firefighter/jira_app/tasks/sync_users_jira.py +47 -0
  241. firefighter/jira_app/types.py +44 -0
  242. firefighter/jira_app/utils.py +112 -0
  243. firefighter/logging/__init__.py +1 -0
  244. firefighter/logging/custom_json_formatter.py +159 -0
  245. firefighter/logging/pretty_formatter.py +73 -0
  246. firefighter/pagerduty/__init__.py +0 -0
  247. firefighter/pagerduty/admin.py +139 -0
  248. firefighter/pagerduty/apps.py +17 -0
  249. firefighter/pagerduty/client.py +67 -0
  250. firefighter/pagerduty/forms/__init__.py +0 -0
  251. firefighter/pagerduty/forms/create_pagerduty_incident.py +43 -0
  252. firefighter/pagerduty/migrations/0001_initial_oss.py +463 -0
  253. firefighter/pagerduty/migrations/__init__.py +0 -0
  254. firefighter/pagerduty/models.py +446 -0
  255. firefighter/pagerduty/service.py +78 -0
  256. firefighter/pagerduty/signals/__init__.py +12 -0
  257. firefighter/pagerduty/signals/get_invites_from_pagerduty.py +24 -0
  258. firefighter/pagerduty/signals/incident_channel_done_oncall.py +26 -0
  259. firefighter/pagerduty/tasks/__init__.py +6 -0
  260. firefighter/pagerduty/tasks/fetch_oncall.py +107 -0
  261. firefighter/pagerduty/tasks/fetch_services.py +35 -0
  262. firefighter/pagerduty/tasks/fetch_users.py +66 -0
  263. firefighter/pagerduty/tasks/trigger_oncall.py +96 -0
  264. firefighter/pagerduty/templates/pages/oncall_list.html +84 -0
  265. firefighter/pagerduty/templates/pages/oncall_trigger.html +16 -0
  266. firefighter/pagerduty/templates/partials/trigger_oncall_form_view.html +14 -0
  267. firefighter/pagerduty/templates/partials/trigger_oncall_form_view_modal.html +21 -0
  268. firefighter/pagerduty/urls.py +16 -0
  269. firefighter/pagerduty/views/__init__.py +0 -0
  270. firefighter/pagerduty/views/oncall_list.py +30 -0
  271. firefighter/pagerduty/views/oncall_trigger.py +64 -0
  272. firefighter/raid/__init__.py +6 -0
  273. firefighter/raid/admin.py +125 -0
  274. firefighter/raid/apps.py +56 -0
  275. firefighter/raid/client.py +213 -0
  276. firefighter/raid/forms.py +467 -0
  277. firefighter/raid/messages.py +239 -0
  278. firefighter/raid/migrations/0001_initial_oss.py +175 -0
  279. firefighter/raid/migrations/__init__.py +0 -0
  280. firefighter/raid/models.py +120 -0
  281. firefighter/raid/serializers.py +323 -0
  282. firefighter/raid/service.py +285 -0
  283. firefighter/raid/signals/__init__.py +0 -0
  284. firefighter/raid/signals/incident_created.py +113 -0
  285. firefighter/raid/signals/incident_updated.py +38 -0
  286. firefighter/raid/signals/update_qualifiers_rotation.py +97 -0
  287. firefighter/raid/tasks/__init__.py +4 -0
  288. firefighter/raid/tasks/daily_qualifier.py +67 -0
  289. firefighter/raid/tasks/weekly_qualifier.py +63 -0
  290. firefighter/raid/types.py +23 -0
  291. firefighter/raid/urls.py +33 -0
  292. firefighter/raid/utils.py +42 -0
  293. firefighter/raid/views/__init__.py +133 -0
  294. firefighter/raid/views/open_normal.py +139 -0
  295. firefighter/slack/__init__.py +0 -0
  296. firefighter/slack/admin.py +381 -0
  297. firefighter/slack/apps.py +23 -0
  298. firefighter/slack/factories.py +96 -0
  299. firefighter/slack/forms/__init__.py +0 -0
  300. firefighter/slack/forms/sos_form.py +11 -0
  301. firefighter/slack/management/__init__.py +0 -0
  302. firefighter/slack/management/commands/__init__.py +0 -0
  303. firefighter/slack/management/commands/generate_manifest.py +201 -0
  304. firefighter/slack/messages/__init__.py +0 -0
  305. firefighter/slack/messages/base.py +129 -0
  306. firefighter/slack/messages/slack_messages.py +824 -0
  307. firefighter/slack/migrations/0001_initial_oss.py +348 -0
  308. firefighter/slack/migrations/__init__.py +0 -0
  309. firefighter/slack/models/__init__.py +7 -0
  310. firefighter/slack/models/conversation.py +461 -0
  311. firefighter/slack/models/incident_channel.py +210 -0
  312. firefighter/slack/models/message.py +138 -0
  313. firefighter/slack/models/sos.py +40 -0
  314. firefighter/slack/models/user.py +462 -0
  315. firefighter/slack/models/user_group.py +192 -0
  316. firefighter/slack/rules.py +55 -0
  317. firefighter/slack/signals/__init__.py +7 -0
  318. firefighter/slack/signals/create_incident_conversation.py +134 -0
  319. firefighter/slack/signals/get_users.py +42 -0
  320. firefighter/slack/signals/handle_incident_channel_done.py +54 -0
  321. firefighter/slack/signals/incident_closed.py +22 -0
  322. firefighter/slack/signals/incident_updated.py +242 -0
  323. firefighter/slack/signals/postmortem_created.py +36 -0
  324. firefighter/slack/signals/roles_reminders.py +111 -0
  325. firefighter/slack/slack_app.py +98 -0
  326. firefighter/slack/slack_incident_context.py +227 -0
  327. firefighter/slack/slack_templating.py +122 -0
  328. firefighter/slack/tasks/__init__.py +9 -0
  329. firefighter/slack/tasks/fetch_conversations_members.py +146 -0
  330. firefighter/slack/tasks/reminder_postmortem.py +74 -0
  331. firefighter/slack/tasks/send_message.py +28 -0
  332. firefighter/slack/tasks/send_reminders.py +118 -0
  333. firefighter/slack/tasks/sync_users.py +67 -0
  334. firefighter/slack/tasks/update_usergroups_members.py +123 -0
  335. firefighter/slack/tasks/update_users.py +22 -0
  336. firefighter/slack/urls.py +10 -0
  337. firefighter/slack/utils.py +108 -0
  338. firefighter/slack/views/__init__.py +18 -0
  339. firefighter/slack/views/events/__init__.py +26 -0
  340. firefighter/slack/views/events/actions_and_shortcuts.py +51 -0
  341. firefighter/slack/views/events/channel_archive.py +26 -0
  342. firefighter/slack/views/events/channel_id_changed.py +25 -0
  343. firefighter/slack/views/events/channel_rename.py +25 -0
  344. firefighter/slack/views/events/channel_shared.py +23 -0
  345. firefighter/slack/views/events/channel_unarchive.py +32 -0
  346. firefighter/slack/views/events/channel_unshared.py +26 -0
  347. firefighter/slack/views/events/commands.py +142 -0
  348. firefighter/slack/views/events/home.py +175 -0
  349. firefighter/slack/views/events/member_joined_channel.py +67 -0
  350. firefighter/slack/views/events/member_left_channel.py +58 -0
  351. firefighter/slack/views/events/message.py +68 -0
  352. firefighter/slack/views/events/message_deleted.py +29 -0
  353. firefighter/slack/views/events/reaction_added.py +128 -0
  354. firefighter/slack/views/modals/__init__.py +49 -0
  355. firefighter/slack/views/modals/base_modal/__init__.py +0 -0
  356. firefighter/slack/views/modals/base_modal/base.py +352 -0
  357. firefighter/slack/views/modals/base_modal/base_mixins.py +9 -0
  358. firefighter/slack/views/modals/base_modal/form_utils.py +574 -0
  359. firefighter/slack/views/modals/base_modal/mixins.py +57 -0
  360. firefighter/slack/views/modals/base_modal/modal_utils.py +86 -0
  361. firefighter/slack/views/modals/close.py +273 -0
  362. firefighter/slack/views/modals/downgrade_workflow.py +93 -0
  363. firefighter/slack/views/modals/key_event_message.py +158 -0
  364. firefighter/slack/views/modals/open.py +551 -0
  365. firefighter/slack/views/modals/opening/__init__.py +0 -0
  366. firefighter/slack/views/modals/opening/check_current_incidents.py +82 -0
  367. firefighter/slack/views/modals/opening/details/__init__.py +0 -0
  368. firefighter/slack/views/modals/opening/details/critical.py +86 -0
  369. firefighter/slack/views/modals/opening/select_impact.py +217 -0
  370. firefighter/slack/views/modals/opening/set_details.py +167 -0
  371. firefighter/slack/views/modals/opening/types.py +20 -0
  372. firefighter/slack/views/modals/postmortem.py +83 -0
  373. firefighter/slack/views/modals/select.py +98 -0
  374. firefighter/slack/views/modals/send_sos.py +135 -0
  375. firefighter/slack/views/modals/status.py +117 -0
  376. firefighter/slack/views/modals/trigger_oncall.py +135 -0
  377. firefighter/slack/views/modals/update.py +61 -0
  378. firefighter/slack/views/modals/update_roles.py +76 -0
  379. firefighter/slack/views/modals/update_status.py +126 -0
  380. firefighter/slack/views/views.py +21 -0
  381. firefighter_fixtures/incidents/components.json +602 -0
  382. firefighter_fixtures/incidents/environments.json +41 -0
  383. firefighter_fixtures/incidents/groups.json +156 -0
  384. firefighter_fixtures/incidents/impact_level.json +178 -0
  385. firefighter_fixtures/incidents/impact_type.json +46 -0
  386. firefighter_fixtures/incidents/incident_role_type.json +32 -0
  387. firefighter_fixtures/incidents/metric_type.json +86 -0
  388. firefighter_fixtures/incidents/milestone_type.json +80 -0
  389. firefighter_fixtures/incidents/priorities.json +116 -0
  390. firefighter_fixtures/incidents/severities.json +92 -0
  391. firefighter_fixtures/raid/area.json +1 -0
  392. firefighter_incident-0.0.1.dist-info/METADATA +105 -0
  393. firefighter_incident-0.0.1.dist-info/RECORD +429 -0
  394. firefighter_incident-0.0.1.dist-info/WHEEL +4 -0
  395. firefighter_incident-0.0.1.dist-info/entry_points.txt +3 -0
  396. firefighter_incident-0.0.1.dist-info/licenses/LICENSE +21 -0
  397. firefighter_tests/__init__.py +0 -0
  398. firefighter_tests/conftest.py +90 -0
  399. firefighter_tests/test_api/test_api_landbot.py +125 -0
  400. firefighter_tests/test_api/test_api_urls.py +60 -0
  401. firefighter_tests/test_confluence/test_confluence_utils.py +60 -0
  402. firefighter_tests/test_firefighter/test_firefighter_utils.py +117 -0
  403. firefighter_tests/test_firefighter/test_logging.py +29 -0
  404. firefighter_tests/test_firefighter/test_urls.py +150 -0
  405. firefighter_tests/test_incidents/test_forms/test_form_select_impact.py +104 -0
  406. firefighter_tests/test_incidents/test_forms/test_form_utils.py +74 -0
  407. firefighter_tests/test_incidents/test_forms/test_update_key_events.py +53 -0
  408. firefighter_tests/test_incidents/test_incident_urls.py +116 -0
  409. firefighter_tests/test_incidents/test_models/test_incident_model.py +30 -0
  410. firefighter_tests/test_incidents/test_models/test_migrations/test_incident_migrations.py +0 -0
  411. firefighter_tests/test_incidents/test_utils/test_date_utils.py +207 -0
  412. firefighter_tests/test_incidents/test_views/test_incident_detail_view.py +24 -0
  413. firefighter_tests/test_incidents/test_views/test_index_view.py +27 -0
  414. firefighter_tests/test_raid/test_raid_client_users.py +120 -0
  415. firefighter_tests/test_raid/test_raid_transitions.py +145 -0
  416. firefighter_tests/test_raid/test_raid_utils.py +26 -0
  417. firefighter_tests/test_slack/conftest.py +66 -0
  418. firefighter_tests/test_slack/test_models/test_conversations.py +26 -0
  419. firefighter_tests/test_slack/test_models/test_incident_channel.py +487 -0
  420. firefighter_tests/test_slack/test_models/test_slack_user.py +194 -0
  421. firefighter_tests/test_slack/test_slack_utils.py +126 -0
  422. firefighter_tests/test_slack/views/modals/test_close.py +978 -0
  423. firefighter_tests/test_slack/views/modals/test_open.py +124 -0
  424. firefighter_tests/test_slack/views/modals/test_send_sos.py +42 -0
  425. firefighter_tests/test_slack/views/modals/test_status.py +72 -0
  426. firefighter_tests/test_slack/views/modals/test_update_status.py +878 -0
  427. gunicorn.conf.py +15 -0
  428. main.py +53 -0
  429. manage.py +23 -0
File without changes
@@ -0,0 +1,16 @@
1
+ # file generated by setuptools_scm
2
+ # don't change, don't track in version control
3
+ TYPE_CHECKING = False
4
+ if TYPE_CHECKING:
5
+ from typing import Tuple, Union
6
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
7
+ else:
8
+ VERSION_TUPLE = object
9
+
10
+ version: str
11
+ __version__: str
12
+ __version_tuple__: VERSION_TUPLE
13
+ version_tuple: VERSION_TUPLE
14
+
15
+ __version__ = version = '0.0.1'
16
+ __version_tuple__ = version_tuple = (0, 0, 1)
@@ -0,0 +1,4 @@
1
+ """The `api` app exposes the API endpoints for the application.
2
+
3
+ It uses the `rest_framework` package to provide the API endpoints.
4
+ """
@@ -0,0 +1,105 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ from django.contrib import messages
7
+ from rest_framework.authtoken.admin import TokenAdmin
8
+ from rest_framework.authtoken.models import TokenProxy
9
+
10
+ from firefighter.api.models import APITokenProxy
11
+ from firefighter.firefighter.admin import admin_custom as admin
12
+ from firefighter.incidents.models.user import User
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ if TYPE_CHECKING:
18
+ from django.db.models import ForeignKey
19
+ from django.db.models.query import QuerySet
20
+ from django.forms import ModelChoiceField, ModelForm
21
+ from django.http.request import HttpRequest as BaseHttpRequest
22
+
23
+ class HttpRequest(BaseHttpRequest):
24
+ user: User
25
+
26
+
27
+ class APITokenAdmin(TokenAdmin):
28
+ """Custom Admin for DRF Token.
29
+ Add supports for custom permissions.
30
+ """
31
+
32
+ def formfield_for_foreignkey(self, db_field: ForeignKey[Any, Any], request: HttpRequest, **kwargs: Any) -> ModelChoiceField: # type: ignore[override]
33
+ """Show all or only current user depending on permissions."""
34
+ if db_field.name == "user":
35
+ if request.user.has_perm("api.can_add_any") or request.user.has_perm(
36
+ "api.can_edit_any"
37
+ ):
38
+ kwargs["queryset"] = User.objects.all()
39
+ elif request.user.has_perm("api.can_add_own"):
40
+ kwargs["queryset"] = User.objects.filter(id=request.user.id)
41
+ return super().formfield_for_foreignkey(db_field, request, **kwargs)
42
+
43
+ def get_form(self, request: HttpRequest, obj: APITokenProxy | None = None, change: bool = False, **kwargs: Any) -> type[ModelForm[APITokenProxy]]: # type: ignore[override] # noqa: FBT001, FBT002
44
+ """Prefill the form with the current user."""
45
+ form: type[ModelForm[APITokenProxy]] = super().get_form(
46
+ request, obj, change, **kwargs
47
+ )
48
+ form.base_fields["user"].initial = request.user
49
+ return form
50
+
51
+ def get_queryset(self, request: HttpRequest) -> QuerySet[APITokenProxy]: # type: ignore[override]
52
+ """Show all or only own tokens depending on permissions."""
53
+ qs = super().get_queryset(request)
54
+ if request.user.has_perm("api.can_view_any"):
55
+ return qs
56
+ return qs.filter(user=request.user)
57
+
58
+ def get_sortable_by(self, request: HttpRequest): # type: ignore[no-untyped-def,override]
59
+ """Hack to send a message depending on the status of the user."""
60
+ if request.user.has_perm("api.can_view_any"):
61
+ self.message_user(request, "You are seeing all tokens.", messages.WARNING)
62
+ elif request.user.has_perm("api.can_view_own"):
63
+ self.message_user(
64
+ request, "You are only seeing your tokens.", messages.WARNING
65
+ )
66
+ return super().get_sortable_by(request)
67
+
68
+ def has_view_permission(self, request: HttpRequest, obj: APITokenProxy | None = None) -> bool: # type: ignore[override]
69
+ if obj is None:
70
+ return request.user.has_perm("api.can_view_any") or request.user.has_perm(
71
+ "api.can_view_own"
72
+ )
73
+ if request.user.has_perm("api.can_view_any"):
74
+ return True
75
+ if request.user.has_perm("api.can_view_own"):
76
+ return bool(obj.user == request.user)
77
+ return False
78
+
79
+ def has_add_permission(self, request: HttpRequest) -> bool: # type: ignore[override]
80
+ return request.user.has_perm("api.can_add_any") or request.user.has_perm(
81
+ "api.can_add_own"
82
+ )
83
+
84
+ def has_delete_permission(self, request: HttpRequest, obj: APITokenProxy | None = None) -> bool: # type: ignore[override]
85
+ if obj is None:
86
+ return request.user.has_perm("api.can_delete_any") or request.user.has_perm(
87
+ "api.can_delete_own"
88
+ )
89
+ if request.user.has_perm("api.can_delete_any"):
90
+ return True
91
+ if request.user.has_perm("api.can_delete_own"):
92
+ return bool(obj.user == request.user)
93
+ return False
94
+
95
+ def has_change_permission(self, request: HttpRequest, obj: APITokenProxy | None = None) -> bool: # type: ignore[override]
96
+ if obj is None:
97
+ return request.user.has_perm("api.can_edit_any")
98
+ if request.user.has_perm("api.can_edit_any"):
99
+ return True
100
+ return False
101
+
102
+
103
+ # Remove the default TokenAdmin created by DRF and replace it with our custom one.
104
+ admin.site.unregister(TokenProxy)
105
+ admin.site.register(APITokenProxy, APITokenAdmin)
@@ -0,0 +1,9 @@
1
+ from __future__ import annotations
2
+
3
+ from django.apps import AppConfig
4
+
5
+
6
+ class ApiConfig(AppConfig):
7
+ default_auto_field = "django.db.models.BigAutoField"
8
+ label = "api"
9
+ name = "firefighter.api"
@@ -0,0 +1,20 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from rest_framework.authentication import TokenAuthentication
6
+
7
+ if TYPE_CHECKING:
8
+ from django.db.models import Model
9
+
10
+
11
+ class BearerTokenAuthentication(TokenAuthentication):
12
+ """To use `Authorization: Bearer <token>` instead of `Authorization: Token <token>`."""
13
+
14
+ keyword = "Bearer"
15
+
16
+ def get_model(self) -> type[Model]:
17
+ # ruff: noqa: PLC0415
18
+ from firefighter.api.models import APIToken
19
+
20
+ return APIToken
@@ -0,0 +1,49 @@
1
+ # Generated by Django 3.2.8 on 2021-11-07 16:58
2
+
3
+ from __future__ import annotations
4
+
5
+ from django.db import migrations
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+ initial = True
10
+
11
+ dependencies = [
12
+ ("authtoken", "0003_tokenproxy"),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.CreateModel(
17
+ name="APIToken",
18
+ fields=[],
19
+ options={
20
+ "proxy": True,
21
+ "default_permissions": [],
22
+ "indexes": [],
23
+ "constraints": [],
24
+ },
25
+ bases=("authtoken.token",),
26
+ ),
27
+ migrations.CreateModel(
28
+ name="APITokenProxy",
29
+ fields=[],
30
+ options={
31
+ "verbose_name": "API Token",
32
+ "permissions": (
33
+ ("can_edit_any", "Can reassign token to any user"),
34
+ ("can_add_any", "Can add token to any user"),
35
+ ("can_view_any", "Can view token of all users"),
36
+ ("can_delete_any", "Can delete token of any user"),
37
+ ("can_add_own", "Can add own tokens"),
38
+ ("can_view_own", "Can view own tokens"),
39
+ ("can_delete_own", "Can delete own tokens"),
40
+ ),
41
+ "abstract": False,
42
+ "proxy": True,
43
+ "default_permissions": [],
44
+ "indexes": [],
45
+ "constraints": [],
46
+ },
47
+ bases=("api.apitoken",),
48
+ ),
49
+ ]
@@ -0,0 +1,28 @@
1
+ # Generated by Django 4.2.3 on 2023-07-04 09:06
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("api", "0001_initial"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.AlterModelOptions(
13
+ name="apitokenproxy",
14
+ options={
15
+ "default_permissions": [],
16
+ "permissions": [
17
+ ("can_edit_any", "Can reassign token to any user"),
18
+ ("can_add_any", "Can add token to any user"),
19
+ ("can_view_any", "Can view token of all users"),
20
+ ("can_delete_any", "Can delete token of any user"),
21
+ ("can_add_own", "Can add own tokens"),
22
+ ("can_view_own", "Can view own tokens"),
23
+ ("can_delete_own", "Can delete own tokens"),
24
+ ],
25
+ "verbose_name": "API Token",
26
+ },
27
+ ),
28
+ ]
File without changes
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ import uuid
4
+ from typing import TYPE_CHECKING, ClassVar, Self, cast
5
+
6
+ from django.conf import settings
7
+ from django.utils.translation import gettext_lazy as _
8
+ from django_stubs_ext.db.models import TypedModelMeta
9
+ from rest_framework.authtoken.models import Token
10
+
11
+ if TYPE_CHECKING:
12
+ from collections.abc import Sequence
13
+
14
+
15
+ class APIToken(Token):
16
+ class Meta(TypedModelMeta):
17
+ default_permissions: ClassVar[Sequence[str]] = []
18
+ proxy = True
19
+
20
+
21
+ class APITokenProxy(APIToken):
22
+ """Proxy mapping pk to user pk for use in admin.
23
+
24
+ Overrides default permissions.
25
+ """
26
+
27
+ @property
28
+ def pk(self: Self) -> uuid.UUID:
29
+ return cast(uuid.UUID, self.user_id) # pyright: ignore[reportGeneralTypeIssues]
30
+
31
+ class Meta(TypedModelMeta):
32
+ permissions = [
33
+ ("can_edit_any", "Can reassign token to any user"),
34
+ ("can_add_any", "Can add token to any user"),
35
+ ("can_view_any", "Can view token of all users"),
36
+ ("can_delete_any", "Can delete token of any user"),
37
+ ("can_add_own", "Can add own tokens"),
38
+ ("can_view_own", "Can view own tokens"),
39
+ ("can_delete_own", "Can delete own tokens"),
40
+ ]
41
+ default_permissions: ClassVar[Sequence[str]] = []
42
+ proxy = "rest_framework.authtoken" in settings.INSTALLED_APPS
43
+ abstract = "rest_framework.authtoken" not in settings.INSTALLED_APPS
44
+ verbose_name = _("API Token")
45
+ verbose_name_plural = _("API Tokens")
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ from rest_framework.permissions import DjangoModelPermissions
4
+
5
+
6
+ class StrictDjangoModelPermissions(DjangoModelPermissions):
7
+ """Custom class to restrict GET requests."""
8
+
9
+ # Map methods into required permission codes.
10
+ perms_map = {
11
+ "GET": ["%(app_label)s.view_%(model_name)s"],
12
+ "OPTIONS": [],
13
+ "HEAD": [],
14
+ "POST": ["%(app_label)s.add_%(model_name)s"],
15
+ "PUT": ["%(app_label)s.change_%(model_name)s"],
16
+ "PATCH": ["%(app_label)s.change_%(model_name)s"],
17
+ "DELETE": ["%(app_label)s.delete_%(model_name)s"],
18
+ }
@@ -0,0 +1,115 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import re
5
+ from functools import cache
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ from rest_framework_csv.renderers import CSVRenderer as BaseCSVRenderer
9
+
10
+ if TYPE_CHECKING:
11
+ from collections.abc import Generator, Iterable
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class CSVRenderer(BaseCSVRenderer):
17
+ """Renderer which serializes to CSV
18
+ Override the default CSV Renderer to allow hiding header fields.
19
+ Hide head by setting labels to "__hidden__".
20
+ """
21
+
22
+ def tablize(
23
+ self, data: Any, header: Any | None = None, labels: Any | None = None
24
+ ) -> Generator[list[Any], None, None]:
25
+ """Convert a list of data into a table.
26
+
27
+ If there is a header provided to tablize it will efficiently yield each
28
+ row as needed. If no header is provided, tablize will need to process
29
+ each row in the data in order to construct a complete header. Thus, if
30
+ you have a lot of data and want to stream it, you should probably
31
+ provide a header to the renderer (using the `header` attribute, or via
32
+ the `renderer_context`).
33
+ """
34
+ # Try to pull the header off of the data, if it's not passed in as an
35
+ # argument.
36
+ if not header and hasattr(data, "header"):
37
+ header = data.header
38
+
39
+ if data:
40
+ # First, flatten the data (i.e., convert it to a list of
41
+ # dictionaries that are each exactly one level deep). The key for
42
+ # each item designates the name of the column that the item will
43
+ # fall into.
44
+ data = self.flatten_data(data)
45
+
46
+ # Get the set of all unique headers, and sort them (unless already provided).
47
+ data, header = self._get_headers(data, header)
48
+
49
+ # Return your "table", with the headers as the first row.
50
+ if labels != "__hidden__":
51
+ if labels:
52
+ yield [labels.get(x, x) for x in header]
53
+ else:
54
+ yield header
55
+
56
+ # Create a row for each dictionary, filling in columns for which the
57
+ # item has no data with None values.
58
+ for item in data:
59
+ row = [item.get(key, None) for key in header]
60
+ yield row
61
+
62
+ elif header:
63
+ # If there's no data but a header was supplied, yield the header.
64
+ if labels:
65
+ yield [labels.get(x, x) for x in header]
66
+ else:
67
+ yield header
68
+
69
+ else:
70
+ # Generator will yield nothing if there's no data and no header
71
+ pass
72
+
73
+ def _get_headers(
74
+ self, data: Iterable[Any], header: None | list[str]
75
+ ) -> tuple[Iterable[Any], list[str]]:
76
+ # If we already have a header, and it does not contain any wildcards,
77
+ # we can use it as-is.
78
+ has_wildcards = any(".*." in x for x in header) if header else False
79
+ if header and not has_wildcards:
80
+ return data, header
81
+
82
+ # We have to materialize the data in order to get the headers.
83
+ data = tuple(data)
84
+ header_fields: set[str] = set()
85
+ for item in data:
86
+ header_fields.update(list(item.keys()))
87
+
88
+ if not has_wildcards:
89
+ return data, sorted(header_fields)
90
+ if header is None:
91
+ header = sorted(header_fields)
92
+
93
+ to_expand: list[str] = [x for x in header if ".*." in x]
94
+ expanded_headers: dict[str, list[str]] = {
95
+ key: sorted([x for x in header_fields if self._get_regex(key).match(x)])
96
+ for key in to_expand
97
+ }
98
+
99
+ return data, [x for h in header for x in expanded_headers.get(h, [h])]
100
+
101
+ @staticmethod
102
+ @cache
103
+ def _get_regex(header: str) -> re.Pattern[str]:
104
+ regex = header.replace(".*.", ".+")
105
+ return re.compile(f"^{regex}$")
106
+
107
+
108
+ class TSVRenderer(CSVRenderer):
109
+ """Renderer which serializes to TSV."""
110
+
111
+ media_type = "text/tab-separated-values"
112
+ format = "tsv"
113
+ writer_opts = {
114
+ "dialect": "excel-tab",
115
+ }