trueppm-api 0.4.0b2__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.
- trueppm_api/__init__.py +14 -0
- trueppm_api/apps/access/__init__.py +0 -0
- trueppm_api/apps/access/apps.py +19 -0
- trueppm_api/apps/access/groups.py +518 -0
- trueppm_api/apps/access/management/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/create_admin.py +164 -0
- trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
- trueppm_api/apps/access/management/commands/revoke_api_tokens.py +209 -0
- trueppm_api/apps/access/migrations/0001_initial.py +61 -0
- trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +167 -0
- trueppm_api/apps/access/migrations/0002_alter_projectmembership_id.py +29 -0
- trueppm_api/apps/access/migrations/0003_projectmembership_soft_delete.py +24 -0
- trueppm_api/apps/access/migrations/0004_alter_projectmembership_role.py +34 -0
- trueppm_api/apps/access/migrations/0005_program_entity_and_membership.py +76 -0
- trueppm_api/apps/access/migrations/0006_role_ordinal_spacing.py +113 -0
- trueppm_api/apps/access/migrations/0007_projectmembership_joined_at_and_more.py +35 -0
- trueppm_api/apps/access/migrations/0008_projectmembership_source_group.py +25 -0
- trueppm_api/apps/access/migrations/0009_projectmembership_pm_proj_serverver_idx.py +20 -0
- trueppm_api/apps/access/migrations/0010_programmembership_joined_at_and_more.py +38 -0
- trueppm_api/apps/access/migrations/0011_alter_programmembership_unique_together_and_more.py +36 -0
- trueppm_api/apps/access/migrations/0012_programmembership_role_title.py +17 -0
- trueppm_api/apps/access/migrations/0013_userdefinedmentiongroup.py +82 -0
- trueppm_api/apps/access/migrations/0014_programmembership_progm_serverver_idx.py +19 -0
- trueppm_api/apps/access/migrations/0015_programuserdefinedmentiongroup.py +86 -0
- trueppm_api/apps/access/migrations/0016_externalstakeholder.py +67 -0
- trueppm_api/apps/access/migrations/0017_viewer_ordinal_one.py +132 -0
- trueppm_api/apps/access/migrations/0018_programmembership_sync_seq_and_more.py +52 -0
- trueppm_api/apps/access/migrations/0019_programmembership_reinstated_at_and_more.py +22 -0
- trueppm_api/apps/access/migrations/__init__.py +0 -0
- trueppm_api/apps/access/models.py +588 -0
- trueppm_api/apps/access/permissions.py +2837 -0
- trueppm_api/apps/access/serializers.py +946 -0
- trueppm_api/apps/access/services.py +667 -0
- trueppm_api/apps/access/signals.py +103 -0
- trueppm_api/apps/access/tasks.py +50 -0
- trueppm_api/apps/access/throttles.py +95 -0
- trueppm_api/apps/access/urls.py +191 -0
- trueppm_api/apps/access/views.py +1931 -0
- trueppm_api/apps/agents/__init__.py +6 -0
- trueppm_api/apps/agents/apps.py +11 -0
- trueppm_api/apps/agents/canonical.py +73 -0
- trueppm_api/apps/agents/deferred.py +108 -0
- trueppm_api/apps/agents/management/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/audit_prune.py +137 -0
- trueppm_api/apps/agents/management/commands/audit_verify.py +101 -0
- trueppm_api/apps/agents/middleware.py +44 -0
- trueppm_api/apps/agents/migrations/0001_initial.py +231 -0
- trueppm_api/apps/agents/migrations/0002_agentactioncheckpoint.py +71 -0
- trueppm_api/apps/agents/migrations/0003_agentactionrefusaldetail.py +58 -0
- trueppm_api/apps/agents/migrations/__init__.py +0 -0
- trueppm_api/apps/agents/models.py +371 -0
- trueppm_api/apps/agents/refusal.py +103 -0
- trueppm_api/apps/agents/serializers.py +85 -0
- trueppm_api/apps/agents/services.py +397 -0
- trueppm_api/apps/agents/signals.py +30 -0
- trueppm_api/apps/agents/urls.py +15 -0
- trueppm_api/apps/agents/views.py +108 -0
- trueppm_api/apps/csvimport/__init__.py +12 -0
- trueppm_api/apps/csvimport/apps.py +11 -0
- trueppm_api/apps/csvimport/mapping.py +363 -0
- trueppm_api/apps/csvimport/migrations/0001_initial.py +74 -0
- trueppm_api/apps/csvimport/migrations/0002_schedule_outline_batch_operations.py +37 -0
- trueppm_api/apps/csvimport/migrations/0003_csvimportrequest_date_order_and_more.py +26 -0
- trueppm_api/apps/csvimport/migrations/__init__.py +0 -0
- trueppm_api/apps/csvimport/models.py +111 -0
- trueppm_api/apps/csvimport/parser.py +1825 -0
- trueppm_api/apps/csvimport/services.py +48 -0
- trueppm_api/apps/csvimport/tasks.py +418 -0
- trueppm_api/apps/csvimport/template.py +37 -0
- trueppm_api/apps/csvimport/urls.py +50 -0
- trueppm_api/apps/csvimport/views.py +793 -0
- trueppm_api/apps/history/__init__.py +0 -0
- trueppm_api/apps/history/apps.py +12 -0
- trueppm_api/apps/history/changelog.py +359 -0
- trueppm_api/apps/history/diff_policy.py +177 -0
- trueppm_api/apps/history/serializers.py +104 -0
- trueppm_api/apps/history/signals.py +82 -0
- trueppm_api/apps/history/tasks.py +105 -0
- trueppm_api/apps/history/urls.py +38 -0
- trueppm_api/apps/history/views.py +443 -0
- trueppm_api/apps/idempotency/__init__.py +0 -0
- trueppm_api/apps/idempotency/apps.py +11 -0
- trueppm_api/apps/idempotency/migrations/0001_initial.py +71 -0
- trueppm_api/apps/idempotency/migrations/__init__.py +0 -0
- trueppm_api/apps/idempotency/mixins.py +226 -0
- trueppm_api/apps/idempotency/models.py +75 -0
- trueppm_api/apps/idempotency/tasks.py +61 -0
- trueppm_api/apps/integrations/__init__.py +13 -0
- trueppm_api/apps/integrations/apps.py +59 -0
- trueppm_api/apps/integrations/connections.py +806 -0
- trueppm_api/apps/integrations/encryption.py +92 -0
- trueppm_api/apps/integrations/external_sources.py +837 -0
- trueppm_api/apps/integrations/git_automation_services.py +231 -0
- trueppm_api/apps/integrations/git_webhook_auth.py +243 -0
- trueppm_api/apps/integrations/http.py +403 -0
- trueppm_api/apps/integrations/me_work.py +218 -0
- trueppm_api/apps/integrations/migrations/0001_initial.py +64 -0
- trueppm_api/apps/integrations/migrations/0001_squashed_0006_tasklink_description_tasklink_preview_type_and_more.py +187 -0
- trueppm_api/apps/integrations/migrations/0002_tasklink.py +67 -0
- trueppm_api/apps/integrations/migrations/0003_tasklink_tasklink_serverver_idx.py +17 -0
- trueppm_api/apps/integrations/migrations/0004_tasklink_custom_title_tasklink_labels.py +25 -0
- trueppm_api/apps/integrations/migrations/0005_boardautomation.py +56 -0
- trueppm_api/apps/integrations/migrations/0006_tasklink_description_tasklink_preview_type_and_more.py +40 -0
- trueppm_api/apps/integrations/migrations/0007_tasklink_created_at.py +23 -0
- trueppm_api/apps/integrations/migrations/0008_integrationcredential_config_externalworkitem.py +66 -0
- trueppm_api/apps/integrations/migrations/0009_externalsyncrequest_and_more.py +96 -0
- trueppm_api/apps/integrations/migrations/0010_externalworkitem_due_date.py +17 -0
- trueppm_api/apps/integrations/migrations/0011_tasklink_sync_seq_tasklink_tasklink_syncseq_idx.py +22 -0
- trueppm_api/apps/integrations/migrations/0012_boardautomation_last_delivery_at_and_more.py +42 -0
- trueppm_api/apps/integrations/migrations/0013_externalsyncrequest_attempt_count_and_more.py +26 -0
- trueppm_api/apps/integrations/migrations/__init__.py +0 -0
- trueppm_api/apps/integrations/models.py +622 -0
- trueppm_api/apps/integrations/notification_channels.py +57 -0
- trueppm_api/apps/integrations/opengraph.py +229 -0
- trueppm_api/apps/integrations/outgoing.py +201 -0
- trueppm_api/apps/integrations/providers.py +519 -0
- trueppm_api/apps/integrations/registry.py +345 -0
- trueppm_api/apps/integrations/serializers.py +453 -0
- trueppm_api/apps/integrations/services.py +171 -0
- trueppm_api/apps/integrations/tasks.py +731 -0
- trueppm_api/apps/integrations/throttles.py +165 -0
- trueppm_api/apps/integrations/urls.py +77 -0
- trueppm_api/apps/integrations/views.py +1106 -0
- trueppm_api/apps/jiraimport/__init__.py +8 -0
- trueppm_api/apps/jiraimport/apps.py +11 -0
- trueppm_api/apps/jiraimport/migrations/0001_initial.py +72 -0
- trueppm_api/apps/jiraimport/migrations/__init__.py +0 -0
- trueppm_api/apps/jiraimport/models.py +70 -0
- trueppm_api/apps/jiraimport/parser.py +350 -0
- trueppm_api/apps/jiraimport/services.py +46 -0
- trueppm_api/apps/jiraimport/tasks.py +277 -0
- trueppm_api/apps/jiraimport/urls.py +15 -0
- trueppm_api/apps/jiraimport/views.py +153 -0
- trueppm_api/apps/msproject/__init__.py +0 -0
- trueppm_api/apps/msproject/apps.py +11 -0
- trueppm_api/apps/msproject/dataclasses.py +153 -0
- trueppm_api/apps/msproject/exporter.py +500 -0
- trueppm_api/apps/msproject/extended_attributes.py +77 -0
- trueppm_api/apps/msproject/importer.py +1095 -0
- trueppm_api/apps/msproject/migrations/0001_import_request.py +77 -0
- trueppm_api/apps/msproject/migrations/0001_squashed_0002_importrequest_creates_project.py +80 -0
- trueppm_api/apps/msproject/migrations/0002_importrequest_creates_project.py +15 -0
- trueppm_api/apps/msproject/migrations/__init__.py +0 -0
- trueppm_api/apps/msproject/models.py +81 -0
- trueppm_api/apps/msproject/parser.py +1231 -0
- trueppm_api/apps/msproject/serializers.py +136 -0
- trueppm_api/apps/msproject/services.py +58 -0
- trueppm_api/apps/msproject/tasks.py +436 -0
- trueppm_api/apps/msproject/urls.py +39 -0
- trueppm_api/apps/msproject/views.py +531 -0
- trueppm_api/apps/notifications/__init__.py +7 -0
- trueppm_api/apps/notifications/apps.py +15 -0
- trueppm_api/apps/notifications/backfill.py +79 -0
- trueppm_api/apps/notifications/categories.py +112 -0
- trueppm_api/apps/notifications/delivery_limits.py +258 -0
- trueppm_api/apps/notifications/digests.py +447 -0
- trueppm_api/apps/notifications/email_backend.py +330 -0
- trueppm_api/apps/notifications/email_health.py +128 -0
- trueppm_api/apps/notifications/migrations/0001_initial.py +207 -0
- trueppm_api/apps/notifications/migrations/0001_squashed_0006_notification_task.py +294 -0
- trueppm_api/apps/notifications/migrations/0002_project_notification_preference.py +67 -0
- trueppm_api/apps/notifications/migrations/0003_projectnotificationpreference_paused.py +17 -0
- trueppm_api/apps/notifications/migrations/0004_clean_unknown_matrix_keys.py +30 -0
- trueppm_api/apps/notifications/migrations/0005_notification_body_notification_event_type_and_more.py +27 -0
- trueppm_api/apps/notifications/migrations/0006_notification_task.py +25 -0
- trueppm_api/apps/notifications/migrations/0007_workspaceemailsettings.py +80 -0
- trueppm_api/apps/notifications/migrations/0008_notification_snoozed_until.py +17 -0
- trueppm_api/apps/notifications/migrations/0009_usernotificationsettings.py +43 -0
- trueppm_api/apps/notifications/migrations/0010_projectnotificationpreference_schema_version.py +20 -0
- trueppm_api/apps/notifications/migrations/0011_usernotificationsettings_digest_hour_and_more.py +96 -0
- trueppm_api/apps/notifications/migrations/__init__.py +0 -0
- trueppm_api/apps/notifications/models.py +1197 -0
- trueppm_api/apps/notifications/receivers.py +155 -0
- trueppm_api/apps/notifications/schema_migrations.py +56 -0
- trueppm_api/apps/notifications/serializers.py +961 -0
- trueppm_api/apps/notifications/services.py +1451 -0
- trueppm_api/apps/notifications/tasks.py +711 -0
- trueppm_api/apps/notifications/throttles.py +98 -0
- trueppm_api/apps/notifications/urls.py +97 -0
- trueppm_api/apps/notifications/views.py +904 -0
- trueppm_api/apps/observability/__init__.py +0 -0
- trueppm_api/apps/observability/apps.py +31 -0
- trueppm_api/apps/observability/logging.py +213 -0
- trueppm_api/apps/observability/migrations/0001_initial.py +35 -0
- trueppm_api/apps/observability/migrations/0001_squashed_0003_alter_purgerun_trigger.py +154 -0
- trueppm_api/apps/observability/migrations/0002_purgerun_retentionpolicy_retentionschedule.py +131 -0
- trueppm_api/apps/observability/migrations/0003_alter_purgerun_trigger.py +21 -0
- trueppm_api/apps/observability/migrations/0004_alter_retentionpolicy_key.py +28 -0
- trueppm_api/apps/observability/migrations/__init__.py +0 -0
- trueppm_api/apps/observability/models.py +141 -0
- trueppm_api/apps/observability/otel/__init__.py +45 -0
- trueppm_api/apps/observability/otel/attributes.py +247 -0
- trueppm_api/apps/observability/otel/export_health.py +533 -0
- trueppm_api/apps/observability/otel/instrumentation.py +351 -0
- trueppm_api/apps/observability/otel/metrics.py +551 -0
- trueppm_api/apps/observability/otel/provider.py +511 -0
- trueppm_api/apps/observability/otel/request_attributes.py +204 -0
- trueppm_api/apps/observability/purge_registry.py +124 -0
- trueppm_api/apps/observability/retention.py +139 -0
- trueppm_api/apps/observability/selectors.py +969 -0
- trueppm_api/apps/observability/serializers.py +142 -0
- trueppm_api/apps/observability/services.py +407 -0
- trueppm_api/apps/observability/tasks.py +266 -0
- trueppm_api/apps/observability/urls.py +27 -0
- trueppm_api/apps/observability/views.py +523 -0
- trueppm_api/apps/profiles/__init__.py +0 -0
- trueppm_api/apps/profiles/apps.py +15 -0
- trueppm_api/apps/profiles/constants.py +54 -0
- trueppm_api/apps/profiles/migrations/0001_initial.py +55 -0
- trueppm_api/apps/profiles/migrations/0001_squashed_0004_userprofile_role_context.py +125 -0
- trueppm_api/apps/profiles/migrations/0002_userprofile_hidden_views.py +17 -0
- trueppm_api/apps/profiles/migrations/0003_projectvisit.py +63 -0
- trueppm_api/apps/profiles/migrations/0004_userprofile_role_context.py +26 -0
- trueppm_api/apps/profiles/migrations/0005_userprofile_schedule_in_deliver.py +20 -0
- trueppm_api/apps/profiles/migrations/0006_userprofile_date_format_userprofile_timezone.py +36 -0
- trueppm_api/apps/profiles/migrations/0007_userpin.py +91 -0
- trueppm_api/apps/profiles/migrations/0008_remove_userprofile_schedule_in_deliver.py +71 -0
- trueppm_api/apps/profiles/migrations/0009_auth_user_email_upper_idx.py +81 -0
- trueppm_api/apps/profiles/migrations/__init__.py +0 -0
- trueppm_api/apps/profiles/models.py +276 -0
- trueppm_api/apps/profiles/serializers.py +199 -0
- trueppm_api/apps/profiles/services.py +466 -0
- trueppm_api/apps/profiles/views.py +124 -0
- trueppm_api/apps/projects/__init__.py +0 -0
- trueppm_api/apps/projects/actual_date_rules.py +163 -0
- trueppm_api/apps/projects/amend.py +133 -0
- trueppm_api/apps/projects/apps.py +38 -0
- trueppm_api/apps/projects/asset_feed.py +459 -0
- trueppm_api/apps/projects/asset_views.py +317 -0
- trueppm_api/apps/projects/attachment_policy.py +278 -0
- trueppm_api/apps/projects/authentication.py +449 -0
- trueppm_api/apps/projects/backfill.py +287 -0
- trueppm_api/apps/projects/backlog_services.py +324 -0
- trueppm_api/apps/projects/backlog_views.py +302 -0
- trueppm_api/apps/projects/batch_operation_views.py +264 -0
- trueppm_api/apps/projects/blocker_services.py +510 -0
- trueppm_api/apps/projects/board_activity.py +462 -0
- trueppm_api/apps/projects/board_activity_views.py +186 -0
- trueppm_api/apps/projects/board_lanes.py +134 -0
- trueppm_api/apps/projects/bulk_settings.py +194 -0
- trueppm_api/apps/projects/bundled_templates.py +215 -0
- trueppm_api/apps/projects/calendar_settings.py +180 -0
- trueppm_api/apps/projects/ceremony_views.py +244 -0
- trueppm_api/apps/projects/commit_moment.py +204 -0
- trueppm_api/apps/projects/config_notice.py +1325 -0
- trueppm_api/apps/projects/custom_field_values.py +266 -0
- trueppm_api/apps/projects/decisions_services.py +93 -0
- trueppm_api/apps/projects/decisions_views.py +94 -0
- trueppm_api/apps/projects/estimation_scale.py +126 -0
- trueppm_api/apps/projects/export_bundle.py +353 -0
- trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4256 -0
- trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2000 -0
- trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1825 -0
- trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1905 -0
- trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1409 -0
- trueppm_api/apps/projects/guardrail_policy_source.py +269 -0
- trueppm_api/apps/projects/inbound_sync.py +555 -0
- trueppm_api/apps/projects/iteration_label.py +85 -0
- trueppm_api/apps/projects/lifecycle.py +191 -0
- trueppm_api/apps/projects/management/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/backfill_in_progress_status.py +92 -0
- trueppm_api/apps/projects/management/commands/create_demo_share_link.py +368 -0
- trueppm_api/apps/projects/management/commands/export_program.py +50 -0
- trueppm_api/apps/projects/management/commands/import_seed.py +165 -0
- trueppm_api/apps/projects/management/commands/load_sample_project.py +247 -0
- trueppm_api/apps/projects/management/commands/seed_capacity.py +405 -0
- trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +178 -0
- trueppm_api/apps/projects/management/commands/shift_sample_dates.py +120 -0
- trueppm_api/apps/projects/mcp_settings.py +305 -0
- trueppm_api/apps/projects/methodology.py +178 -0
- trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
- trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5595 -0
- trueppm_api/apps/projects/migrations/0002_alter_uuid_pk_serialize_false.py +57 -0
- trueppm_api/apps/projects/migrations/0003_rename_task_project_index.py +25 -0
- trueppm_api/apps/projects/migrations/0004_soft_delete_and_dependency_versioned.py +70 -0
- trueppm_api/apps/projects/migrations/0005_task_assignee.py +33 -0
- trueppm_api/apps/projects/migrations/0006_task_utilization_index.py +31 -0
- trueppm_api/apps/projects/migrations/0007_task_is_milestone.py +28 -0
- trueppm_api/apps/projects/migrations/0008_baseline.py +119 -0
- trueppm_api/apps/projects/migrations/0009_historicaldependency_historicalproject_and_more.py +210 -0
- trueppm_api/apps/projects/migrations/0010_risk.py +229 -0
- trueppm_api/apps/projects/migrations/0011_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0012_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0013_task_planned_start.py +24 -0
- trueppm_api/apps/projects/migrations/0014_short_hex_ids.py +74 -0
- trueppm_api/apps/projects/migrations/0015_backfill_short_ids.py +48 -0
- trueppm_api/apps/projects/migrations/0016_task_actual_dates.py +42 -0
- trueppm_api/apps/projects/migrations/0017_boardcolumnconfig.py +43 -0
- trueppm_api/apps/projects/migrations/0018_estimation_governance.py +62 -0
- trueppm_api/apps/projects/migrations/0019_backfill_wbs_paths.py +28 -0
- trueppm_api/apps/projects/migrations/0020_task_status_5col.py +65 -0
- trueppm_api/apps/projects/migrations/0021_boardsavedview.py +64 -0
- trueppm_api/apps/projects/migrations/0022_task_status_changed_at_priority_rank.py +35 -0
- trueppm_api/apps/projects/migrations/0023_risk_framework_fields.py +96 -0
- trueppm_api/apps/projects/migrations/0024_riskcomment.py +52 -0
- trueppm_api/apps/projects/migrations/0025_sprint.py +352 -0
- trueppm_api/apps/projects/migrations/0026_project_methodology.py +30 -0
- trueppm_api/apps/projects/migrations/0027_sprint_retro.py +89 -0
- trueppm_api/apps/projects/migrations/0028_normalize_notes_field.py +42 -0
- trueppm_api/apps/projects/migrations/0029_complete_implies_full_progress.py +38 -0
- trueppm_api/apps/projects/migrations/0030_review_implies_full_progress.py +37 -0
- trueppm_api/apps/projects/migrations/0031_task_remaining_points.py +22 -0
- trueppm_api/apps/projects/migrations/0032_task_is_subtask_and_sprint_scope_change.py +90 -0
- trueppm_api/apps/projects/migrations/0033_task_my_work_index.py +38 -0
- trueppm_api/apps/projects/migrations/0034_inbound_task_sync.py +313 -0
- trueppm_api/apps/projects/migrations/0035_retro_versioned_and_suggested_assignee.py +173 -0
- trueppm_api/apps/projects/migrations/0036_program_entity_and_membership.py +157 -0
- trueppm_api/apps/projects/migrations/0037_sprint_capacity_points.py +20 -0
- trueppm_api/apps/projects/migrations/0038_taskcomment_commentreaction_commentacknowledgement_and_more.py +233 -0
- trueppm_api/apps/projects/migrations/0039_apitoken_and_more.py +88 -0
- trueppm_api/apps/projects/migrations/0040_program_general_fields.py +119 -0
- trueppm_api/apps/projects/migrations/0041_ceremony_template_and_phase_gate_config.py +210 -0
- trueppm_api/apps/projects/migrations/0041_project_general_fields.py +106 -0
- trueppm_api/apps/projects/migrations/0042_merge_20260522_0410.py +12 -0
- trueppm_api/apps/projects/migrations/0042_program_rollup_config.py +95 -0
- trueppm_api/apps/projects/migrations/0042_workflow_color_and_custom_fields.py +94 -0
- trueppm_api/apps/projects/migrations/0043_merge_20260522_1118.py +10 -0
- trueppm_api/apps/projects/migrations/0044_merge_20260522_rollup.py +10 -0
- trueppm_api/apps/projects/migrations/0044_project_archive_program_close.py +119 -0
- trueppm_api/apps/projects/migrations/0045_program_risk_policy.py +78 -0
- trueppm_api/apps/projects/migrations/0046_merge_20260523_2248.py +12 -0
- trueppm_api/apps/projects/migrations/0047_apitokenauditentry_program_and_more.py +55 -0
- trueppm_api/apps/projects/migrations/0048_historicalprogram_color_program_color.py +32 -0
- trueppm_api/apps/projects/migrations/0049_backlog_item.py +112 -0
- trueppm_api/apps/projects/migrations/0050_backlog_item_trgm_search.py +26 -0
- trueppm_api/apps/projects/migrations/0051_alter_calendar_working_days_alter_dependency_lag_and_more.py +144 -0
- trueppm_api/apps/projects/migrations/0052_recurring_tasks.py +278 -0
- trueppm_api/apps/projects/migrations/0053_dependency_dep_pred_serverver_idx_and_more.py +56 -0
- trueppm_api/apps/projects/migrations/0054_sprintscopechange_goal_impact_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0055_historicaltask_sprint_pending_and_more.py +51 -0
- trueppm_api/apps/projects/migrations/0056_historicalproject_prioritization_model_and_more.py +276 -0
- trueppm_api/apps/projects/migrations/0057_historicalsprint_binding_committed_snapshot_and_more.py +58 -0
- trueppm_api/apps/projects/migrations/0058_historicaltask_delivery_mode_and_more.py +72 -0
- trueppm_api/apps/projects/migrations/0059_forecastsnapshot.py +80 -0
- trueppm_api/apps/projects/migrations/0060_historicalprojectsignalprivacypolicy_and_more.py +90 -0
- trueppm_api/apps/projects/migrations/0061_baselinetask_story_points.py +17 -0
- trueppm_api/apps/projects/migrations/0062_project_is_sample.py +22 -0
- trueppm_api/apps/projects/migrations/0063_historicalsprint_wip_limit_sprint_wip_limit.py +22 -0
- trueppm_api/apps/projects/migrations/0064_sprinttaskoutcome.py +97 -0
- trueppm_api/apps/projects/migrations/0065_historicalsprint_goal_outcome_sprint_goal_outcome.py +32 -0
- trueppm_api/apps/projects/migrations/0066_historicalproject_lead_project_lead.py +38 -0
- trueppm_api/apps/projects/migrations/0067_sprint_exclude_from_velocity.py +22 -0
- trueppm_api/apps/projects/migrations/0068_project_recalculated_at.py +23 -0
- trueppm_api/apps/projects/migrations/0069_iteration_label.py +22 -0
- trueppm_api/apps/projects/migrations/0070_historicalprogram_iteration_label_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0071_backfill_iteration_label_inherit.py +35 -0
- trueppm_api/apps/projects/migrations/0072_pulseresponse_retroboarditem.py +129 -0
- trueppm_api/apps/projects/migrations/0073_sprinttaskoutcome_demo_ready.py +17 -0
- trueppm_api/apps/projects/migrations/0074_historicaltask_blocked_reason_task_blocked_reason.py +30 -0
- trueppm_api/apps/projects/migrations/0075_risk_decimal_short_id.py +42 -0
- trueppm_api/apps/projects/migrations/0076_sprinttaskoutcome_review_polish.py +45 -0
- trueppm_api/apps/projects/migrations/0077_task_blocker_fields.py +111 -0
- trueppm_api/apps/projects/migrations/0078_alter_risktask_unique_together_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0079_historicalproject_status_date_project_status_date.py +22 -0
- trueppm_api/apps/projects/migrations/0080_alter_historicaltask_type_alter_task_type.py +46 -0
- trueppm_api/apps/projects/migrations/0081_historicalprogram_allow_guests_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0082_project_last_sync_version.py +90 -0
- trueppm_api/apps/projects/migrations/0083_historicalprogram_mc_history_attribution_audience_and_more.py +108 -0
- trueppm_api/apps/projects/migrations/0084_tasknote.py +71 -0
- trueppm_api/apps/projects/migrations/0085_sprint_ix_sprint_velocity.py +33 -0
- trueppm_api/apps/projects/migrations/0086_board_card_search_trgm.py +33 -0
- trueppm_api/apps/projects/migrations/0087_historicalprogram_task_duration_change_percent_policy_and_more.py +155 -0
- trueppm_api/apps/projects/migrations/0088_historicalprogram_allowed_attachment_types_and_more.py +61 -0
- trueppm_api/apps/projects/migrations/0089_signalceilingraiseproposal_signalceilingraisevote_and_more.py +157 -0
- trueppm_api/apps/projects/migrations/0090_historicaltask_proj_histdate_index.py +44 -0
- trueppm_api/apps/projects/migrations/0091_project_board_cadence.py +39 -0
- trueppm_api/apps/projects/migrations/0092_program_target_date.py +22 -0
- trueppm_api/apps/projects/migrations/0093_cross_project_dependency_consent.py +58 -0
- trueppm_api/apps/projects/migrations/0094_project_decisions_policy.py +90 -0
- trueppm_api/apps/projects/migrations/0095_poker_session_vote.py +127 -0
- trueppm_api/apps/projects/migrations/0095_squashed_0097_release_0_3.py +225 -0
- trueppm_api/apps/projects/migrations/0096_crossprojectslipconflict.py +91 -0
- trueppm_api/apps/projects/migrations/0097_backlogitem_backlogitem_tags_gin_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0098_historicalproject_show_baselines_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0099_apitoken_scopes.py +48 -0
- trueppm_api/apps/projects/migrations/0100_task_dep_serverver_idx_concurrent.py +61 -0
- trueppm_api/apps/projects/migrations/0101_dependency_deleted_at_task_deleted_at.py +22 -0
- trueppm_api/apps/projects/migrations/0102_project_deleted_at_project_proj_isdel_deletedat_idx.py +25 -0
- trueppm_api/apps/projects/migrations/0103_taskdurationchangeevent_task_dur_evt_sprint_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0104_historicalproject_stale_task_threshold_days_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0104_trash_restore_1113.py +38 -0
- trueppm_api/apps/projects/migrations/0105_historical_changelog_indexes.py +62 -0
- trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
- trueppm_api/apps/projects/migrations/0107_task_activity_event.py +70 -0
- trueppm_api/apps/projects/migrations/0108_remove_apitoken_api_token_scope_xor_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0109_projectexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0110_sharelink.py +123 -0
- trueppm_api/apps/projects/migrations/0111_projectcalendarlayer.py +67 -0
- trueppm_api/apps/projects/migrations/0112_sharelink_expires_at_alter_sharelink_content_kind.py +31 -0
- trueppm_api/apps/projects/migrations/0113_alter_historicaltask_percent_complete_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0114_historicalproject_default_member_role_and_more.py +28 -0
- trueppm_api/apps/projects/migrations/0115_historicalproject_end_date_shift_threshold_days_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0116_alter_taskactivityevent_event_type.py +28 -0
- trueppm_api/apps/projects/migrations/0117_label_tasklabel_label_tasks_and_more.py +126 -0
- trueppm_api/apps/projects/migrations/0118_programexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0119_historicalprogram_calendar_program_calendar.py +36 -0
- trueppm_api/apps/projects/migrations/0120_alter_backlogitem_item_type.py +29 -0
- trueppm_api/apps/projects/migrations/0121_task_three_point_estimate_ordered.py +64 -0
- trueppm_api/apps/projects/migrations/0122_task_relation.py +173 -0
- trueppm_api/apps/projects/migrations/0123_remove_historicalproject_agile_features_and_more.py +20 -0
- trueppm_api/apps/projects/migrations/0124_dependency_is_driving.py +17 -0
- trueppm_api/apps/projects/migrations/0125_historicalprogram_estimation_scale_and_more.py +68 -0
- trueppm_api/apps/projects/migrations/0126_projectcustomfield_show_on_card_taskcustomfieldvalue.py +80 -0
- trueppm_api/apps/projects/migrations/0127_historicalprogram_mcp_enabled_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0128_acceptancecriterion_sync_seq_apitoken_sync_seq_and_more.py +192 -0
- trueppm_api/apps/projects/migrations/0129_sharelink_show_milestone_dates.py +20 -0
- trueppm_api/apps/projects/migrations/0130_programimportjob.py +85 -0
- trueppm_api/apps/projects/migrations/0131_historicalprogram_sprint_picker_ready_only_default_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0132_task_scheduled_start.py +17 -0
- trueppm_api/apps/projects/migrations/0133_project_status_date_floor_armed_at.py +17 -0
- trueppm_api/apps/projects/migrations/0134_task_edited_at_task_seeded_at_task_source_id_and_more.py +65 -0
- trueppm_api/apps/projects/migrations/0135_projecttemplate_templateapplication_and_more.py +175 -0
- trueppm_api/apps/projects/migrations/0136_schedule_outline_batch_operations.py +118 -0
- trueppm_api/apps/projects/migrations/0137_collapse_dual_scope_api_tokens.py +54 -0
- trueppm_api/apps/projects/migrations/0138_apitokenauditentry_api_token_audit_owner_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0139_historicaltask_duration_unit_task_duration_unit.py +32 -0
- trueppm_api/apps/projects/migrations/0140_historicaltask_auto_container_and_more.py +113 -0
- trueppm_api/apps/projects/migrations/0141_historicalproject_draft_started_at_and_more.py +44 -0
- trueppm_api/apps/projects/migrations/0142_baseline_calendar_hours_per_day_and_more.py +31 -0
- trueppm_api/apps/projects/migrations/0143_sprintcloserequest_failure_reason_and_more.py +42 -0
- trueppm_api/apps/projects/migrations/0144_projecttemplate_source_project_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0145_seed_bundled_templates.py +65 -0
- trueppm_api/apps/projects/migrations/0146_historicaltask_board_lane_task_board_lane.py +32 -0
- trueppm_api/apps/projects/migrations/0147_structuraloperation.py +128 -0
- trueppm_api/apps/projects/migrations/0148_task_unique_task_wbs_path_per_project_live.py +76 -0
- trueppm_api/apps/projects/migrations/0149_drop_workshops_tables.py +42 -0
- trueppm_api/apps/projects/migrations/0150_clear_uncommitted_cpm_output.py +130 -0
- trueppm_api/apps/projects/migrations/0151_config_notice_request.py +70 -0
- trueppm_api/apps/projects/migrations/0152_aggregate_task_wbs_name_idx.py +20 -0
- trueppm_api/apps/projects/migrations/0153_program_sample_anchor_date.py +22 -0
- trueppm_api/apps/projects/migrations/__init__.py +0 -0
- trueppm_api/apps/projects/models.py +8946 -0
- trueppm_api/apps/projects/poker_services.py +154 -0
- trueppm_api/apps/projects/poker_views.py +398 -0
- trueppm_api/apps/projects/product_backlog_services.py +450 -0
- trueppm_api/apps/projects/program_label_services.py +94 -0
- trueppm_api/apps/projects/program_label_views.py +189 -0
- trueppm_api/apps/projects/program_rollup.py +575 -0
- trueppm_api/apps/projects/program_schedule.py +745 -0
- trueppm_api/apps/projects/program_views.py +3220 -0
- trueppm_api/apps/projects/project_templates.py +422 -0
- trueppm_api/apps/projects/receivers.py +234 -0
- trueppm_api/apps/projects/refusal_codes.py +140 -0
- trueppm_api/apps/projects/reorder_services.py +145 -0
- trueppm_api/apps/projects/restructure_hooks.py +54 -0
- trueppm_api/apps/projects/retro_board_services.py +368 -0
- trueppm_api/apps/projects/retro_services.py +285 -0
- trueppm_api/apps/projects/risk_import.py +396 -0
- trueppm_api/apps/projects/row_identity.py +113 -0
- trueppm_api/apps/projects/schema_migrations.py +228 -0
- trueppm_api/apps/projects/schemas/seed_v1.json +493 -0
- trueppm_api/apps/projects/schemas/seed_v2.json +892 -0
- trueppm_api/apps/projects/seed/__init__.py +61 -0
- trueppm_api/apps/projects/seed/exporter.py +1644 -0
- trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
- trueppm_api/apps/projects/seed/importer.py +2390 -0
- trueppm_api/apps/projects/seed/reanchor.py +551 -0
- trueppm_api/apps/projects/seed/reldates.py +126 -0
- trueppm_api/apps/projects/seed/replace.py +126 -0
- trueppm_api/apps/projects/seed/replay.py +1691 -0
- trueppm_api/apps/projects/seed/replay_ctx.py +41 -0
- trueppm_api/apps/projects/seed/samples.py +427 -0
- trueppm_api/apps/projects/seed/validation.py +1149 -0
- trueppm_api/apps/projects/serializers.py +12310 -0
- trueppm_api/apps/projects/services.py +6966 -0
- trueppm_api/apps/projects/share_serializers.py +132 -0
- trueppm_api/apps/projects/share_services.py +325 -0
- trueppm_api/apps/projects/share_views.py +374 -0
- trueppm_api/apps/projects/sharing_settings.py +187 -0
- trueppm_api/apps/projects/signal_privacy_services.py +909 -0
- trueppm_api/apps/projects/signal_privacy_views.py +476 -0
- trueppm_api/apps/projects/signals.py +132 -0
- trueppm_api/apps/projects/slip_conflict.py +266 -0
- trueppm_api/apps/projects/sprint_cadence.py +453 -0
- trueppm_api/apps/projects/sprint_picker_settings.py +132 -0
- trueppm_api/apps/projects/standup.py +304 -0
- trueppm_api/apps/projects/standup_views.py +99 -0
- trueppm_api/apps/projects/structural_operation_services.py +727 -0
- trueppm_api/apps/projects/structural_operation_views.py +311 -0
- trueppm_api/apps/projects/surface_visibility.py +126 -0
- trueppm_api/apps/projects/task_batch_services.py +379 -0
- trueppm_api/apps/projects/task_bulk.py +1152 -0
- trueppm_api/apps/projects/task_classification.py +561 -0
- trueppm_api/apps/projects/task_classification_defaults.py +95 -0
- trueppm_api/apps/projects/task_duration_settings.py +142 -0
- trueppm_api/apps/projects/task_grouping.py +754 -0
- trueppm_api/apps/projects/tasks.py +1917 -0
- trueppm_api/apps/projects/template_divergence.py +168 -0
- trueppm_api/apps/projects/template_services.py +160 -0
- trueppm_api/apps/projects/template_tasks.py +258 -0
- trueppm_api/apps/projects/template_views.py +799 -0
- trueppm_api/apps/projects/throttles.py +311 -0
- trueppm_api/apps/projects/urls.py +1011 -0
- trueppm_api/apps/projects/utilization.py +885 -0
- trueppm_api/apps/projects/views.py +20572 -0
- trueppm_api/apps/projects/wbs_paths.py +119 -0
- trueppm_api/apps/resources/__init__.py +0 -0
- trueppm_api/apps/resources/apps.py +8 -0
- trueppm_api/apps/resources/capacity.py +121 -0
- trueppm_api/apps/resources/migrations/0001_initial.py +68 -0
- trueppm_api/apps/resources/migrations/0001_squashed_0013_alter_projectresource_unique_together_and_more.py +281 -0
- trueppm_api/apps/resources/migrations/0002_alter_uuid_pk_serialize_false.py +36 -0
- trueppm_api/apps/resources/migrations/0003_resource_soft_delete.py +30 -0
- trueppm_api/apps/resources/migrations/0004_add_taskresource_resource_index.py +25 -0
- trueppm_api/apps/resources/migrations/0005_resource_job_role.py +20 -0
- trueppm_api/apps/resources/migrations/0006_skill.py +34 -0
- trueppm_api/apps/resources/migrations/0007_resource_skill.py +63 -0
- trueppm_api/apps/resources/migrations/0008_project_resource.py +63 -0
- trueppm_api/apps/resources/migrations/0009_task_skill_requirement.py +60 -0
- trueppm_api/apps/resources/migrations/0010_alter_resourceskill_options_and_more.py +20 -0
- trueppm_api/apps/resources/migrations/0011_alter_projectresource_notes.py +17 -0
- trueppm_api/apps/resources/migrations/0012_add_resource_user_fk.py +26 -0
- trueppm_api/apps/resources/migrations/0013_alter_projectresource_unique_together_and_more.py +53 -0
- trueppm_api/apps/resources/migrations/0014_projectresource_sync_seq_resource_sync_seq_and_more.py +37 -0
- trueppm_api/apps/resources/migrations/0015_projectresource_deactivated_with_resource.py +17 -0
- trueppm_api/apps/resources/migrations/__init__.py +0 -0
- trueppm_api/apps/resources/models.py +298 -0
- trueppm_api/apps/resources/serializers.py +426 -0
- trueppm_api/apps/resources/services.py +465 -0
- trueppm_api/apps/resources/urls.py +28 -0
- trueppm_api/apps/resources/views.py +1749 -0
- trueppm_api/apps/scheduling/__init__.py +0 -0
- trueppm_api/apps/scheduling/apps.py +153 -0
- trueppm_api/apps/scheduling/calendars.py +119 -0
- trueppm_api/apps/scheduling/deadletter.py +91 -0
- trueppm_api/apps/scheduling/forecast_history_settings.py +215 -0
- trueppm_api/apps/scheduling/forecast_staleness.py +313 -0
- trueppm_api/apps/scheduling/graph_guard.py +263 -0
- trueppm_api/apps/scheduling/management/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/prune_forecast_snapshots.py +53 -0
- trueppm_api/apps/scheduling/migrations/0001_failed_task.py +62 -0
- trueppm_api/apps/scheduling/migrations/0001_squashed_0007_projectforecastsnapshot.py +309 -0
- trueppm_api/apps/scheduling/migrations/0002_schedulerequest.py +78 -0
- trueppm_api/apps/scheduling/migrations/0003_add_schedule_request_reason.py +27 -0
- trueppm_api/apps/scheduling/migrations/0004_velocity_suggestion.py +94 -0
- trueppm_api/apps/scheduling/migrations/0005_montecarlorun.py +61 -0
- trueppm_api/apps/scheduling/migrations/0006_montecarlorun_distribution.py +17 -0
- trueppm_api/apps/scheduling/migrations/0007_projectforecastsnapshot.py +65 -0
- trueppm_api/apps/scheduling/migrations/0008_alter_schedulerequest_reason.py +28 -0
- trueppm_api/apps/scheduling/migrations/0008_folded_schedulerequest_reason.py +34 -0
- trueppm_api/apps/scheduling/migrations/0009_alter_schedulerequest_reason.py +29 -0
- trueppm_api/apps/scheduling/migrations/0010_failedtask_resolution_note_failedtask_resolved_at_and_more.py +43 -0
- trueppm_api/apps/scheduling/migrations/0011_failedtask_project.py +26 -0
- trueppm_api/apps/scheduling/migrations/0012_montecarlorun_diagnostic.py +17 -0
- trueppm_api/apps/scheduling/migrations/0013_montecarlorun_status_date.py +17 -0
- trueppm_api/apps/scheduling/migrations/0014_montecarlorun_plan_version.py +17 -0
- trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
- trueppm_api/apps/scheduling/models.py +516 -0
- trueppm_api/apps/scheduling/receivers.py +160 -0
- trueppm_api/apps/scheduling/risk_premium.py +283 -0
- trueppm_api/apps/scheduling/serializers.py +564 -0
- trueppm_api/apps/scheduling/services.py +1182 -0
- trueppm_api/apps/scheduling/signals.py +56 -0
- trueppm_api/apps/scheduling/tasks.py +1984 -0
- trueppm_api/apps/scheduling/telemetry.py +80 -0
- trueppm_api/apps/scheduling/units.py +35 -0
- trueppm_api/apps/scheduling/urls.py +57 -0
- trueppm_api/apps/scheduling/views.py +2564 -0
- trueppm_api/apps/sso/__init__.py +0 -0
- trueppm_api/apps/sso/apps.py +19 -0
- trueppm_api/apps/sso/extensions.py +130 -0
- trueppm_api/apps/sso/management/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/seed_sso_keycloak.py +172 -0
- trueppm_api/apps/sso/migrations/0001_initial.py +119 -0
- trueppm_api/apps/sso/migrations/0002_remove_oidcprovider_workspace_ssoproviderpolicy_and_more.py +169 -0
- trueppm_api/apps/sso/migrations/__init__.py +0 -0
- trueppm_api/apps/sso/models.py +148 -0
- trueppm_api/apps/sso/serializers.py +437 -0
- trueppm_api/apps/sso/services.py +1385 -0
- trueppm_api/apps/sso/urls.py +47 -0
- trueppm_api/apps/sso/views.py +935 -0
- trueppm_api/apps/sync/__init__.py +0 -0
- trueppm_api/apps/sync/apps.py +15 -0
- trueppm_api/apps/sync/backfill.py +208 -0
- trueppm_api/apps/sync/broadcast.py +320 -0
- trueppm_api/apps/sync/conflict.py +262 -0
- trueppm_api/apps/sync/consumers.py +471 -0
- trueppm_api/apps/sync/migrations/0001_initial.py +59 -0
- trueppm_api/apps/sync/migrations/0001_squashed_0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +77 -0
- trueppm_api/apps/sync/migrations/0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +41 -0
- trueppm_api/apps/sync/migrations/0003_boardevent.py +46 -0
- trueppm_api/apps/sync/migrations/0004_seed_sync_seq.py +24 -0
- trueppm_api/apps/sync/migrations/0005_program_sync_sequence.py +35 -0
- trueppm_api/apps/sync/migrations/__init__.py +0 -0
- trueppm_api/apps/sync/models.py +225 -0
- trueppm_api/apps/sync/pagination.py +231 -0
- trueppm_api/apps/sync/sequence.py +371 -0
- trueppm_api/apps/sync/serializers.py +687 -0
- trueppm_api/apps/sync/tasks.py +260 -0
- trueppm_api/apps/sync/throttles.py +175 -0
- trueppm_api/apps/sync/upload.py +501 -0
- trueppm_api/apps/sync/urls.py +17 -0
- trueppm_api/apps/sync/views.py +930 -0
- trueppm_api/apps/sync/ws_auth.py +193 -0
- trueppm_api/apps/taskruns/__init__.py +0 -0
- trueppm_api/apps/taskruns/apps.py +11 -0
- trueppm_api/apps/taskruns/migrations/0001_initial.py +92 -0
- trueppm_api/apps/taskruns/migrations/__init__.py +0 -0
- trueppm_api/apps/taskruns/models.py +68 -0
- trueppm_api/apps/taskruns/serializers.py +92 -0
- trueppm_api/apps/taskruns/tasks.py +60 -0
- trueppm_api/apps/taskruns/tracker.py +286 -0
- trueppm_api/apps/taskruns/urls.py +46 -0
- trueppm_api/apps/taskruns/views.py +195 -0
- trueppm_api/apps/teams/__init__.py +8 -0
- trueppm_api/apps/teams/apps.py +16 -0
- trueppm_api/apps/teams/migrations/0001_initial.py +139 -0
- trueppm_api/apps/teams/migrations/0001_squashed_0002_default_teams.py +142 -0
- trueppm_api/apps/teams/migrations/0002_default_teams.py +62 -0
- trueppm_api/apps/teams/migrations/0003_team_sync_seq_teammembership_sync_seq.py +22 -0
- trueppm_api/apps/teams/migrations/__init__.py +0 -0
- trueppm_api/apps/teams/models.py +145 -0
- trueppm_api/apps/teams/permissions.py +149 -0
- trueppm_api/apps/teams/serializers.py +95 -0
- trueppm_api/apps/teams/services.py +434 -0
- trueppm_api/apps/teams/signals.py +61 -0
- trueppm_api/apps/teams/urls.py +34 -0
- trueppm_api/apps/teams/views.py +201 -0
- trueppm_api/apps/timetracking/__init__.py +0 -0
- trueppm_api/apps/timetracking/apps.py +9 -0
- trueppm_api/apps/timetracking/migrations/0001_initial.py +117 -0
- trueppm_api/apps/timetracking/migrations/0002_timesheetsubmission.py +46 -0
- trueppm_api/apps/timetracking/migrations/0003_timeentry_deleted_at_timeentry_deleted_by.py +31 -0
- trueppm_api/apps/timetracking/migrations/0004_timeentry_sync_seq_and_more.py +24 -0
- trueppm_api/apps/timetracking/migrations/__init__.py +0 -0
- trueppm_api/apps/timetracking/models.py +178 -0
- trueppm_api/apps/timetracking/serializers.py +175 -0
- trueppm_api/apps/timetracking/services.py +117 -0
- trueppm_api/apps/timetracking/urls.py +47 -0
- trueppm_api/apps/timetracking/views.py +526 -0
- trueppm_api/apps/webhooks/__init__.py +0 -0
- trueppm_api/apps/webhooks/apps.py +8 -0
- trueppm_api/apps/webhooks/backfill.py +90 -0
- trueppm_api/apps/webhooks/dispatch.py +144 -0
- trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
- trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +243 -0
- trueppm_api/apps/webhooks/migrations/0002_alter_webhook_events.py +32 -0
- trueppm_api/apps/webhooks/migrations/0003_delivery_status_created_idx.py +17 -0
- trueppm_api/apps/webhooks/migrations/0004_webhook_program_alter_webhook_project_and_more.py +64 -0
- trueppm_api/apps/webhooks/migrations/0005_webhook_delivery_sequence_numbers.py +30 -0
- trueppm_api/apps/webhooks/migrations/0006_webhook_format_alter_webhook_events_and_more.py +61 -0
- trueppm_api/apps/webhooks/migrations/0007_sprint_lifecycle_events.py +63 -0
- trueppm_api/apps/webhooks/migrations/0008_risk_baseline_comment_events.py +73 -0
- trueppm_api/apps/webhooks/migrations/0009_encrypt_webhook_secret_and_failure_counters.py +90 -0
- trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
- trueppm_api/apps/webhooks/models.py +372 -0
- trueppm_api/apps/webhooks/serializers.py +318 -0
- trueppm_api/apps/webhooks/tasks.py +663 -0
- trueppm_api/apps/webhooks/urls.py +22 -0
- trueppm_api/apps/webhooks/views.py +383 -0
- trueppm_api/apps/workflow_engine/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/apps.py +23 -0
- trueppm_api/apps/workflow_engine/migrations/0001_initial.py +230 -0
- trueppm_api/apps/workflow_engine/migrations/0001_squashed_0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +234 -0
- trueppm_api/apps/workflow_engine/migrations/0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +16 -0
- trueppm_api/apps/workflow_engine/migrations/0003_workflowoutboxrow_workflow_outbox_recovery_idx.py +21 -0
- trueppm_api/apps/workflow_engine/migrations/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/models.py +265 -0
- trueppm_api/apps/workflow_engine/tasks.py +368 -0
- trueppm_api/apps/workspace/__init__.py +0 -0
- trueppm_api/apps/workspace/apps.py +10 -0
- trueppm_api/apps/workspace/backfill.py +111 -0
- trueppm_api/apps/workspace/export.py +242 -0
- trueppm_api/apps/workspace/migrations/0001_initial.py +326 -0
- trueppm_api/apps/workspace/migrations/0001_squashed_0014_workspacemembership_availability_effective_from_and_more.py +829 -0
- trueppm_api/apps/workspace/migrations/0002_remove_workspace_fiscal_year_start_and_more.py +85 -0
- trueppm_api/apps/workspace/migrations/0003_workspaceexportjob.py +73 -0
- trueppm_api/apps/workspace/migrations/0004_alter_workspaceinvite_status.py +27 -0
- trueppm_api/apps/workspace/migrations/0005_workspace_iteration_label_and_more.py +26 -0
- trueppm_api/apps/workspace/migrations/0006_alter_groupmembership_unique_together_and_more.py +45 -0
- trueppm_api/apps/workspace/migrations/0007_workspace_public_sharing_override_policy.py +21 -0
- trueppm_api/apps/workspace/migrations/0008_workspace_mc_history_attribution_audience_and_more.py +44 -0
- trueppm_api/apps/workspace/migrations/0009_workspace_methodology_and_more.py +186 -0
- trueppm_api/apps/workspace/migrations/0010_workspace_logo.py +38 -0
- trueppm_api/apps/workspace/migrations/0011_historicalworkspace_task_duration_change_percent_override_policy_and_more.py +56 -0
- trueppm_api/apps/workspace/migrations/0012_historicalworkspace_allowed_attachment_types_and_more.py +64 -0
- trueppm_api/apps/workspace/migrations/0013_auditevent.py +70 -0
- trueppm_api/apps/workspace/migrations/0014_workspacemembership_availability_effective_from_and_more.py +39 -0
- trueppm_api/apps/workspace/migrations/0015_trash_restore_1113.py +34 -0
- trueppm_api/apps/workspace/migrations/0016_historicalworkspace_calendar_and_more.py +55 -0
- trueppm_api/apps/workspace/migrations/0017_historicalworkspace_estimation_scale_and_more.py +38 -0
- trueppm_api/apps/workspace/migrations/0018_historicalworkspace_feedback_enabled_and_more.py +32 -0
- trueppm_api/apps/workspace/migrations/0019_historicalworkspace_mcp_enabled_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0020_viewer_ordinal_one.py +34 -0
- trueppm_api/apps/workspace/migrations/0021_group_sync_seq_groupmembership_sync_seq_and_more.py +27 -0
- trueppm_api/apps/workspace/migrations/0022_historicalworkspace_sprint_picker_ready_only_default_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0023_alter_auditevent_event_type.py +32 -0
- trueppm_api/apps/workspace/migrations/0024_alter_auditevent_event_type.py +33 -0
- trueppm_api/apps/workspace/migrations/0025_alter_auditevent_event_type.py +36 -0
- trueppm_api/apps/workspace/migrations/0026_alter_auditevent_event_type.py +37 -0
- trueppm_api/apps/workspace/migrations/0027_alter_auditevent_event_type.py +42 -0
- trueppm_api/apps/workspace/migrations/__init__.py +0 -0
- trueppm_api/apps/workspace/models.py +857 -0
- trueppm_api/apps/workspace/permissions.py +187 -0
- trueppm_api/apps/workspace/serializers.py +590 -0
- trueppm_api/apps/workspace/services.py +919 -0
- trueppm_api/apps/workspace/signals.py +37 -0
- trueppm_api/apps/workspace/tasks.py +555 -0
- trueppm_api/apps/workspace/urls.py +113 -0
- trueppm_api/apps/workspace/views.py +1502 -0
- trueppm_api/asgi.py +42 -0
- trueppm_api/celery.py +31 -0
- trueppm_api/core/__init__.py +0 -0
- trueppm_api/core/admin_site.py +206 -0
- trueppm_api/core/auth_views.py +916 -0
- trueppm_api/core/constant_time.py +51 -0
- trueppm_api/core/csp.py +54 -0
- trueppm_api/core/db.py +87 -0
- trueppm_api/core/db_session.py +37 -0
- trueppm_api/core/exception_handlers.py +157 -0
- trueppm_api/core/export_downloads.py +56 -0
- trueppm_api/core/extension_providers.py +56 -0
- trueppm_api/core/extension_signals.py +147 -0
- trueppm_api/core/idempotent.py +260 -0
- trueppm_api/core/metadata.py +42 -0
- trueppm_api/core/middleware.py +51 -0
- trueppm_api/core/openapi.py +949 -0
- trueppm_api/core/password_reset.py +500 -0
- trueppm_api/core/protect_conflict.py +100 -0
- trueppm_api/core/ratelimit.py +148 -0
- trueppm_api/core/redis_throttle.py +82 -0
- trueppm_api/core/refresh_cookie_policy.py +156 -0
- trueppm_api/core/request_body.py +86 -0
- trueppm_api/core/security_checks.py +884 -0
- trueppm_api/core/storage_config.py +105 -0
- trueppm_api/core/text_decode.py +168 -0
- trueppm_api/core/throttling.py +344 -0
- trueppm_api/core/valkey.py +214 -0
- trueppm_api/core/valkey_cache.py +66 -0
- trueppm_api/core/valkey_checks.py +104 -0
- trueppm_api/core/valkey_config.py +39 -0
- trueppm_api/core/worker_broker_wait.py +149 -0
- trueppm_api/core/worker_heartbeat.py +134 -0
- trueppm_api/fields.py +36 -0
- trueppm_api/permissions.py +30 -0
- trueppm_api/py.typed +0 -0
- trueppm_api/routing.py +11 -0
- trueppm_api/settings/__init__.py +0 -0
- trueppm_api/settings/base.py +2455 -0
- trueppm_api/settings/dev.py +273 -0
- trueppm_api/settings/prod.py +228 -0
- trueppm_api/urls.py +153 -0
- trueppm_api/workflows/__init__.py +38 -0
- trueppm_api/workflows/backends/__init__.py +6 -0
- trueppm_api/workflows/backends/default.py +297 -0
- trueppm_api/workflows/consumers/__init__.py +11 -0
- trueppm_api/workflows/consumers/requeue_failed_task.py +65 -0
- trueppm_api/workflows/interface.py +148 -0
- trueppm_api/workflows/registry.py +106 -0
- trueppm_api/workflows/services.py +90 -0
- trueppm_api/wsgi.py +11 -0
- trueppm_api-0.4.0b2.dist-info/METADATA +70 -0
- trueppm_api-0.4.0b2.dist-info/RECORD +759 -0
- trueppm_api-0.4.0b2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
"""Structured-blocker service layer (ADR-0124, #1135/#1134/#1125).
|
|
2
|
+
|
|
3
|
+
The organizing principle of the blocker wave is a privacy split:
|
|
4
|
+
|
|
5
|
+
* ``blocker_type`` + age (``blocked_since``) + actor (``blocked_by``) +
|
|
6
|
+
``blocking_task`` are the **team-shareable, queryable, routable** triage signal.
|
|
7
|
+
* ``blocked_reason`` (free text) is **PRIVATE** — visible only to the task's
|
|
8
|
+
assignee and the users @-mentioned on it. It is contributor voice and must
|
|
9
|
+
NEVER become a filterable/queryable PM-surveillance field (Morgan's hard-NO).
|
|
10
|
+
|
|
11
|
+
This module is the single source of truth for that boundary:
|
|
12
|
+
|
|
13
|
+
* :func:`can_read_blocker_reason` — the one authorization predicate every read
|
|
14
|
+
surface (the task serializer, both roll-ups, the standup) consults.
|
|
15
|
+
* :func:`resolve_impediment_recipients` — the SM/PM recipient resolver for the
|
|
16
|
+
``task.blocked`` notification (#1134).
|
|
17
|
+
* :func:`blocked_age_seconds` / :func:`project_blocked_rollup` /
|
|
18
|
+
:func:`sprint_blocked_rollup` — the read-only roll-up endpoints, none of which
|
|
19
|
+
carry reason text.
|
|
20
|
+
|
|
21
|
+
Keeping the gate in one helper (rather than re-deriving the predicate at each
|
|
22
|
+
surface) is the mitigation for the highest-rated risk in the ADR: a reason leak
|
|
23
|
+
through any one inconsistent surface.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from typing import TYPE_CHECKING, Any
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from django.contrib.auth.models import AbstractBaseUser
|
|
32
|
+
|
|
33
|
+
from trueppm_api.apps.projects.models import Task
|
|
34
|
+
|
|
35
|
+
# Upper bound on the ``?min_age_days=`` roll-up filter (#1157, ADR-0165). ~100 years
|
|
36
|
+
# — far beyond any real impediment age — chosen so an out-of-range value fails loud
|
|
37
|
+
# as a 400 rather than overflowing ``timedelta`` into an uncaught 500.
|
|
38
|
+
MAX_MIN_AGE_DAYS = 36500
|
|
39
|
+
|
|
40
|
+
# Hard ceiling on rows returned by the blocked roll-ups (#2855). The roll-up is a
|
|
41
|
+
# triage surface — a human reads it oldest-first and clears from the top — so an
|
|
42
|
+
# unbounded response is never *useful*, only expensive: before this cap a project
|
|
43
|
+
# could return every flagged-blocked task it had, with no LIMIT and no pagination.
|
|
44
|
+
# A cap rather than a page: adding a fourth pagination envelope to this bespoke
|
|
45
|
+
# ``{count, blocked}`` shape is exactly the fragmentation #2842 is closing. Rows are
|
|
46
|
+
# ordered oldest-blocked first, so a truncated response keeps the rows that matter
|
|
47
|
+
# and ``truncated`` tells the client the tail was dropped.
|
|
48
|
+
MAX_BLOCKED_ROLLUP_ROWS = 500
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def can_read_blocker_reason(task: Task, user: AbstractBaseUser | None) -> bool:
|
|
52
|
+
"""Return whether ``user`` may read ``task.blocked_reason`` (the private free text).
|
|
53
|
+
|
|
54
|
+
The reason text is contributor voice. Only two parties may read it:
|
|
55
|
+
|
|
56
|
+
1. the task's **assignee** (it is their own blocker), and
|
|
57
|
+
2. any user **@-mentioned on the task** (they were explicitly looped in).
|
|
58
|
+
|
|
59
|
+
Everyone else — including the PM, the Scrum Master, and any other project
|
|
60
|
+
member — gets the structured signal (type/age/actor/link) but never the
|
|
61
|
+
reason. This is the Morgan surveillance boundary; it is enforced here once
|
|
62
|
+
and consulted by every read surface so there is no divergent second copy.
|
|
63
|
+
|
|
64
|
+
A ``None`` / unauthenticated user can never read the reason.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
task: The blocked task whose reason visibility is being decided.
|
|
68
|
+
user: The requesting user (``request.user``), or ``None``.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
``True`` iff the reason text may be included in the response for this user.
|
|
72
|
+
"""
|
|
73
|
+
if user is None or not getattr(user, "is_authenticated", False):
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
# The assignee owns the blocker — they always see their own reason.
|
|
77
|
+
if task.assignee_id is not None and str(task.assignee_id) == str(user.pk):
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
# @-mentioned on the task: a Mention whose source comment is on this task and
|
|
81
|
+
# whose ``mentioned_user`` is the requester. Today the only mention source is
|
|
82
|
+
# ``task_comment`` (a future ``task_note`` source — #476 — would widen this).
|
|
83
|
+
#
|
|
84
|
+
# ``annotate_tasks_queryset`` primes this as an Exists() subquery (#2855) so the
|
|
85
|
+
# list path costs no per-row query. Trust it only when it was computed for THIS
|
|
86
|
+
# user: the annotation is viewer-specific, so honoring one built for a different
|
|
87
|
+
# requester would leak contributor voice across the Morgan boundary. Any other
|
|
88
|
+
# caller — an un-annotated instance, or one annotated for someone else — falls
|
|
89
|
+
# through to the live query below, which is always correct.
|
|
90
|
+
annotated_for = getattr(task, "viewer_is_mentioned_for", None)
|
|
91
|
+
if annotated_for is not None and str(annotated_for) == str(user.pk):
|
|
92
|
+
return bool(getattr(task, "viewer_is_mentioned", False))
|
|
93
|
+
|
|
94
|
+
from trueppm_api.apps.notifications.models import Mention
|
|
95
|
+
|
|
96
|
+
return Mention.objects.filter(
|
|
97
|
+
task_comment__task_id=task.pk,
|
|
98
|
+
mentioned_user_id=user.pk,
|
|
99
|
+
).exists()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def blocked_age_seconds(task_blocked_since: Any) -> int | None:
|
|
103
|
+
"""Return the integer seconds a task has been blocked, or ``None`` if not blocked.
|
|
104
|
+
|
|
105
|
+
Age (not the reason) is the escalation signal: an impediment open for two days
|
|
106
|
+
routes differently than one open for ten minutes. Derived from
|
|
107
|
+
``blocked_since`` (stamped in ``Task.save()`` on the flag transition). Clamped
|
|
108
|
+
at zero so a clock skew can never produce a negative age.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
task_blocked_since: The ``blocked_since`` datetime, or ``None``.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Whole seconds since the task was flagged, ``None`` when ``blocked_since``
|
|
115
|
+
is unset (the task is not flagged blocked).
|
|
116
|
+
"""
|
|
117
|
+
if task_blocked_since is None:
|
|
118
|
+
return None
|
|
119
|
+
from django.utils import timezone
|
|
120
|
+
|
|
121
|
+
delta = timezone.now() - task_blocked_since
|
|
122
|
+
return max(0, int(delta.total_seconds()))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def resolve_impediment_recipients(task: Task) -> set[Any]:
|
|
126
|
+
"""Resolve the user PKs that should be notified a task was flagged blocked (#1134).
|
|
127
|
+
|
|
128
|
+
Bridges project → team → facet (ADR-0078) and project → role (the access
|
|
129
|
+
matrix) to reach the people whose job is to clear impediments:
|
|
130
|
+
|
|
131
|
+
* the **assignee** (the owner of the blocked work — existing behavior), plus
|
|
132
|
+
* every **Scrum Master** (``TeamMembership.is_scrum_master``) on the task's
|
|
133
|
+
project's default team, plus
|
|
134
|
+
* every **PM** — a project membership with ``role >= Role.ADMIN``.
|
|
135
|
+
|
|
136
|
+
The actor who raised the flag is intentionally NOT excluded here: a Scrum
|
|
137
|
+
Master or PM who flags someone else's task still wants the assignee notified,
|
|
138
|
+
and the assignee-vs-actor exclusion is applied separately at the call site for
|
|
139
|
+
the assignee path only (preserving the existing "notify assignee, not actor"
|
|
140
|
+
contract). De-duped across all three sources.
|
|
141
|
+
|
|
142
|
+
If a project has no team / no SM facet set, the resolver still returns the
|
|
143
|
+
PM(s) — so a blocker always reaches someone who can act (ADR-0124 §Risks).
|
|
144
|
+
|
|
145
|
+
**All three arms are scoped to live ``ProjectMembership`` (#3334).** The PM arm
|
|
146
|
+
always was. The SM arm reached the facet through a hand-rolled ``TeamMembership``
|
|
147
|
+
query that filtered neither the project row nor ``Team.is_deleted``, so a member
|
|
148
|
+
whose project membership had been revoked — or one holding the facet on a
|
|
149
|
+
soft-deleted team — kept receiving a notice naming a specific task on a project
|
|
150
|
+
they can no longer open. It now routes through
|
|
151
|
+
:func:`~trueppm_api.apps.teams.services.facet_holder_user_ids`, where that guard
|
|
152
|
+
lives once for all facet-sourced cohorts rather than being re-remembered per
|
|
153
|
+
call site. Two consequences of reading the shared helper: the facet is taken from
|
|
154
|
+
the **default** team only (as every other ADR-0078 facet reader already does —
|
|
155
|
+
non-default teams are not creatable through the API, #599), and the ``facets=``
|
|
156
|
+
restriction keeps this cohort SM-only. A Product Owner is not an
|
|
157
|
+
impediment-clearer and ADR-0124 does not route to them.
|
|
158
|
+
|
|
159
|
+
The assignee arm needed the floor too, for a reason worth stating because it is
|
|
160
|
+
not the facet story. ``TaskSerializer._validate_assignee_membership`` (#684)
|
|
161
|
+
already refuses to *set* an assignee who is not a live project member, so "the
|
|
162
|
+
assignee is a member" is an invariant the write path enforces. Nothing re-checks
|
|
163
|
+
it afterwards, and revoking a membership does not clear ``Task.assignee`` — so
|
|
164
|
+
the value simply goes stale, and reading it unguarded meant an offboarded
|
|
165
|
+
assignee kept being told their own task was flagged. Flooring it here enforces at
|
|
166
|
+
read what #684 already enforces at write, and brings this resolver in step with
|
|
167
|
+
its sibling :func:`~trueppm_api.apps.projects.config_notice.assigned_recipient_ids`,
|
|
168
|
+
which floors its assignee cohort for the same reason.
|
|
169
|
+
|
|
170
|
+
One ``ProjectMembership`` read serves both the assignee floor and the PM arm, so
|
|
171
|
+
the query count is unchanged at two.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
task: The task that was just flagged blocked.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
A de-duplicated set of user PKs to notify, gated downstream by each
|
|
178
|
+
recipient's own ``NotificationPreference``.
|
|
179
|
+
"""
|
|
180
|
+
from trueppm_api.apps.access.models import ProjectMembership, Role
|
|
181
|
+
from trueppm_api.apps.teams.services import facet_holder_user_ids
|
|
182
|
+
|
|
183
|
+
live_roles: dict[Any, int] = dict(
|
|
184
|
+
ProjectMembership.objects.filter(project_id=task.project_id, is_deleted=False).values_list(
|
|
185
|
+
"user_id", "role"
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
recipients: set[Any] = set()
|
|
190
|
+
|
|
191
|
+
if task.assignee_id is not None and task.assignee_id in live_roles:
|
|
192
|
+
recipients.add(task.assignee_id)
|
|
193
|
+
|
|
194
|
+
recipients.update(facet_holder_user_ids(task.project_id, facets=("is_scrum_master",)))
|
|
195
|
+
|
|
196
|
+
# PM(s): project membership at ADMIN or above (Project Manager / Project Admin).
|
|
197
|
+
recipients.update(uid for uid, role in live_roles.items() if role >= Role.ADMIN)
|
|
198
|
+
|
|
199
|
+
return recipients
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def task_deep_link(task: Task) -> str:
|
|
203
|
+
"""Return the public frontend deep-link to a task, or ``""`` when unconfigured.
|
|
204
|
+
|
|
205
|
+
Matches the path the in-app inbox navigates to so web and email resolve to the
|
|
206
|
+
same place (``NotificationRow.tsx`` → ``/projects/{id}/schedule?task={id}``); the
|
|
207
|
+
SPA renders that route responsively, so the link is mobile-friendly (#1158).
|
|
208
|
+
|
|
209
|
+
Built from ``settings.FRONTEND_BASE_URL`` (the public origin the API is served
|
|
210
|
+
behind). When that is empty — the zero-config default — there is no reachable
|
|
211
|
+
public URL to point at, so this returns ``""`` and callers omit the link line
|
|
212
|
+
rather than emit a broken relative link.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
task: The task to link to.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
An absolute task URL, or ``""`` when ``FRONTEND_BASE_URL`` is unset.
|
|
219
|
+
"""
|
|
220
|
+
from django.conf import settings
|
|
221
|
+
|
|
222
|
+
base = (getattr(settings, "FRONTEND_BASE_URL", "") or "").rstrip("/")
|
|
223
|
+
if not base:
|
|
224
|
+
return ""
|
|
225
|
+
return f"{base}/projects/{task.project_id}/schedule?task={task.pk}"
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def render_blocker_notification(task: Task) -> tuple[str, str]:
|
|
229
|
+
"""Render the ``task.blocked`` subject + body — type/age/actor/link, NEVER reason.
|
|
230
|
+
|
|
231
|
+
The privacy boundary is enforced at the point of render, not just at the
|
|
232
|
+
serializer: the notification body that reaches the SM/PM (and is frozen onto
|
|
233
|
+
the email row by the drain) carries the structured triage signal — the blocker
|
|
234
|
+
type label, a coarse age, the actor who flagged it, the soft ``blocking_task``
|
|
235
|
+
link, and a deep-link to the task — and never the free-text reason. A PM must
|
|
236
|
+
not be able to read a contributor's reason through a notification body
|
|
237
|
+
(ADR-0124 §4).
|
|
238
|
+
|
|
239
|
+
The actor / blocking-task / deep-link lines (#1158) make the email actionable:
|
|
240
|
+
the recipient sees who flagged it and what it waits on, and opens the task in
|
|
241
|
+
one tap instead of hunting for it. Each enrichment line is emitted only when its
|
|
242
|
+
source is present, so a bare flag still renders cleanly, and the link line is
|
|
243
|
+
omitted entirely when ``FRONTEND_BASE_URL`` is unset.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
task: The freshly-flagged task.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
``(subject, body)`` — both reason-free.
|
|
250
|
+
"""
|
|
251
|
+
from trueppm_api.apps.projects.models import BlockerType
|
|
252
|
+
|
|
253
|
+
type_label = ""
|
|
254
|
+
if task.blocker_type:
|
|
255
|
+
try:
|
|
256
|
+
type_label = BlockerType(task.blocker_type).label
|
|
257
|
+
except ValueError:
|
|
258
|
+
type_label = ""
|
|
259
|
+
|
|
260
|
+
age = blocked_age_seconds(task.blocked_since)
|
|
261
|
+
age_label = _coarse_age_label(age)
|
|
262
|
+
|
|
263
|
+
subject = f"{task.name} is blocked"
|
|
264
|
+
detail_bits = [b for b in (type_label, age_label) if b]
|
|
265
|
+
if detail_bits:
|
|
266
|
+
lead = f'"{task.name}" was flagged blocked — {" · ".join(detail_bits)}.'
|
|
267
|
+
else:
|
|
268
|
+
lead = f'"{task.name}" was flagged blocked.'
|
|
269
|
+
|
|
270
|
+
lines = [lead]
|
|
271
|
+
# Actor + soft link are the already-team-shareable signal (never the reason).
|
|
272
|
+
if task.blocked_by_id and task.blocked_by:
|
|
273
|
+
lines.append(f"Flagged by {task.blocked_by.username}.")
|
|
274
|
+
if task.blocking_task_id and task.blocking_task:
|
|
275
|
+
lines.append(f'Waiting on: "{task.blocking_task.name}".')
|
|
276
|
+
link = task_deep_link(task)
|
|
277
|
+
if link:
|
|
278
|
+
lines += ["", f"Open the task: {link}"]
|
|
279
|
+
|
|
280
|
+
return subject, "\n".join(lines)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _coarse_age_label(age_seconds: int | None) -> str:
|
|
284
|
+
"""Return a coarse human age label ("2d", "3h", "just now") for a blocked age.
|
|
285
|
+
|
|
286
|
+
Coarse by design: the standup/notification never needs sub-hour precision, and
|
|
287
|
+
a coarse label avoids implying minute-level surveillance of when a contributor
|
|
288
|
+
flagged their work.
|
|
289
|
+
"""
|
|
290
|
+
if age_seconds is None:
|
|
291
|
+
return ""
|
|
292
|
+
days = age_seconds // 86400
|
|
293
|
+
if days >= 1:
|
|
294
|
+
return f"{days}d"
|
|
295
|
+
hours = age_seconds // 3600
|
|
296
|
+
if hours >= 1:
|
|
297
|
+
return f"{hours}h"
|
|
298
|
+
return "just now"
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _blocking_task_payload(task: Task) -> dict[str, Any] | None:
|
|
302
|
+
"""Serialize the soft ``blocking_task`` link as ``{id, short_id, title}`` or ``None``.
|
|
303
|
+
|
|
304
|
+
Uses the already-``select_related`` ``blocking_task`` to avoid an N+1 across a
|
|
305
|
+
roll-up list. The short_id is rendered with the ``T-`` prefix to match the rest
|
|
306
|
+
of the task surfaces.
|
|
307
|
+
"""
|
|
308
|
+
bt = task.blocking_task
|
|
309
|
+
if bt is None:
|
|
310
|
+
return None
|
|
311
|
+
return {
|
|
312
|
+
"id": str(bt.pk),
|
|
313
|
+
"short_id": f"T-{bt.short_id}" if bt.short_id else "",
|
|
314
|
+
"title": bt.name,
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _blocked_row(task: Task) -> dict[str, Any]:
|
|
319
|
+
"""Build one reason-free roll-up row for a blocked task.
|
|
320
|
+
|
|
321
|
+
Reason text is **omitted entirely** from roll-up rows — for everyone, including
|
|
322
|
+
the assignee. The roll-ups are a triage/escalation surface (who is blocked, on
|
|
323
|
+
what type, for how long, waiting on what); the private reason is read on the
|
|
324
|
+
task drawer, gated by :func:`can_read_blocker_reason`. Omitting it here is the
|
|
325
|
+
simplest correct way to guarantee the roll-up can never leak it.
|
|
326
|
+
"""
|
|
327
|
+
return {
|
|
328
|
+
"task_id": str(task.pk),
|
|
329
|
+
"task_short_id": f"T-{task.short_id}" if task.short_id else "",
|
|
330
|
+
"title": task.name,
|
|
331
|
+
"assignee": (
|
|
332
|
+
{"id": str(task.assignee_id), "username": task.assignee.username}
|
|
333
|
+
if task.assignee_id and task.assignee
|
|
334
|
+
else None
|
|
335
|
+
),
|
|
336
|
+
"blocker_type": task.blocker_type or None,
|
|
337
|
+
"blocked_since": task.blocked_since.isoformat() if task.blocked_since else None,
|
|
338
|
+
"blocked_age_seconds": blocked_age_seconds(task.blocked_since),
|
|
339
|
+
"blocked_by": (
|
|
340
|
+
{"id": str(task.blocked_by_id), "username": task.blocked_by.username}
|
|
341
|
+
if task.blocked_by_id and task.blocked_by
|
|
342
|
+
else None
|
|
343
|
+
),
|
|
344
|
+
"blocking_task": _blocking_task_payload(task),
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def parse_blocked_filters(query_params: Any) -> dict[str, Any]:
|
|
349
|
+
"""Validate the roll-up filter query params into kwargs for the roll-up builders (#1157).
|
|
350
|
+
|
|
351
|
+
Accepts the two team-shareable triage filters and **nothing reason-derived** —
|
|
352
|
+
the Morgan boundary (ADR-0124 §4) is structural here: ``blocked_reason`` is not a
|
|
353
|
+
recognized key, so there is no path by which the private reason becomes
|
|
354
|
+
queryable. Both params are optional; absent → no filtering (today's behavior).
|
|
355
|
+
|
|
356
|
+
* ``blocker_type`` — must be a member of :class:`BlockerType`. An unknown value
|
|
357
|
+
raises (→ 400) rather than silently returning an empty list, so an automated
|
|
358
|
+
escalation report fails loud on a typo instead of under-counting.
|
|
359
|
+
* ``min_age_days`` — a non-negative integer; ``0`` is a no-op. A negative,
|
|
360
|
+
non-integer, or absurdly large value (> ``MAX_MIN_AGE_DAYS``, ~100 years)
|
|
361
|
+
raises (→ 400) — the cap keeps an out-of-range input on the fail-loud 400 path
|
|
362
|
+
instead of overflowing ``timedelta`` into an uncaught 500.
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
query_params: The request ``query_params`` (a ``QueryDict``-like mapping).
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
``{"blocker_type": <str|None>, "min_age_days": <int|None>}`` — the kwargs the
|
|
369
|
+
roll-up builders thread into :func:`_blocked_queryset`.
|
|
370
|
+
|
|
371
|
+
Raises:
|
|
372
|
+
rest_framework.exceptions.ValidationError: on an unknown ``blocker_type`` or a
|
|
373
|
+
negative / non-integer ``min_age_days``.
|
|
374
|
+
"""
|
|
375
|
+
from rest_framework.exceptions import ValidationError
|
|
376
|
+
|
|
377
|
+
from trueppm_api.apps.projects.models import BlockerType
|
|
378
|
+
|
|
379
|
+
blocker_type = query_params.get("blocker_type") or None
|
|
380
|
+
if blocker_type is not None and blocker_type not in set(BlockerType.values):
|
|
381
|
+
raise ValidationError(
|
|
382
|
+
{"blocker_type": [f"Unknown blocker_type. Valid: {', '.join(BlockerType.values)}."]}
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
min_age_days: int | None = None
|
|
386
|
+
raw_age = query_params.get("min_age_days")
|
|
387
|
+
if raw_age is not None and raw_age != "":
|
|
388
|
+
try:
|
|
389
|
+
min_age_days = int(raw_age)
|
|
390
|
+
except (TypeError, ValueError):
|
|
391
|
+
raise ValidationError({"min_age_days": ["Must be a non-negative integer."]}) from None
|
|
392
|
+
if min_age_days < 0 or min_age_days > MAX_MIN_AGE_DAYS:
|
|
393
|
+
raise ValidationError(
|
|
394
|
+
{"min_age_days": [f"Must be an integer between 0 and {MAX_MIN_AGE_DAYS}."]}
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
return {"blocker_type": blocker_type, "min_age_days": min_age_days}
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _blocked_queryset(
|
|
401
|
+
base_qs: Any, *, blocker_type: str | None = None, min_age_days: int | None = None
|
|
402
|
+
) -> Any:
|
|
403
|
+
"""Filter a Task queryset to flagged-blocked rows, oldest-blocked first.
|
|
404
|
+
|
|
405
|
+
"Blocked" is the flag-of-record: a non-empty ``blocked_reason``. Ordered by
|
|
406
|
+
``blocked_since`` ascending (oldest/most-aged first) because age drives
|
|
407
|
+
escalation — the SM/PM want the longest-running impediments at the top. Rows
|
|
408
|
+
are ``select_related`` on ``assignee``, ``blocked_by``, and ``blocking_task``
|
|
409
|
+
so the roll-up serialization adds no per-row query.
|
|
410
|
+
|
|
411
|
+
The optional ``blocker_type`` / ``min_age_days`` filters (#1157) narrow the list
|
|
412
|
+
on the team-shareable structured signal only. ``min_age_days`` filters on
|
|
413
|
+
``blocked_since`` (the age clock), keeping rows flagged at or before
|
|
414
|
+
``now - N days``. Neither filter touches ``blocked_reason``.
|
|
415
|
+
"""
|
|
416
|
+
qs = (
|
|
417
|
+
base_qs.exclude(blocked_reason="")
|
|
418
|
+
.filter(is_deleted=False)
|
|
419
|
+
.select_related("assignee", "blocked_by", "blocking_task")
|
|
420
|
+
.order_by("blocked_since", "id")
|
|
421
|
+
)
|
|
422
|
+
if blocker_type:
|
|
423
|
+
qs = qs.filter(blocker_type=blocker_type)
|
|
424
|
+
if min_age_days:
|
|
425
|
+
from datetime import timedelta
|
|
426
|
+
|
|
427
|
+
from django.utils import timezone
|
|
428
|
+
|
|
429
|
+
cutoff = timezone.now() - timedelta(days=min_age_days)
|
|
430
|
+
# blocked_since <= cutoff ⇒ blocked at least min_age_days ago. NULL
|
|
431
|
+
# blocked_since (legacy rows never re-saved) cannot satisfy an age floor.
|
|
432
|
+
qs = qs.filter(blocked_since__isnull=False, blocked_since__lte=cutoff)
|
|
433
|
+
return qs
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _capped_blocked_rows(qs: Any) -> tuple[list[dict[str, Any]], bool]:
|
|
437
|
+
"""Serialize at most :data:`MAX_BLOCKED_ROLLUP_ROWS` roll-up rows.
|
|
438
|
+
|
|
439
|
+
Slices one row past the cap so "there was more" is known without a second
|
|
440
|
+
COUNT query, then discards the extra. ``truncated`` is the signal the client
|
|
441
|
+
needs to say "showing the 500 oldest" rather than silently implying the list
|
|
442
|
+
is complete.
|
|
443
|
+
"""
|
|
444
|
+
window = list(qs[: MAX_BLOCKED_ROLLUP_ROWS + 1])
|
|
445
|
+
truncated = len(window) > MAX_BLOCKED_ROLLUP_ROWS
|
|
446
|
+
return [_blocked_row(t) for t in window[:MAX_BLOCKED_ROLLUP_ROWS]], truncated
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def project_blocked_rollup(
|
|
450
|
+
project: Any, *, blocker_type: str | None = None, min_age_days: int | None = None
|
|
451
|
+
) -> dict[str, Any]:
|
|
452
|
+
"""Return the reason-free "blocked tasks on this project" roll-up (#1134/#1157).
|
|
453
|
+
|
|
454
|
+
Backs ``GET /projects/{id}/blocked/``. Lists every flagged-blocked task in the
|
|
455
|
+
project (oldest-blocked first) with type + age + actor + assignee +
|
|
456
|
+
blocking-task link — and **no reason text** (see :func:`_blocked_row`).
|
|
457
|
+
Optionally narrowed by ``blocker_type`` / ``min_age_days`` (#1157).
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
project: The ``Project`` whose blocked tasks to list.
|
|
461
|
+
blocker_type: Optional :class:`BlockerType` value to match.
|
|
462
|
+
min_age_days: Optional minimum blocked-age in days.
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
``{"project_id", "count", "blocked": [<row>, ...], "truncated": bool}`` —
|
|
466
|
+
capped at :data:`MAX_BLOCKED_ROLLUP_ROWS` rows, oldest-blocked first.
|
|
467
|
+
"""
|
|
468
|
+
qs = _blocked_queryset(project.tasks, blocker_type=blocker_type, min_age_days=min_age_days)
|
|
469
|
+
rows, truncated = _capped_blocked_rows(qs)
|
|
470
|
+
return {
|
|
471
|
+
"project_id": str(project.pk),
|
|
472
|
+
"count": len(rows),
|
|
473
|
+
"blocked": rows,
|
|
474
|
+
"truncated": truncated,
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def sprint_blocked_rollup(
|
|
479
|
+
sprint: Any, *, blocker_type: str | None = None, min_age_days: int | None = None
|
|
480
|
+
) -> dict[str, Any]:
|
|
481
|
+
"""Return the reason-free "blocked tasks in this sprint" roll-up (#1134/#1157).
|
|
482
|
+
|
|
483
|
+
Backs ``GET /sprints/{id}/blocked/`` — the SM's sprint-scoped impediment list.
|
|
484
|
+
Same shape and same reason-omission as :func:`project_blocked_rollup`, scoped
|
|
485
|
+
to the sprint's tasks, with the same optional ``blocker_type`` / ``min_age_days``
|
|
486
|
+
filters (#1157).
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
sprint: The ``Sprint`` whose blocked tasks to list.
|
|
490
|
+
blocker_type: Optional :class:`BlockerType` value to match.
|
|
491
|
+
min_age_days: Optional minimum blocked-age in days.
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
``{"sprint_id", "count", "blocked": [<row>, ...], "truncated": bool}`` —
|
|
495
|
+
capped at :data:`MAX_BLOCKED_ROLLUP_ROWS` rows, oldest-blocked first.
|
|
496
|
+
"""
|
|
497
|
+
from trueppm_api.apps.projects.models import Task
|
|
498
|
+
|
|
499
|
+
qs = _blocked_queryset(
|
|
500
|
+
Task.objects.filter(sprint_id=sprint.pk),
|
|
501
|
+
blocker_type=blocker_type,
|
|
502
|
+
min_age_days=min_age_days,
|
|
503
|
+
)
|
|
504
|
+
rows, truncated = _capped_blocked_rows(qs)
|
|
505
|
+
return {
|
|
506
|
+
"sprint_id": str(sprint.pk),
|
|
507
|
+
"count": len(rows),
|
|
508
|
+
"blocked": rows,
|
|
509
|
+
"truncated": truncated,
|
|
510
|
+
}
|