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.
- firefighter/__init__.py +0 -0
- firefighter/_version.py +16 -0
- firefighter/api/__init__.py +4 -0
- firefighter/api/admin.py +105 -0
- firefighter/api/apps.py +9 -0
- firefighter/api/authentication.py +20 -0
- firefighter/api/migrations/0001_initial.py +49 -0
- firefighter/api/migrations/0002_alter_apitokenproxy_options.py +28 -0
- firefighter/api/migrations/__init__.py +0 -0
- firefighter/api/models.py +45 -0
- firefighter/api/permissions.py +18 -0
- firefighter/api/renderer.py +115 -0
- firefighter/api/serializers.py +291 -0
- firefighter/api/urls.py +79 -0
- firefighter/api/views/__init__.py +13 -0
- firefighter/api/views/_base.py +80 -0
- firefighter/api/views/components.py +10 -0
- firefighter/api/views/environments.py +10 -0
- firefighter/api/views/groups.py +10 -0
- firefighter/api/views/incident_cost_types.py +10 -0
- firefighter/api/views/incident_costs.py +10 -0
- firefighter/api/views/incidents.py +263 -0
- firefighter/api/views/severities.py +10 -0
- firefighter/components/__init__.py +11 -0
- firefighter/components/avatar/__init__.py +0 -0
- firefighter/components/avatar/avatar.html +11 -0
- firefighter/components/avatar/avatar.py +20 -0
- firefighter/components/card/__init__.py +0 -0
- firefighter/components/card/card.html +17 -0
- firefighter/components/card/card.py +16 -0
- firefighter/components/export_button/__init__.py +0 -0
- firefighter/components/export_button/export_button.html +33 -0
- firefighter/components/export_button/export_button.py +41 -0
- firefighter/components/form/__init__.py +0 -0
- firefighter/components/form/form.html +18 -0
- firefighter/components/form/form.py +23 -0
- firefighter/components/form_field/__init__.py +0 -0
- firefighter/components/form_field/form_field.html +18 -0
- firefighter/components/form_field/form_field.py +26 -0
- firefighter/components/messages/__init__.py +0 -0
- firefighter/components/messages/messages.html +52 -0
- firefighter/components/messages/messages.py +16 -0
- firefighter/components/modal/__init__.py +0 -0
- firefighter/components/modal/modal.html +64 -0
- firefighter/components/modal/modal.py +20 -0
- firefighter/confluence/__init__.py +0 -0
- firefighter/confluence/admin.py +52 -0
- firefighter/confluence/apps.py +15 -0
- firefighter/confluence/client.py +192 -0
- firefighter/confluence/management/__init__.py +0 -0
- firefighter/confluence/management/commands/__init__.py +0 -0
- firefighter/confluence/management/commands/sort_postmortems.py +25 -0
- firefighter/confluence/management/commands/sort_runbooks.py +23 -0
- firefighter/confluence/management/commands/sync_postmortems.py +16 -0
- firefighter/confluence/management/commands/sync_runbooks.py +14 -0
- firefighter/confluence/migrations/0001_initial_oss.py +104 -0
- firefighter/confluence/migrations/__init__.py +0 -0
- firefighter/confluence/models.py +189 -0
- firefighter/confluence/serializers.py +11 -0
- firefighter/confluence/service.py +336 -0
- firefighter/confluence/signals/__init__.py +3 -0
- firefighter/confluence/signals/incident_updated.py +54 -0
- firefighter/confluence/tables.py +22 -0
- firefighter/confluence/tasks/__init__.py +9 -0
- firefighter/confluence/tasks/archive_postmortems.py +223 -0
- firefighter/confluence/tasks/sort_runbooks.py +85 -0
- firefighter/confluence/tasks/sync_pages_content.py +34 -0
- firefighter/confluence/tasks/sync_postmortems.py +71 -0
- firefighter/confluence/tasks/sync_runbooks.py +88 -0
- firefighter/confluence/templates/oncall_team.xml +6 -0
- firefighter/confluence/templates/pages/runbook_list.html +20 -0
- firefighter/confluence/urls.py +22 -0
- firefighter/confluence/utils.py +103 -0
- firefighter/confluence/views/__init__.py +0 -0
- firefighter/confluence/views/api.py +10 -0
- firefighter/confluence/views/postmortem/__init__.py +0 -0
- firefighter/confluence/views/postmortem/postmortem_detail.py +0 -0
- firefighter/confluence/views/runbook/__init__.py +0 -0
- firefighter/confluence/views/runbook/runbook_list.py +46 -0
- firefighter/firefighter/__init__.py +26 -0
- firefighter/firefighter/admin.py +12 -0
- firefighter/firefighter/apps.py +50 -0
- firefighter/firefighter/asgi.py +17 -0
- firefighter/firefighter/celery_client.py +40 -0
- firefighter/firefighter/fields_forms_widgets.py +92 -0
- firefighter/firefighter/filters.py +144 -0
- firefighter/firefighter/formats/__init__.py +1 -0
- firefighter/firefighter/formats/en/__init__.py +0 -0
- firefighter/firefighter/formats/en/formats.py +5 -0
- firefighter/firefighter/http_client.py +56 -0
- firefighter/firefighter/management/__init__.py +0 -0
- firefighter/firefighter/management/commands/__init__.py +0 -0
- firefighter/firefighter/management/commands/task.py +35 -0
- firefighter/firefighter/middleware.py +35 -0
- firefighter/firefighter/settings/__init__.py +53 -0
- firefighter/firefighter/settings/components/__init__.py +0 -0
- firefighter/firefighter/settings/components/api.py +173 -0
- firefighter/firefighter/settings/components/caches.py +41 -0
- firefighter/firefighter/settings/components/celery.py +39 -0
- firefighter/firefighter/settings/components/common.py +308 -0
- firefighter/firefighter/settings/components/confluence.py +43 -0
- firefighter/firefighter/settings/components/jira_app.py +21 -0
- firefighter/firefighter/settings/components/logging.py +105 -0
- firefighter/firefighter/settings/components/pagerduty.py +17 -0
- firefighter/firefighter/settings/components/raid.py +22 -0
- firefighter/firefighter/settings/components/slack.py +53 -0
- firefighter/firefighter/settings/environments/__init__.py +3 -0
- firefighter/firefighter/settings/environments/dev.py +204 -0
- firefighter/firefighter/settings/environments/prod.py +61 -0
- firefighter/firefighter/settings/settings_builder.py +41 -0
- firefighter/firefighter/settings/settings_utils.py +25 -0
- firefighter/firefighter/sso.py +44 -0
- firefighter/firefighter/tables_utils.py +19 -0
- firefighter/firefighter/templates/admin/base.html +106 -0
- firefighter/firefighter/templates/admin/login.html +14 -0
- firefighter/firefighter/templates/admin/send_message_conversation.html +45 -0
- firefighter/firefighter/templates/robots.txt +2 -0
- firefighter/firefighter/urls.py +113 -0
- firefighter/firefighter/utils.py +92 -0
- firefighter/firefighter/views.py +71 -0
- firefighter/firefighter/wsgi.py +22 -0
- firefighter/incidents/__init__.py +0 -0
- firefighter/incidents/admin.py +737 -0
- firefighter/incidents/apps.py +13 -0
- firefighter/incidents/enums.py +32 -0
- firefighter/incidents/factories.py +92 -0
- firefighter/incidents/forms/__init__.py +3 -0
- firefighter/incidents/forms/close_incident.py +38 -0
- firefighter/incidents/forms/create_incident.py +92 -0
- firefighter/incidents/forms/select_impact.py +123 -0
- firefighter/incidents/forms/update_key_events.py +115 -0
- firefighter/incidents/forms/update_roles.py +95 -0
- firefighter/incidents/forms/update_status.py +36 -0
- firefighter/incidents/forms/utils.py +97 -0
- firefighter/incidents/menus.py +162 -0
- firefighter/incidents/migrations/0001_initial_oss.py +1617 -0
- firefighter/incidents/migrations/__init__.py +0 -0
- firefighter/incidents/models/__init__.py +15 -0
- firefighter/incidents/models/component.py +226 -0
- firefighter/incidents/models/environment.py +26 -0
- firefighter/incidents/models/group.py +20 -0
- firefighter/incidents/models/impact.py +125 -0
- firefighter/incidents/models/incident.py +706 -0
- firefighter/incidents/models/incident_cost.py +48 -0
- firefighter/incidents/models/incident_cost_type.py +27 -0
- firefighter/incidents/models/incident_membership.py +58 -0
- firefighter/incidents/models/incident_role_type.py +65 -0
- firefighter/incidents/models/incident_update.py +136 -0
- firefighter/incidents/models/metric_type.py +89 -0
- firefighter/incidents/models/milestone_type.py +65 -0
- firefighter/incidents/models/priority.py +65 -0
- firefighter/incidents/models/severity.py +56 -0
- firefighter/incidents/models/user.py +94 -0
- firefighter/incidents/signals.py +78 -0
- firefighter/incidents/static/css/incident.css +171 -0
- firefighter/incidents/static/css/main.css +2 -0
- firefighter/incidents/static/css/main.min.css +1 -0
- firefighter/incidents/static/css/tailwind.css +69 -0
- firefighter/incidents/static/img/favicon/android-chrome-192x192.png +0 -0
- firefighter/incidents/static/img/favicon/android-chrome-512x512.png +0 -0
- firefighter/incidents/static/img/favicon/apple-touch-icon.png +0 -0
- firefighter/incidents/static/img/favicon/favicon-16x16.png +0 -0
- firefighter/incidents/static/img/favicon/favicon-32x32.png +0 -0
- firefighter/incidents/static/img/favicon/favicon.ico +0 -0
- firefighter/incidents/static/img/favicon/site.webmanifest +1 -0
- firefighter/incidents/static/img/gameday.png +0 -0
- firefighter/incidents/static/img/logo-firefighter.png +0 -0
- firefighter/incidents/static/img/p1.png +0 -0
- firefighter/incidents/static/img/p2.png +0 -0
- firefighter/incidents/static/img/p3.png +0 -0
- firefighter/incidents/static/img/p4.png +0 -0
- firefighter/incidents/static/img/p5.png +0 -0
- firefighter/incidents/static/js/main.js +24 -0
- firefighter/incidents/static/js/main.min.js +15 -0
- firefighter/incidents/tables.py +108 -0
- firefighter/incidents/tasks/__init__.py +3 -0
- firefighter/incidents/tasks/updateoncall.py +115 -0
- firefighter/incidents/templates/incidents/errors/base.html +21 -0
- firefighter/incidents/templates/incidents/filter.html +80 -0
- firefighter/incidents/templates/incidents/table/priority_column.html +8 -0
- firefighter/incidents/templates/incidents/table/status_column.html +1 -0
- firefighter/incidents/templates/incidents/table.html +57 -0
- firefighter/incidents/templates/incidents/widgets/form_container.html +91 -0
- firefighter/incidents/templates/incidents/widgets/grouped_checkbox_nested.html +97 -0
- firefighter/incidents/templates/incidents/widgets/input_option.html +4 -0
- firefighter/incidents/templates/layouts/index.html +25 -0
- firefighter/incidents/templates/layouts/partials/created_at_help.html +58 -0
- firefighter/incidents/templates/layouts/partials/environment_pill.html +11 -0
- firefighter/incidents/templates/layouts/partials/footer.html +34 -0
- firefighter/incidents/templates/layouts/partials/header.html +89 -0
- firefighter/incidents/templates/layouts/partials/incident_card.html +28 -0
- firefighter/incidents/templates/layouts/partials/incident_metrics.html +26 -0
- firefighter/incidents/templates/layouts/partials/incident_timeline.html +117 -0
- firefighter/incidents/templates/layouts/partials/incident_update_key_events_view.html +13 -0
- firefighter/incidents/templates/layouts/partials/incident_update_key_events_view_modal.html +22 -0
- firefighter/incidents/templates/layouts/partials/partial_table_list_paginated.html +108 -0
- firefighter/incidents/templates/layouts/partials/priority_icon.html +14 -0
- firefighter/incidents/templates/layouts/partials/priority_pill.html +4 -0
- firefighter/incidents/templates/layouts/partials/status_pill.html +18 -0
- firefighter/incidents/templates/layouts/partials/table.html +30 -0
- firefighter/incidents/templates/layouts/partials/user_card.html +14 -0
- firefighter/incidents/templates/layouts/partials/user_tooltip.html +13 -0
- firefighter/incidents/templates/layouts/view_filters.html +30 -0
- firefighter/incidents/templates/pages/component_detail.html +92 -0
- firefighter/incidents/templates/pages/component_list.html +22 -0
- firefighter/incidents/templates/pages/dashboard.html +40 -0
- firefighter/incidents/templates/pages/docs_metrics.html +84 -0
- firefighter/incidents/templates/pages/incident_create.html +53 -0
- firefighter/incidents/templates/pages/incident_detail.html +233 -0
- firefighter/incidents/templates/pages/incident_list.html +29 -0
- firefighter/incidents/templates/pages/incident_role_types_detail.html +88 -0
- firefighter/incidents/templates/pages/incident_role_types_list.html +32 -0
- firefighter/incidents/templates/pages/incident_statistics.html +124 -0
- firefighter/incidents/templates/pages/incident_statistics_partial.html +121 -0
- firefighter/incidents/templates/pages/incident_update_key_events_form.html +16 -0
- firefighter/incidents/templates/pages/user_detail.html +110 -0
- firefighter/incidents/urls.py +67 -0
- firefighter/incidents/views/__init__.py +0 -0
- firefighter/incidents/views/components/__init__.py +0 -0
- firefighter/incidents/views/components/details.py +33 -0
- firefighter/incidents/views/components/list.py +63 -0
- firefighter/incidents/views/date_filter.py +166 -0
- firefighter/incidents/views/date_utils.py +189 -0
- firefighter/incidents/views/docs/__init__.py +0 -0
- firefighter/incidents/views/docs/metrics.py +47 -0
- firefighter/incidents/views/docs/role_types.py +35 -0
- firefighter/incidents/views/errors.py +249 -0
- firefighter/incidents/views/reports.py +564 -0
- firefighter/incidents/views/users/__init__.py +0 -0
- firefighter/incidents/views/users/details.py +60 -0
- firefighter/incidents/views/views.py +297 -0
- firefighter/jira_app/__init__.py +0 -0
- firefighter/jira_app/admin.py +24 -0
- firefighter/jira_app/apps.py +15 -0
- firefighter/jira_app/client.py +455 -0
- firefighter/jira_app/migrations/0001_initial_oss.py +84 -0
- firefighter/jira_app/migrations/__init__.py +0 -0
- firefighter/jira_app/models.py +61 -0
- firefighter/jira_app/tasks/__init__.py +3 -0
- firefighter/jira_app/tasks/sync_users_jira.py +47 -0
- firefighter/jira_app/types.py +44 -0
- firefighter/jira_app/utils.py +112 -0
- firefighter/logging/__init__.py +1 -0
- firefighter/logging/custom_json_formatter.py +159 -0
- firefighter/logging/pretty_formatter.py +73 -0
- firefighter/pagerduty/__init__.py +0 -0
- firefighter/pagerduty/admin.py +139 -0
- firefighter/pagerduty/apps.py +17 -0
- firefighter/pagerduty/client.py +67 -0
- firefighter/pagerduty/forms/__init__.py +0 -0
- firefighter/pagerduty/forms/create_pagerduty_incident.py +43 -0
- firefighter/pagerduty/migrations/0001_initial_oss.py +463 -0
- firefighter/pagerduty/migrations/__init__.py +0 -0
- firefighter/pagerduty/models.py +446 -0
- firefighter/pagerduty/service.py +78 -0
- firefighter/pagerduty/signals/__init__.py +12 -0
- firefighter/pagerduty/signals/get_invites_from_pagerduty.py +24 -0
- firefighter/pagerduty/signals/incident_channel_done_oncall.py +26 -0
- firefighter/pagerduty/tasks/__init__.py +6 -0
- firefighter/pagerduty/tasks/fetch_oncall.py +107 -0
- firefighter/pagerduty/tasks/fetch_services.py +35 -0
- firefighter/pagerduty/tasks/fetch_users.py +66 -0
- firefighter/pagerduty/tasks/trigger_oncall.py +96 -0
- firefighter/pagerduty/templates/pages/oncall_list.html +84 -0
- firefighter/pagerduty/templates/pages/oncall_trigger.html +16 -0
- firefighter/pagerduty/templates/partials/trigger_oncall_form_view.html +14 -0
- firefighter/pagerduty/templates/partials/trigger_oncall_form_view_modal.html +21 -0
- firefighter/pagerduty/urls.py +16 -0
- firefighter/pagerduty/views/__init__.py +0 -0
- firefighter/pagerduty/views/oncall_list.py +30 -0
- firefighter/pagerduty/views/oncall_trigger.py +64 -0
- firefighter/raid/__init__.py +6 -0
- firefighter/raid/admin.py +125 -0
- firefighter/raid/apps.py +56 -0
- firefighter/raid/client.py +213 -0
- firefighter/raid/forms.py +467 -0
- firefighter/raid/messages.py +239 -0
- firefighter/raid/migrations/0001_initial_oss.py +175 -0
- firefighter/raid/migrations/__init__.py +0 -0
- firefighter/raid/models.py +120 -0
- firefighter/raid/serializers.py +323 -0
- firefighter/raid/service.py +285 -0
- firefighter/raid/signals/__init__.py +0 -0
- firefighter/raid/signals/incident_created.py +113 -0
- firefighter/raid/signals/incident_updated.py +38 -0
- firefighter/raid/signals/update_qualifiers_rotation.py +97 -0
- firefighter/raid/tasks/__init__.py +4 -0
- firefighter/raid/tasks/daily_qualifier.py +67 -0
- firefighter/raid/tasks/weekly_qualifier.py +63 -0
- firefighter/raid/types.py +23 -0
- firefighter/raid/urls.py +33 -0
- firefighter/raid/utils.py +42 -0
- firefighter/raid/views/__init__.py +133 -0
- firefighter/raid/views/open_normal.py +139 -0
- firefighter/slack/__init__.py +0 -0
- firefighter/slack/admin.py +381 -0
- firefighter/slack/apps.py +23 -0
- firefighter/slack/factories.py +96 -0
- firefighter/slack/forms/__init__.py +0 -0
- firefighter/slack/forms/sos_form.py +11 -0
- firefighter/slack/management/__init__.py +0 -0
- firefighter/slack/management/commands/__init__.py +0 -0
- firefighter/slack/management/commands/generate_manifest.py +201 -0
- firefighter/slack/messages/__init__.py +0 -0
- firefighter/slack/messages/base.py +129 -0
- firefighter/slack/messages/slack_messages.py +824 -0
- firefighter/slack/migrations/0001_initial_oss.py +348 -0
- firefighter/slack/migrations/__init__.py +0 -0
- firefighter/slack/models/__init__.py +7 -0
- firefighter/slack/models/conversation.py +461 -0
- firefighter/slack/models/incident_channel.py +210 -0
- firefighter/slack/models/message.py +138 -0
- firefighter/slack/models/sos.py +40 -0
- firefighter/slack/models/user.py +462 -0
- firefighter/slack/models/user_group.py +192 -0
- firefighter/slack/rules.py +55 -0
- firefighter/slack/signals/__init__.py +7 -0
- firefighter/slack/signals/create_incident_conversation.py +134 -0
- firefighter/slack/signals/get_users.py +42 -0
- firefighter/slack/signals/handle_incident_channel_done.py +54 -0
- firefighter/slack/signals/incident_closed.py +22 -0
- firefighter/slack/signals/incident_updated.py +242 -0
- firefighter/slack/signals/postmortem_created.py +36 -0
- firefighter/slack/signals/roles_reminders.py +111 -0
- firefighter/slack/slack_app.py +98 -0
- firefighter/slack/slack_incident_context.py +227 -0
- firefighter/slack/slack_templating.py +122 -0
- firefighter/slack/tasks/__init__.py +9 -0
- firefighter/slack/tasks/fetch_conversations_members.py +146 -0
- firefighter/slack/tasks/reminder_postmortem.py +74 -0
- firefighter/slack/tasks/send_message.py +28 -0
- firefighter/slack/tasks/send_reminders.py +118 -0
- firefighter/slack/tasks/sync_users.py +67 -0
- firefighter/slack/tasks/update_usergroups_members.py +123 -0
- firefighter/slack/tasks/update_users.py +22 -0
- firefighter/slack/urls.py +10 -0
- firefighter/slack/utils.py +108 -0
- firefighter/slack/views/__init__.py +18 -0
- firefighter/slack/views/events/__init__.py +26 -0
- firefighter/slack/views/events/actions_and_shortcuts.py +51 -0
- firefighter/slack/views/events/channel_archive.py +26 -0
- firefighter/slack/views/events/channel_id_changed.py +25 -0
- firefighter/slack/views/events/channel_rename.py +25 -0
- firefighter/slack/views/events/channel_shared.py +23 -0
- firefighter/slack/views/events/channel_unarchive.py +32 -0
- firefighter/slack/views/events/channel_unshared.py +26 -0
- firefighter/slack/views/events/commands.py +142 -0
- firefighter/slack/views/events/home.py +175 -0
- firefighter/slack/views/events/member_joined_channel.py +67 -0
- firefighter/slack/views/events/member_left_channel.py +58 -0
- firefighter/slack/views/events/message.py +68 -0
- firefighter/slack/views/events/message_deleted.py +29 -0
- firefighter/slack/views/events/reaction_added.py +128 -0
- firefighter/slack/views/modals/__init__.py +49 -0
- firefighter/slack/views/modals/base_modal/__init__.py +0 -0
- firefighter/slack/views/modals/base_modal/base.py +352 -0
- firefighter/slack/views/modals/base_modal/base_mixins.py +9 -0
- firefighter/slack/views/modals/base_modal/form_utils.py +574 -0
- firefighter/slack/views/modals/base_modal/mixins.py +57 -0
- firefighter/slack/views/modals/base_modal/modal_utils.py +86 -0
- firefighter/slack/views/modals/close.py +273 -0
- firefighter/slack/views/modals/downgrade_workflow.py +93 -0
- firefighter/slack/views/modals/key_event_message.py +158 -0
- firefighter/slack/views/modals/open.py +551 -0
- firefighter/slack/views/modals/opening/__init__.py +0 -0
- firefighter/slack/views/modals/opening/check_current_incidents.py +82 -0
- firefighter/slack/views/modals/opening/details/__init__.py +0 -0
- firefighter/slack/views/modals/opening/details/critical.py +86 -0
- firefighter/slack/views/modals/opening/select_impact.py +217 -0
- firefighter/slack/views/modals/opening/set_details.py +167 -0
- firefighter/slack/views/modals/opening/types.py +20 -0
- firefighter/slack/views/modals/postmortem.py +83 -0
- firefighter/slack/views/modals/select.py +98 -0
- firefighter/slack/views/modals/send_sos.py +135 -0
- firefighter/slack/views/modals/status.py +117 -0
- firefighter/slack/views/modals/trigger_oncall.py +135 -0
- firefighter/slack/views/modals/update.py +61 -0
- firefighter/slack/views/modals/update_roles.py +76 -0
- firefighter/slack/views/modals/update_status.py +126 -0
- firefighter/slack/views/views.py +21 -0
- firefighter_fixtures/incidents/components.json +602 -0
- firefighter_fixtures/incidents/environments.json +41 -0
- firefighter_fixtures/incidents/groups.json +156 -0
- firefighter_fixtures/incidents/impact_level.json +178 -0
- firefighter_fixtures/incidents/impact_type.json +46 -0
- firefighter_fixtures/incidents/incident_role_type.json +32 -0
- firefighter_fixtures/incidents/metric_type.json +86 -0
- firefighter_fixtures/incidents/milestone_type.json +80 -0
- firefighter_fixtures/incidents/priorities.json +116 -0
- firefighter_fixtures/incidents/severities.json +92 -0
- firefighter_fixtures/raid/area.json +1 -0
- firefighter_incident-0.0.1.dist-info/METADATA +105 -0
- firefighter_incident-0.0.1.dist-info/RECORD +429 -0
- firefighter_incident-0.0.1.dist-info/WHEEL +4 -0
- firefighter_incident-0.0.1.dist-info/entry_points.txt +3 -0
- firefighter_incident-0.0.1.dist-info/licenses/LICENSE +21 -0
- firefighter_tests/__init__.py +0 -0
- firefighter_tests/conftest.py +90 -0
- firefighter_tests/test_api/test_api_landbot.py +125 -0
- firefighter_tests/test_api/test_api_urls.py +60 -0
- firefighter_tests/test_confluence/test_confluence_utils.py +60 -0
- firefighter_tests/test_firefighter/test_firefighter_utils.py +117 -0
- firefighter_tests/test_firefighter/test_logging.py +29 -0
- firefighter_tests/test_firefighter/test_urls.py +150 -0
- firefighter_tests/test_incidents/test_forms/test_form_select_impact.py +104 -0
- firefighter_tests/test_incidents/test_forms/test_form_utils.py +74 -0
- firefighter_tests/test_incidents/test_forms/test_update_key_events.py +53 -0
- firefighter_tests/test_incidents/test_incident_urls.py +116 -0
- firefighter_tests/test_incidents/test_models/test_incident_model.py +30 -0
- firefighter_tests/test_incidents/test_models/test_migrations/test_incident_migrations.py +0 -0
- firefighter_tests/test_incidents/test_utils/test_date_utils.py +207 -0
- firefighter_tests/test_incidents/test_views/test_incident_detail_view.py +24 -0
- firefighter_tests/test_incidents/test_views/test_index_view.py +27 -0
- firefighter_tests/test_raid/test_raid_client_users.py +120 -0
- firefighter_tests/test_raid/test_raid_transitions.py +145 -0
- firefighter_tests/test_raid/test_raid_utils.py +26 -0
- firefighter_tests/test_slack/conftest.py +66 -0
- firefighter_tests/test_slack/test_models/test_conversations.py +26 -0
- firefighter_tests/test_slack/test_models/test_incident_channel.py +487 -0
- firefighter_tests/test_slack/test_models/test_slack_user.py +194 -0
- firefighter_tests/test_slack/test_slack_utils.py +126 -0
- firefighter_tests/test_slack/views/modals/test_close.py +978 -0
- firefighter_tests/test_slack/views/modals/test_open.py +124 -0
- firefighter_tests/test_slack/views/modals/test_send_sos.py +42 -0
- firefighter_tests/test_slack/views/modals/test_status.py +72 -0
- firefighter_tests/test_slack/views/modals/test_update_status.py +878 -0
- gunicorn.conf.py +15 -0
- main.py +53 -0
- manage.py +23 -0
firefighter/__init__.py
ADDED
|
File without changes
|
firefighter/_version.py
ADDED
|
@@ -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)
|
firefighter/api/admin.py
ADDED
|
@@ -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)
|
firefighter/api/apps.py
ADDED
|
@@ -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
|
+
}
|