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,462 @@
|
|
|
1
|
+
"""Board-level activity feed aggregator (ADR-0160, #325).
|
|
2
|
+
|
|
3
|
+
A read-only aggregator that joins existing change-history sources into one
|
|
4
|
+
time-ordered, board-scoped feed of card mutations. No new model, no migration —
|
|
5
|
+
every event already has a durable home:
|
|
6
|
+
|
|
7
|
+
* ``HistoricalTask`` (django-simple-history on ``Task``) — field changes diffed
|
|
8
|
+
against the same task's immediately-prior row over a curated board-relevant
|
|
9
|
+
allowlist (``task_created`` / ``task_updated`` / ``task_deleted``), plus the
|
|
10
|
+
``sprint`` delta surfaced as a first-class ``entered_sprint`` / ``exited_sprint``
|
|
11
|
+
/ ``moved_sprint`` event (the #325 AC's mid-sprint scope events).
|
|
12
|
+
* ``TaskComment`` creates — ``comment_added``.
|
|
13
|
+
|
|
14
|
+
The feed is built in Python (the ``TaskHistoryView`` precedent paginates a Python
|
|
15
|
+
list) but **bounded**: each source is queried for events older than the ``until``
|
|
16
|
+
cursor, capped at ``OVERFETCH * limit`` rows, and the three lists are merge-sorted
|
|
17
|
+
by timestamp DESC. Pagination is keyset-on-timestamp via ``until`` (the client passes
|
|
18
|
+
the returned ``next_until`` back) — no offset rebuild, no cursor-on-non-unique-leading
|
|
19
|
+
trap. ``HistoricalTask`` diffs are computed within the fetched batch (no per-row
|
|
20
|
+
``prev_record`` query → no N+1); the documented edge is that a change whose prior row
|
|
21
|
+
fell outside the batch surfaces with an empty ``changes`` list.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from collections import defaultdict
|
|
27
|
+
from datetime import datetime
|
|
28
|
+
from typing import TYPE_CHECKING, Any
|
|
29
|
+
|
|
30
|
+
from django.contrib.auth import get_user_model
|
|
31
|
+
from django.utils import timezone
|
|
32
|
+
|
|
33
|
+
from trueppm_api.apps.access.models import Role
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
from trueppm_api.apps.projects.models import Project
|
|
37
|
+
|
|
38
|
+
# Board-relevant Task fields surfaced as field-change deltas. CPM outputs and sync
|
|
39
|
+
# internals are already excluded from HistoricalTask; this is a further curation to
|
|
40
|
+
# the fields a board reader cares about (not the full per-task drawer history).
|
|
41
|
+
# ``sprint`` is deliberately NOT here — a sprint move is its own first-class event.
|
|
42
|
+
_BOARD_DIFF_FIELDS: tuple[str, ...] = (
|
|
43
|
+
"name",
|
|
44
|
+
"status",
|
|
45
|
+
"percent_complete",
|
|
46
|
+
"story_points",
|
|
47
|
+
"remaining_points",
|
|
48
|
+
"assignee_id",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Cost/budget delta fields hidden from below-MEMBER readers (ADR-0160 RBAC). There
|
|
52
|
+
# are no cost fields on Task yet (intentionally absent until the cost model #73);
|
|
53
|
+
# the set is empty today so the gate is a no-op, but it is the live seam the moment
|
|
54
|
+
# cost fields land — the AC ("Viewer doesn't see cost-field deltas") is satisfied
|
|
55
|
+
# structurally, not deferred.
|
|
56
|
+
_COST_FIELDS: frozenset[str] = frozenset()
|
|
57
|
+
|
|
58
|
+
_TASK_EVENT_TYPES: frozenset[str] = frozenset({"task_created", "task_updated", "task_deleted"})
|
|
59
|
+
_SPRINT_EVENT_TYPES: frozenset[str] = frozenset({"entered_sprint", "exited_sprint", "moved_sprint"})
|
|
60
|
+
_COMMENT_EVENT_TYPES: frozenset[str] = frozenset({"comment_added"})
|
|
61
|
+
EVENT_TYPES: frozenset[str] = _TASK_EVENT_TYPES | _SPRINT_EVENT_TYPES | _COMMENT_EVENT_TYPES
|
|
62
|
+
|
|
63
|
+
_OVERFETCH = 4 # rows fetched per source = OVERFETCH * limit (bounded scan)
|
|
64
|
+
DEFAULT_LIMIT = 50
|
|
65
|
+
MAX_LIMIT = 100
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _scalar(value: Any) -> str | None:
|
|
69
|
+
return str(value) if value is not None else None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def build_board_activity(
|
|
73
|
+
project: Project,
|
|
74
|
+
*,
|
|
75
|
+
until: datetime | None = None,
|
|
76
|
+
since: datetime | None = None,
|
|
77
|
+
actor_id: Any = None,
|
|
78
|
+
event_types: set[str] | None = None,
|
|
79
|
+
limit: int = DEFAULT_LIMIT,
|
|
80
|
+
role: int | None = None,
|
|
81
|
+
sprint_id: str | None = None,
|
|
82
|
+
) -> dict[str, Any]:
|
|
83
|
+
"""Build one page of the board activity feed (ADR-0160).
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
project: the board's project.
|
|
87
|
+
until: keyset cursor — return events strictly older than this (default now).
|
|
88
|
+
since: optional lower time bound (inclusive).
|
|
89
|
+
actor_id: optional filter to one actor (user id).
|
|
90
|
+
event_types: optional set of ``EVENT_TYPES`` to include (default all).
|
|
91
|
+
limit: page size (clamped to ``[1, MAX_LIMIT]``).
|
|
92
|
+
role: the requester's project ``Role`` ordinal — drives cost-field gating.
|
|
93
|
+
sprint_id: optional sprint scope (ADR-0412, #1946) — narrow to events whose
|
|
94
|
+
task is currently in this sprint OR that reference this sprint directly
|
|
95
|
+
(an entered/exited/moved event whose old-or-new sprint is this one), so a
|
|
96
|
+
removal stays visible in the sprint it left even after the task moves on.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
``{"results": [event, ...], "next_until": <iso str | None>}`` where each event
|
|
100
|
+
is ``{id, event_type, actor, actor_id, timestamp, task_id, task_name,
|
|
101
|
+
changes: [{field, old, new}], sprint_id, scope_change_status}``
|
|
102
|
+
(``scope_change_status`` is the SprintScopeChange accept-gate status on
|
|
103
|
+
``entered_sprint`` events, else null). ``next_until`` is the oldest
|
|
104
|
+
returned timestamp (pass it back as ``until`` for the next page), or ``None``
|
|
105
|
+
when the window is exhausted.
|
|
106
|
+
"""
|
|
107
|
+
limit = max(1, min(int(limit), MAX_LIMIT))
|
|
108
|
+
if until is None:
|
|
109
|
+
until = timezone.now()
|
|
110
|
+
wanted = event_types if event_types else EVENT_TYPES
|
|
111
|
+
cap = _OVERFETCH * limit
|
|
112
|
+
|
|
113
|
+
events: list[dict[str, Any]] = []
|
|
114
|
+
if wanted & (_TASK_EVENT_TYPES | _SPRINT_EVENT_TYPES):
|
|
115
|
+
events.extend(_history_events(project, until, since, wanted, cap, role))
|
|
116
|
+
if wanted & _COMMENT_EVENT_TYPES:
|
|
117
|
+
events.extend(_comment_events(project, until, since, cap))
|
|
118
|
+
|
|
119
|
+
# Sprint scope (ADR-0412, #1946) is applied AFTER the per-source fetch for the
|
|
120
|
+
# same reason as the actor post-filter below: pushing ``task__sprint_id`` into
|
|
121
|
+
# the HistoricalTask query would drop the immediately-prior row a diff needs, and
|
|
122
|
+
# it could not express the "event references the sprint it *left*" case that keeps
|
|
123
|
+
# a removal visible in the sprint it was pulled from.
|
|
124
|
+
if sprint_id is not None:
|
|
125
|
+
events = _filter_by_sprint(events, str(sprint_id))
|
|
126
|
+
|
|
127
|
+
# Actor filtering is applied AFTER the per-source fetch, not pushed into the
|
|
128
|
+
# HistoricalTask query: filtering the source rows by actor would drop the
|
|
129
|
+
# immediately-prior row a diff needs (a status change by A diffs against B's
|
|
130
|
+
# prior save), leaving the actor's change with no computable delta.
|
|
131
|
+
if actor_id is not None:
|
|
132
|
+
wanted_actor = str(actor_id)
|
|
133
|
+
events = [e for e in events if e["actor_id"] == wanted_actor]
|
|
134
|
+
|
|
135
|
+
# Merge-sort DESC by timestamp; the stable id breaks exact-timestamp ties so the
|
|
136
|
+
# ordering is deterministic across requests.
|
|
137
|
+
events.sort(key=lambda e: (e["timestamp"], e["id"]), reverse=True)
|
|
138
|
+
|
|
139
|
+
page = events[:limit]
|
|
140
|
+
next_until: str | None = None
|
|
141
|
+
if len(events) > limit and page:
|
|
142
|
+
# More rows beyond this page existed in the fetched window — hand the client
|
|
143
|
+
# the oldest returned timestamp to resume from (keyset).
|
|
144
|
+
next_until = page[-1]["timestamp"].isoformat()
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
"results": [_serialize_event(e) for e in page],
|
|
148
|
+
"next_until": next_until,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _filter_by_sprint(events: list[dict[str, Any]], sprint_id: str) -> list[dict[str, Any]]:
|
|
153
|
+
"""Narrow the feed to one sprint's scope (ADR-0412, #1946).
|
|
154
|
+
|
|
155
|
+
An event is in scope when its task is CURRENTLY in ``sprint_id`` (``task.sprint_id``,
|
|
156
|
+
read live in one bulk query — not from the historical row, so the feed tracks where
|
|
157
|
+
a task *is now*) OR the event itself references ``sprint_id`` directly (an
|
|
158
|
+
entered/exited/moved event whose old-or-new sprint is this one). The second clause is
|
|
159
|
+
what keeps a *removal* visible in the sprint it left: an ``exited_sprint`` carries the
|
|
160
|
+
left sprint as its old id even though the task's current sprint is now null/elsewhere.
|
|
161
|
+
"""
|
|
162
|
+
task_ids = {e["task_id"] for e in events}
|
|
163
|
+
current = _current_sprint_map(task_ids)
|
|
164
|
+
out: list[dict[str, Any]] = []
|
|
165
|
+
for e in events:
|
|
166
|
+
if current.get(e["task_id"]) == sprint_id or sprint_id in (
|
|
167
|
+
e.get("_old_sprint_id"),
|
|
168
|
+
e.get("_new_sprint_id"),
|
|
169
|
+
):
|
|
170
|
+
out.append(e)
|
|
171
|
+
return out
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _current_sprint_map(task_ids: set[str]) -> dict[str, str | None]:
|
|
175
|
+
"""Map each task id (str) to its CURRENT ``sprint_id`` (str or None), in one query."""
|
|
176
|
+
if not task_ids:
|
|
177
|
+
return {}
|
|
178
|
+
from trueppm_api.apps.projects.models import Task
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
str(pk): (str(sid) if sid is not None else None)
|
|
182
|
+
for pk, sid in Task.objects.filter(pk__in=task_ids).values_list("pk", "sprint_id")
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _history_events(
|
|
187
|
+
project: Project,
|
|
188
|
+
until: datetime,
|
|
189
|
+
since: datetime | None,
|
|
190
|
+
wanted: set[str] | frozenset[str],
|
|
191
|
+
cap: int,
|
|
192
|
+
role: int | None,
|
|
193
|
+
) -> list[dict[str, Any]]:
|
|
194
|
+
"""Field-change + sprint-transition events from HistoricalTask (bounded batch)."""
|
|
195
|
+
from trueppm_api.apps.projects.models import Task
|
|
196
|
+
|
|
197
|
+
HistoricalTask = Task.history.model
|
|
198
|
+
|
|
199
|
+
qs = HistoricalTask.objects.filter(project_id=project.pk, history_date__lt=until)
|
|
200
|
+
if since is not None:
|
|
201
|
+
qs = qs.filter(history_date__gte=since)
|
|
202
|
+
rows = list(qs.order_by("-history_date")[:cap])
|
|
203
|
+
|
|
204
|
+
# Group newest-first per task so each row can diff against the next-older row in
|
|
205
|
+
# the batch without a per-row prev_record query (no N+1).
|
|
206
|
+
by_task: dict[Any, list[Any]] = defaultdict(list)
|
|
207
|
+
for row in rows:
|
|
208
|
+
by_task[row.id].append(row)
|
|
209
|
+
|
|
210
|
+
# Resolve the user + sprint references that appear, in two bulk queries.
|
|
211
|
+
user_ids = {r.history_user_id for r in rows if r.history_user_id is not None}
|
|
212
|
+
user_ids |= {r.assignee_id for r in rows if r.assignee_id is not None}
|
|
213
|
+
sprint_ids = {r.sprint_id for r in rows if r.sprint_id is not None}
|
|
214
|
+
user_names = _user_name_map(user_ids)
|
|
215
|
+
sprint_names = _sprint_name_map(sprint_ids)
|
|
216
|
+
|
|
217
|
+
hide_cost = role is not None and role < Role.MEMBER
|
|
218
|
+
events: list[dict[str, Any]] = []
|
|
219
|
+
for group in by_task.values():
|
|
220
|
+
for i, rec in enumerate(group):
|
|
221
|
+
older = group[i + 1] if i + 1 < len(group) else None
|
|
222
|
+
events.extend(
|
|
223
|
+
_events_from_record(rec, older, wanted, user_names, sprint_names, hide_cost)
|
|
224
|
+
)
|
|
225
|
+
_attach_scope_change_status(events)
|
|
226
|
+
return events
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _attach_scope_change_status(events: list[dict[str, Any]]) -> None:
|
|
230
|
+
"""Enrich ``entered_sprint`` events with the ``SprintScopeChange`` accept-gate status.
|
|
231
|
+
|
|
232
|
+
A *post-activation* mid-sprint injection records a ``SprintScopeChange`` (ADR-0102)
|
|
233
|
+
whose ``status`` (``pending``/``accepted``/``rejected``) is the accept-gate outcome for
|
|
234
|
+
the *same* "task entered sprint X" fact the ``HistoricalTask`` sprint delta already
|
|
235
|
+
yields — which is why ADR-0160 sources the event from history (not also
|
|
236
|
+
``SprintScopeChange``) to avoid double-counting, and deferred surfacing the *status* to
|
|
237
|
+
this enrichment (ADR-0160 Amendment B3, #1264).
|
|
238
|
+
|
|
239
|
+
Stamps ``scope_change_status`` onto each ``entered_sprint`` event via **one batched**
|
|
240
|
+
query keyed by ``(task_id, sprint_id)`` — index-covered by ``scope_change_task_sprint_idx``
|
|
241
|
+
— leaving it ``None`` for a pre-activation entry (no scope-change row). The status is not
|
|
242
|
+
cost-gated, so it is Viewer-readable like the rest of the feed. Mutates ``events`` in
|
|
243
|
+
place; a no-op when the batch holds no ``entered_sprint`` event.
|
|
244
|
+
"""
|
|
245
|
+
keys = {
|
|
246
|
+
(e["task_id"], e["sprint_id"])
|
|
247
|
+
for e in events
|
|
248
|
+
if e["event_type"] == "entered_sprint" and e.get("sprint_id")
|
|
249
|
+
}
|
|
250
|
+
if not keys:
|
|
251
|
+
return
|
|
252
|
+
|
|
253
|
+
from trueppm_api.apps.projects.models import SprintScopeChange
|
|
254
|
+
|
|
255
|
+
status_map: dict[tuple[str, str], str] = {}
|
|
256
|
+
# Ordered ascending so the latest row wins on the rare re-injection of one task into the
|
|
257
|
+
# same sprint (multiple rows per (task, sprint)); the cross-product filter overfetches
|
|
258
|
+
# at most distinct-tasks × distinct-sprints rows, but only exact pairs are read back.
|
|
259
|
+
rows = (
|
|
260
|
+
SprintScopeChange.objects.filter(
|
|
261
|
+
task_id__in={k[0] for k in keys},
|
|
262
|
+
sprint_id__in={k[1] for k in keys},
|
|
263
|
+
)
|
|
264
|
+
.order_by("added_at")
|
|
265
|
+
.values("task_id", "sprint_id", "status")
|
|
266
|
+
)
|
|
267
|
+
for row in rows:
|
|
268
|
+
status_map[(str(row["task_id"]), str(row["sprint_id"]))] = row["status"]
|
|
269
|
+
|
|
270
|
+
for e in events:
|
|
271
|
+
if e["event_type"] == "entered_sprint":
|
|
272
|
+
# entered_sprint always carries a non-null sprint_id (see _sprint_event).
|
|
273
|
+
e["scope_change_status"] = status_map.get((e["task_id"], e["sprint_id"]))
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _events_from_record(
|
|
277
|
+
rec: Any,
|
|
278
|
+
older: Any,
|
|
279
|
+
wanted: set[str] | frozenset[str],
|
|
280
|
+
user_names: dict[Any, str],
|
|
281
|
+
sprint_names: dict[Any, str],
|
|
282
|
+
hide_cost: bool,
|
|
283
|
+
) -> list[dict[str, Any]]:
|
|
284
|
+
"""Derive the 0..2 board events a single HistoricalTask row contributes."""
|
|
285
|
+
out: list[dict[str, Any]] = []
|
|
286
|
+
actor_id = rec.history_user_id
|
|
287
|
+
base = {
|
|
288
|
+
"actor": user_names.get(actor_id),
|
|
289
|
+
"actor_id": str(actor_id) if actor_id is not None else None,
|
|
290
|
+
"timestamp": rec.history_date,
|
|
291
|
+
"task_id": str(rec.id),
|
|
292
|
+
"task_name": rec.name,
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
# django-simple-history history_type: "+" created, "~" changed, "-" deleted.
|
|
296
|
+
if rec.history_type == "+":
|
|
297
|
+
if "task_created" in wanted:
|
|
298
|
+
out.append(
|
|
299
|
+
{
|
|
300
|
+
**base,
|
|
301
|
+
"id": f"hist:{rec.history_id}",
|
|
302
|
+
"event_type": "task_created",
|
|
303
|
+
"changes": [],
|
|
304
|
+
}
|
|
305
|
+
)
|
|
306
|
+
return out
|
|
307
|
+
if rec.history_type == "-":
|
|
308
|
+
if "task_deleted" in wanted:
|
|
309
|
+
out.append(
|
|
310
|
+
{
|
|
311
|
+
**base,
|
|
312
|
+
"id": f"hist:{rec.history_id}",
|
|
313
|
+
"event_type": "task_deleted",
|
|
314
|
+
"changes": [],
|
|
315
|
+
}
|
|
316
|
+
)
|
|
317
|
+
return out
|
|
318
|
+
|
|
319
|
+
# A "~" (changed) row. With no prior-in-batch we cannot diff, so it is dropped
|
|
320
|
+
# (the documented deep-page edge — a board reader sees no change without a delta).
|
|
321
|
+
if older is None:
|
|
322
|
+
return out
|
|
323
|
+
|
|
324
|
+
# Sprint transition is its own first-class event (held out of the field diff).
|
|
325
|
+
if wanted & _SPRINT_EVENT_TYPES:
|
|
326
|
+
sprint_event = _sprint_event(rec, older, base, sprint_names)
|
|
327
|
+
if sprint_event is not None:
|
|
328
|
+
out.append(sprint_event)
|
|
329
|
+
|
|
330
|
+
if "task_updated" in wanted:
|
|
331
|
+
changes = _field_changes(rec, older, user_names, hide_cost)
|
|
332
|
+
if changes:
|
|
333
|
+
out.append(
|
|
334
|
+
{
|
|
335
|
+
**base,
|
|
336
|
+
"id": f"hist:{rec.history_id}",
|
|
337
|
+
"event_type": "task_updated",
|
|
338
|
+
"changes": changes,
|
|
339
|
+
}
|
|
340
|
+
)
|
|
341
|
+
return out
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _field_changes(
|
|
345
|
+
rec: Any, older: Any, user_names: dict[Any, str], hide_cost: bool
|
|
346
|
+
) -> list[dict[str, Any]]:
|
|
347
|
+
"""Board-allowlist field deltas between two HistoricalTask rows."""
|
|
348
|
+
changes: list[dict[str, Any]] = []
|
|
349
|
+
for field in _BOARD_DIFF_FIELDS:
|
|
350
|
+
if hide_cost and field in _COST_FIELDS:
|
|
351
|
+
continue
|
|
352
|
+
new_val = getattr(rec, field, None)
|
|
353
|
+
old_val = getattr(older, field, None)
|
|
354
|
+
if new_val == old_val:
|
|
355
|
+
continue
|
|
356
|
+
if field == "assignee_id":
|
|
357
|
+
changes.append(
|
|
358
|
+
{
|
|
359
|
+
"field": "assignee",
|
|
360
|
+
"old": user_names.get(old_val) if old_val is not None else None,
|
|
361
|
+
"new": user_names.get(new_val) if new_val is not None else None,
|
|
362
|
+
}
|
|
363
|
+
)
|
|
364
|
+
else:
|
|
365
|
+
changes.append({"field": field, "old": _scalar(old_val), "new": _scalar(new_val)})
|
|
366
|
+
return changes
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _sprint_event(
|
|
370
|
+
rec: Any, older: Any, base: dict[str, Any], sprint_names: dict[Any, str]
|
|
371
|
+
) -> dict[str, Any] | None:
|
|
372
|
+
"""An entered/exited/moved-sprint event from a sprint_id delta, or None."""
|
|
373
|
+
new_sprint = rec.sprint_id
|
|
374
|
+
old_sprint = older.sprint_id
|
|
375
|
+
if new_sprint == old_sprint:
|
|
376
|
+
return None
|
|
377
|
+
if old_sprint is None:
|
|
378
|
+
event_type = "entered_sprint"
|
|
379
|
+
elif new_sprint is None:
|
|
380
|
+
event_type = "exited_sprint"
|
|
381
|
+
else:
|
|
382
|
+
event_type = "moved_sprint"
|
|
383
|
+
return {
|
|
384
|
+
**base,
|
|
385
|
+
"id": f"hist:{rec.history_id}:sprint",
|
|
386
|
+
"event_type": event_type,
|
|
387
|
+
"sprint_id": str(new_sprint) if new_sprint is not None else None,
|
|
388
|
+
# Internal (stripped before serialization) — both endpoints of the sprint
|
|
389
|
+
# transition so ``_filter_by_sprint`` can keep a removal visible in the
|
|
390
|
+
# sprint it LEFT (its old id), not just the one it entered (ADR-0412, #1946).
|
|
391
|
+
"_old_sprint_id": str(old_sprint) if old_sprint is not None else None,
|
|
392
|
+
"_new_sprint_id": str(new_sprint) if new_sprint is not None else None,
|
|
393
|
+
"changes": [
|
|
394
|
+
{
|
|
395
|
+
"field": "sprint",
|
|
396
|
+
"old": sprint_names.get(old_sprint) if old_sprint is not None else None,
|
|
397
|
+
"new": sprint_names.get(new_sprint) if new_sprint is not None else None,
|
|
398
|
+
}
|
|
399
|
+
],
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _comment_events(
|
|
404
|
+
project: Project,
|
|
405
|
+
until: datetime,
|
|
406
|
+
since: datetime | None,
|
|
407
|
+
cap: int,
|
|
408
|
+
) -> list[dict[str, Any]]:
|
|
409
|
+
"""``comment_added`` events from TaskComment creates (bounded batch)."""
|
|
410
|
+
from trueppm_api.apps.projects.models import TaskComment
|
|
411
|
+
|
|
412
|
+
qs = TaskComment.objects.filter(
|
|
413
|
+
task__project_id=project.pk, is_deleted=False, created_at__lt=until
|
|
414
|
+
)
|
|
415
|
+
if since is not None:
|
|
416
|
+
qs = qs.filter(created_at__gte=since)
|
|
417
|
+
rows = list(qs.select_related("author", "task").order_by("-created_at")[:cap])
|
|
418
|
+
return [
|
|
419
|
+
{
|
|
420
|
+
"id": f"comment:{comment.pk}",
|
|
421
|
+
"event_type": "comment_added",
|
|
422
|
+
"actor": comment.author.username if comment.author else None,
|
|
423
|
+
"actor_id": str(comment.author_id) if comment.author_id else None,
|
|
424
|
+
"timestamp": comment.created_at,
|
|
425
|
+
"task_id": str(comment.task_id),
|
|
426
|
+
"task_name": comment.task.name,
|
|
427
|
+
"changes": [],
|
|
428
|
+
}
|
|
429
|
+
for comment in rows
|
|
430
|
+
]
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def _user_name_map(user_ids: set[Any]) -> dict[Any, str]:
|
|
434
|
+
if not user_ids:
|
|
435
|
+
return {}
|
|
436
|
+
user_model = get_user_model()
|
|
437
|
+
return dict(user_model.objects.filter(pk__in=user_ids).values_list("pk", "username"))
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def _sprint_name_map(sprint_ids: set[Any]) -> dict[Any, str]:
|
|
441
|
+
if not sprint_ids:
|
|
442
|
+
return {}
|
|
443
|
+
from trueppm_api.apps.projects.models import Sprint
|
|
444
|
+
|
|
445
|
+
return dict(Sprint.objects.filter(pk__in=sprint_ids).values_list("pk", "name"))
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _serialize_event(event: dict[str, Any]) -> dict[str, Any]:
|
|
449
|
+
"""Stamp the timestamp as an ISO string for the response (kept native for sort).
|
|
450
|
+
|
|
451
|
+
Internal ``_``-prefixed keys (e.g. the sprint-transition endpoints used by the
|
|
452
|
+
``?sprint=`` scope filter) are dropped here — the payload is rendered directly to
|
|
453
|
+
JSON (the response serializer is schema-only), so they must not leak.
|
|
454
|
+
"""
|
|
455
|
+
out = {k: v for k, v in event.items() if not k.startswith("_")}
|
|
456
|
+
ts = out["timestamp"]
|
|
457
|
+
out["timestamp"] = ts.isoformat() if isinstance(ts, datetime) else ts
|
|
458
|
+
out.setdefault("sprint_id", None)
|
|
459
|
+
# Present on every row (null off ``entered_sprint``) so the response shape is uniform —
|
|
460
|
+
# same contract as ``sprint_id`` (ADR-0160 Amendment B3, #1264).
|
|
461
|
+
out.setdefault("scope_change_status", None)
|
|
462
|
+
return out
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Board-level activity feed endpoint (ADR-0160, #325).
|
|
2
|
+
|
|
3
|
+
GET /api/v1/projects/<project_pk>/board/activity?until=&since=&actor=&type=&limit=
|
|
4
|
+
|
|
5
|
+
A read-only, board-scoped, filterable feed of card mutations (status/assignee/points
|
|
6
|
+
changes, sprint entries/exits, comments) aggregated from existing change-history
|
|
7
|
+
sources — see :mod:`board_activity`. Any project member (Viewer+) may read it; the
|
|
8
|
+
keyset cursor is ``until`` (pass the response's ``next_until`` back to page).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from django.utils import timezone
|
|
16
|
+
from django.utils.dateparse import parse_datetime
|
|
17
|
+
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
|
|
18
|
+
from rest_framework import serializers, status
|
|
19
|
+
from rest_framework.generics import get_object_or_404
|
|
20
|
+
from rest_framework.permissions import IsAuthenticated
|
|
21
|
+
from rest_framework.request import Request
|
|
22
|
+
from rest_framework.response import Response
|
|
23
|
+
from rest_framework.views import APIView
|
|
24
|
+
|
|
25
|
+
from trueppm_api.apps.access.permissions import (
|
|
26
|
+
IsProjectMember,
|
|
27
|
+
_membership_role,
|
|
28
|
+
)
|
|
29
|
+
from trueppm_api.apps.projects.board_activity import (
|
|
30
|
+
EVENT_TYPES,
|
|
31
|
+
MAX_LIMIT,
|
|
32
|
+
build_board_activity,
|
|
33
|
+
)
|
|
34
|
+
from trueppm_api.apps.projects.models import Project
|
|
35
|
+
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
# Serializers (response schema for drf-spectacular)
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class BoardActivityChangeSerializer(serializers.Serializer[Any]):
|
|
42
|
+
field = serializers.CharField()
|
|
43
|
+
old = serializers.CharField(allow_null=True)
|
|
44
|
+
new = serializers.CharField(allow_null=True)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class BoardActivityEventSerializer(serializers.Serializer[Any]):
|
|
48
|
+
id = serializers.CharField()
|
|
49
|
+
# CharField (not ChoiceField) deliberately: a ChoiceField over EVENT_TYPES emits a
|
|
50
|
+
# second `event_type` enum component that collides with the existing one and renames
|
|
51
|
+
# the canonical `EventTypeEnum` in the schema (the #859 schema-drift trap). The valid
|
|
52
|
+
# values are documented on the `type` query parameter instead.
|
|
53
|
+
event_type = serializers.CharField()
|
|
54
|
+
actor = serializers.CharField(allow_null=True)
|
|
55
|
+
actor_id = serializers.CharField(allow_null=True)
|
|
56
|
+
timestamp = serializers.DateTimeField()
|
|
57
|
+
task_id = serializers.CharField()
|
|
58
|
+
task_name = serializers.CharField()
|
|
59
|
+
sprint_id = serializers.CharField(allow_null=True)
|
|
60
|
+
# SprintScopeChange accept-gate status (pending/accepted/rejected) on `entered_sprint`
|
|
61
|
+
# events, else null (ADR-0160 Amendment B3, #1264). CharField not ChoiceField for the
|
|
62
|
+
# same #859 enum-collision reason as `event_type` above.
|
|
63
|
+
scope_change_status = serializers.CharField(allow_null=True)
|
|
64
|
+
changes = BoardActivityChangeSerializer(many=True)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class BoardActivityResponseSerializer(serializers.Serializer[Any]):
|
|
68
|
+
results = BoardActivityEventSerializer(many=True)
|
|
69
|
+
next_until = serializers.DateTimeField(allow_null=True)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _parse_dt(raw: str | None, field: str) -> Any:
|
|
73
|
+
"""Parse an ISO datetime query param into an aware datetime, or 400 on garbage."""
|
|
74
|
+
if raw is None:
|
|
75
|
+
return None
|
|
76
|
+
parsed = parse_datetime(raw)
|
|
77
|
+
if parsed is None:
|
|
78
|
+
from rest_framework.exceptions import ValidationError
|
|
79
|
+
|
|
80
|
+
raise ValidationError({field: f"Invalid datetime '{raw}' (expected ISO 8601)."})
|
|
81
|
+
if timezone.is_naive(parsed):
|
|
82
|
+
parsed = timezone.make_aware(parsed, timezone.get_current_timezone())
|
|
83
|
+
return parsed
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class BoardActivityView(APIView):
|
|
87
|
+
"""GET the board's filterable, time-ordered activity feed (Viewer+)."""
|
|
88
|
+
|
|
89
|
+
# IsProjectNotArchived is deliberately omitted: history/activity is a read-only
|
|
90
|
+
# audit surface that must stay accessible after a project is archived. (It was
|
|
91
|
+
# also a no-op here — the permission passes all SAFE_METHODS and this view is
|
|
92
|
+
# GET-only — so listing it only misdocumented the RBAC contract; #1890.)
|
|
93
|
+
permission_classes = [IsAuthenticated, IsProjectMember] # noqa: RUF012
|
|
94
|
+
|
|
95
|
+
@extend_schema(
|
|
96
|
+
summary="Board-level activity feed (filterable, board-scoped)",
|
|
97
|
+
parameters=[
|
|
98
|
+
OpenApiParameter(
|
|
99
|
+
"until",
|
|
100
|
+
str,
|
|
101
|
+
description="Keyset cursor — events older than this ISO datetime (default now).",
|
|
102
|
+
),
|
|
103
|
+
OpenApiParameter(
|
|
104
|
+
"since", str, description="Lower time bound (inclusive ISO datetime)."
|
|
105
|
+
),
|
|
106
|
+
OpenApiParameter("actor", str, description="Filter to one actor (user id)."),
|
|
107
|
+
OpenApiParameter(
|
|
108
|
+
"type",
|
|
109
|
+
str,
|
|
110
|
+
description=f"Comma-separated event types: {', '.join(sorted(EVENT_TYPES))}.",
|
|
111
|
+
),
|
|
112
|
+
OpenApiParameter("limit", int, description=f"Page size (1..{MAX_LIMIT}, default 50)."),
|
|
113
|
+
OpenApiParameter(
|
|
114
|
+
"sprint",
|
|
115
|
+
str,
|
|
116
|
+
description=(
|
|
117
|
+
"Narrow to one sprint's scope (ADR-0412): events whose task is "
|
|
118
|
+
"currently in this sprint, plus entered/exited/moved events that "
|
|
119
|
+
"reference it. Must be a sprint in this project (404 otherwise)."
|
|
120
|
+
),
|
|
121
|
+
),
|
|
122
|
+
],
|
|
123
|
+
responses={200: OpenApiResponse(BoardActivityResponseSerializer)},
|
|
124
|
+
)
|
|
125
|
+
def get(self, request: Request, project_pk: str) -> Response:
|
|
126
|
+
project = get_object_or_404(Project, pk=project_pk, is_deleted=False)
|
|
127
|
+
self.check_object_permissions(request, project)
|
|
128
|
+
|
|
129
|
+
until = _parse_dt(request.query_params.get("until"), "until")
|
|
130
|
+
since = _parse_dt(request.query_params.get("since"), "since")
|
|
131
|
+
# actor is an opaque user-id string (integer PKs); an unknown/garbage value
|
|
132
|
+
# simply matches nothing, which is standard REST for a filter param.
|
|
133
|
+
actor_id = request.query_params.get("actor") or None
|
|
134
|
+
|
|
135
|
+
types_raw = request.query_params.get("type")
|
|
136
|
+
event_types: set[str] | None = None
|
|
137
|
+
if types_raw:
|
|
138
|
+
requested = {t.strip() for t in types_raw.split(",") if t.strip()}
|
|
139
|
+
unknown = requested - EVENT_TYPES
|
|
140
|
+
if unknown:
|
|
141
|
+
return Response(
|
|
142
|
+
{"type": f"Unknown event type(s): {', '.join(sorted(unknown))}."},
|
|
143
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
144
|
+
)
|
|
145
|
+
event_types = requested
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
limit = int(request.query_params.get("limit", 50))
|
|
149
|
+
except (TypeError, ValueError):
|
|
150
|
+
return Response({"limit": "Must be an integer."}, status=status.HTTP_400_BAD_REQUEST)
|
|
151
|
+
|
|
152
|
+
# Sprint scope (ADR-0412, #1946). Validate the sprint belongs to THIS project
|
|
153
|
+
# so a sprint id from another project can never leak that project's activity —
|
|
154
|
+
# the view is already project-scoped and IsProjectMember-gated, so a cross-
|
|
155
|
+
# project or unknown id is a 404 (a malformed non-UUID id is a 400).
|
|
156
|
+
sprint_scope: str | None = None
|
|
157
|
+
sprint_raw = request.query_params.get("sprint") or None
|
|
158
|
+
if sprint_raw is not None:
|
|
159
|
+
from django.core.exceptions import ValidationError as DjangoValidationError
|
|
160
|
+
|
|
161
|
+
from trueppm_api.apps.projects.models import Sprint
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
sprint = Sprint.objects.get(pk=sprint_raw, project_id=project.pk, is_deleted=False)
|
|
165
|
+
except Sprint.DoesNotExist:
|
|
166
|
+
return Response(
|
|
167
|
+
{"sprint": "No such sprint in this project."},
|
|
168
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
169
|
+
)
|
|
170
|
+
except (DjangoValidationError, ValueError):
|
|
171
|
+
return Response(
|
|
172
|
+
{"sprint": "Invalid sprint id."}, status=status.HTTP_400_BAD_REQUEST
|
|
173
|
+
)
|
|
174
|
+
sprint_scope = str(sprint.pk)
|
|
175
|
+
|
|
176
|
+
payload = build_board_activity(
|
|
177
|
+
project,
|
|
178
|
+
until=until,
|
|
179
|
+
since=since,
|
|
180
|
+
actor_id=actor_id,
|
|
181
|
+
event_types=event_types,
|
|
182
|
+
limit=limit,
|
|
183
|
+
role=_membership_role(request, project.pk),
|
|
184
|
+
sprint_id=sprint_scope,
|
|
185
|
+
)
|
|
186
|
+
return Response(payload)
|