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,277 @@
|
|
|
1
|
+
"""Celery tasks for offline Jira import (mirrors msproject.tasks)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import logging
|
|
7
|
+
from collections.abc import Generator
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
from datetime import timedelta
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from celery import shared_task
|
|
13
|
+
|
|
14
|
+
from trueppm_api.core.idempotent import idempotent_task
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
# A Jira import running longer than this is considered orphaned by the drain
|
|
19
|
+
# (slightly longer than the 9-minute soft limit so the task can record its own
|
|
20
|
+
# failure before the drain resets the row).
|
|
21
|
+
_IMPORT_ORPHAN_MINUTES = 15
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class _NoOpTracker:
|
|
25
|
+
"""Fallback when TaskRunTracker is not available."""
|
|
26
|
+
|
|
27
|
+
task_run_id = None
|
|
28
|
+
|
|
29
|
+
def update(self, pct: int, msg: str = "") -> None:
|
|
30
|
+
logger.info("import.jira progress: %d%% -- %s", pct, msg)
|
|
31
|
+
|
|
32
|
+
def set_result(self, result: dict[str, Any]) -> None:
|
|
33
|
+
logger.info("import.jira result: %s", result)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@contextmanager
|
|
37
|
+
def _get_tracker(
|
|
38
|
+
celery_task: Any,
|
|
39
|
+
project_id: str,
|
|
40
|
+
initiated_by_id: int | None,
|
|
41
|
+
) -> Generator[Any, None, None]:
|
|
42
|
+
"""Try to use TaskRunTracker; fall back to no-op if not available."""
|
|
43
|
+
try:
|
|
44
|
+
from trueppm_api.apps.taskruns.tracker import TaskRunTracker
|
|
45
|
+
|
|
46
|
+
with TaskRunTracker(
|
|
47
|
+
celery_task,
|
|
48
|
+
project_id=project_id,
|
|
49
|
+
task_name="import.jira",
|
|
50
|
+
initiated_by_id=initiated_by_id,
|
|
51
|
+
) as tracker:
|
|
52
|
+
yield tracker
|
|
53
|
+
except ImportError:
|
|
54
|
+
yield _NoOpTracker()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@shared_task( # type: ignore[untyped-decorator]
|
|
58
|
+
bind=True,
|
|
59
|
+
name="jira.import_file",
|
|
60
|
+
soft_time_limit=540,
|
|
61
|
+
time_limit=600,
|
|
62
|
+
acks_late=True,
|
|
63
|
+
reject_on_worker_lost=True,
|
|
64
|
+
)
|
|
65
|
+
def import_jira(
|
|
66
|
+
self: Any,
|
|
67
|
+
project_id: str,
|
|
68
|
+
file_content_b64: str,
|
|
69
|
+
filename: str,
|
|
70
|
+
initiated_by_id: int | None = None,
|
|
71
|
+
import_request_id: str | None = None,
|
|
72
|
+
) -> dict[str, Any]:
|
|
73
|
+
"""Parse an uploaded Jira XML export and import it as a CPM-schedulable network.
|
|
74
|
+
|
|
75
|
+
Parses offline (no network), validates the derived dependency graph with the
|
|
76
|
+
shared guard (#1665) **before** persisting anything, then reuses the MS
|
|
77
|
+
Project importer to bulk-create the Task + FS Dependency network and triggers
|
|
78
|
+
a CPM recalculation via the scheduling outbox.
|
|
79
|
+
|
|
80
|
+
A deterministic failure — an unparseable file or a cyclic / infeasible graph
|
|
81
|
+
— marks the outbox row DEAD so the drain stops re-dispatching it forever.
|
|
82
|
+
"""
|
|
83
|
+
from trueppm_api.apps.jiraimport.parser import JiraImportError, parse_jira_xml
|
|
84
|
+
|
|
85
|
+
with _get_tracker(self, project_id, initiated_by_id) as tracker:
|
|
86
|
+
file_content = base64.b64decode(file_content_b64)
|
|
87
|
+
tracker.update(5, f"Parsing {filename}...")
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
project_data = parse_jira_xml(file_content)
|
|
91
|
+
except JiraImportError:
|
|
92
|
+
# Unparseable input is deterministic: retrying the same bytes always
|
|
93
|
+
# fails. Mark the row DEAD (terminal) so the orphan drain stops.
|
|
94
|
+
if import_request_id:
|
|
95
|
+
_mark_import_dead(import_request_id)
|
|
96
|
+
raise
|
|
97
|
+
|
|
98
|
+
# Validate the derived dependency graph BEFORE any write (#1665, ADR-0259).
|
|
99
|
+
# A prospect's messy export (cyclic links) must never persist an
|
|
100
|
+
# infeasible network that then crashes the CPM / what-if engine live.
|
|
101
|
+
# Validate in uid space — a relabeling of the eventual PKs — so nothing
|
|
102
|
+
# is created if the graph is bad. The parser already quarantined
|
|
103
|
+
# self-loops and dangling edges; a genuine cycle is rejected here.
|
|
104
|
+
from trueppm_api.apps.scheduling.graph_guard import (
|
|
105
|
+
InfeasibleGraphError,
|
|
106
|
+
validate_task_graph,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
edges = [
|
|
110
|
+
(str(link.predecessor_uid), str(task.uid))
|
|
111
|
+
for task in project_data.tasks
|
|
112
|
+
for link in task.predecessor_links
|
|
113
|
+
]
|
|
114
|
+
tracker.update(20, "Validating dependency graph...")
|
|
115
|
+
try:
|
|
116
|
+
validate_task_graph(edges)
|
|
117
|
+
except InfeasibleGraphError:
|
|
118
|
+
# Deterministic: the same export always produces the same cycle.
|
|
119
|
+
if import_request_id:
|
|
120
|
+
_mark_import_dead(import_request_id)
|
|
121
|
+
raise
|
|
122
|
+
|
|
123
|
+
from django.db import transaction
|
|
124
|
+
|
|
125
|
+
from trueppm_api.apps.msproject.importer import (
|
|
126
|
+
broadcast_import_project_record_change,
|
|
127
|
+
import_project,
|
|
128
|
+
)
|
|
129
|
+
from trueppm_api.apps.projects.models import TaskSource
|
|
130
|
+
|
|
131
|
+
# Make the additive Jira import idempotent under re-dispatch (#1673).
|
|
132
|
+
# import_project defaults to wipe_existing=False, so a second delivery
|
|
133
|
+
# would bulk-create the whole network again. The outbox row is the
|
|
134
|
+
# idempotency token: claim it (DISPATCHED -> DONE) and run the
|
|
135
|
+
# bulk_create in ONE transaction. A duplicate delivery — a drain
|
|
136
|
+
# re-dispatch after a worker-death window, or an acks_late /
|
|
137
|
+
# reject_on_worker_lost redelivery of an already completed message —
|
|
138
|
+
# finds no DISPATCHED row to claim and skips the import. The claim's row
|
|
139
|
+
# lock serializes any concurrent delivery; because claim and import share
|
|
140
|
+
# the transaction, a mid-import failure rolls the claim back too,
|
|
141
|
+
# returning the row to DISPATCHED for a clean retry. Mirrors the MS
|
|
142
|
+
# Project import-into-existing fix.
|
|
143
|
+
with transaction.atomic():
|
|
144
|
+
if import_request_id and not _claim_import(import_request_id):
|
|
145
|
+
logger.info(
|
|
146
|
+
"import.jira: request %s already completed — skipping duplicate delivery",
|
|
147
|
+
import_request_id,
|
|
148
|
+
)
|
|
149
|
+
return {"skipped": True, "tasks_created": 0}
|
|
150
|
+
# Explicit source_kind: this path shares import_project with the MS
|
|
151
|
+
# Project importer, whose default would mislabel Jira rows (ADR-0786).
|
|
152
|
+
summary = import_project(
|
|
153
|
+
project_id,
|
|
154
|
+
project_data,
|
|
155
|
+
tracker=tracker,
|
|
156
|
+
source_kind=TaskSource.JIRA_IMPORT,
|
|
157
|
+
source_id=import_request_id or None,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
tracker.set_result(summary)
|
|
161
|
+
|
|
162
|
+
if summary["tasks_created"] > 0:
|
|
163
|
+
# Trigger CPM via the outbox (survives a broker outage at this point)
|
|
164
|
+
# and emit tasks_restructured so live clients render the imported tree
|
|
165
|
+
# immediately, ahead of the async recalc's cpm_complete (mirrors the
|
|
166
|
+
# MS Project importer, #1359). Both now run just after the import's
|
|
167
|
+
# atomic() has committed (no ambient transaction remains → on_commit
|
|
168
|
+
# fires immediately), so peers never see the event for a rolled-back
|
|
169
|
+
# import.
|
|
170
|
+
from trueppm_api.apps.scheduling.services import enqueue_recalculate
|
|
171
|
+
from trueppm_api.apps.sync.broadcast import broadcast_board_event
|
|
172
|
+
|
|
173
|
+
enqueue_recalculate(project_id)
|
|
174
|
+
transaction.on_commit(
|
|
175
|
+
lambda: broadcast_board_event(project_id, "tasks_restructured", {})
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# See the CSV importer's note: a start shift is not a task-tree change,
|
|
179
|
+
# so tasks_restructured does not cover it.
|
|
180
|
+
broadcast_import_project_record_change(project_id, summary)
|
|
181
|
+
|
|
182
|
+
return summary
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _claim_import(import_request_id: str) -> bool:
|
|
186
|
+
"""Atomically claim a DISPATCHED JiraImportRequest, flipping it to DONE.
|
|
187
|
+
|
|
188
|
+
The conditional ``UPDATE ... WHERE status=DISPATCHED`` is the exactly-once
|
|
189
|
+
idempotency token for the import (#1673): run inside the same transaction as
|
|
190
|
+
the bulk_create, the row transitions to DONE atomically with the imported
|
|
191
|
+
network. Returns ``True`` when this delivery won the claim (import), ``False``
|
|
192
|
+
when the row was already terminal — a duplicate delivery whose import must be
|
|
193
|
+
skipped so the additive path does not bulk-create the network twice. Clears
|
|
194
|
+
the payload (only needed for pre-terminal retry).
|
|
195
|
+
"""
|
|
196
|
+
from trueppm_api.apps.jiraimport.models import JiraImportRequest, JiraImportStatus
|
|
197
|
+
|
|
198
|
+
claimed = JiraImportRequest.objects.filter(
|
|
199
|
+
id=import_request_id, status=JiraImportStatus.DISPATCHED
|
|
200
|
+
).update(status=JiraImportStatus.DONE, file_content_b64="")
|
|
201
|
+
return bool(claimed)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _mark_import_dead(import_request_id: str) -> None:
|
|
205
|
+
"""Flip the row to DEAD after a terminal failure and clear the payload.
|
|
206
|
+
|
|
207
|
+
DEAD is excluded from orphan-drain recovery, so a deterministically bad file
|
|
208
|
+
(unparseable, or a cyclic graph) is not re-dispatched forever.
|
|
209
|
+
"""
|
|
210
|
+
from trueppm_api.apps.jiraimport.models import JiraImportRequest, JiraImportStatus
|
|
211
|
+
|
|
212
|
+
JiraImportRequest.objects.filter(id=import_request_id).exclude(
|
|
213
|
+
status=JiraImportStatus.DONE
|
|
214
|
+
).update(status=JiraImportStatus.DEAD, file_content_b64="")
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@idempotent_task(
|
|
218
|
+
lock_key_template="drain_jira_import_queue",
|
|
219
|
+
lock_ttl=60,
|
|
220
|
+
on_contention="skip",
|
|
221
|
+
soft_time_limit=25,
|
|
222
|
+
time_limit=30,
|
|
223
|
+
acks_late=True,
|
|
224
|
+
reject_on_worker_lost=True,
|
|
225
|
+
name="jira.drain_import_queue",
|
|
226
|
+
)
|
|
227
|
+
def drain_jira_import_queue(self: object) -> None:
|
|
228
|
+
"""Dispatch pending JiraImportRequest rows; recover orphaned dispatched rows.
|
|
229
|
+
|
|
230
|
+
Runs every 30 seconds via Celery Beat. Recovers dispatched rows older than
|
|
231
|
+
``_IMPORT_ORPHAN_MINUTES`` (worker died mid-import).
|
|
232
|
+
"""
|
|
233
|
+
_do_jira_import_drain()
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _do_jira_import_drain() -> None:
|
|
237
|
+
"""Business logic for drain_jira_import_queue — extracted for testability."""
|
|
238
|
+
from django.utils import timezone
|
|
239
|
+
|
|
240
|
+
from trueppm_api.apps.jiraimport.models import JiraImportRequest, JiraImportStatus
|
|
241
|
+
|
|
242
|
+
now = timezone.now()
|
|
243
|
+
orphan_cutoff = now - timedelta(minutes=_IMPORT_ORPHAN_MINUTES)
|
|
244
|
+
|
|
245
|
+
recovered = JiraImportRequest.objects.filter(
|
|
246
|
+
status=JiraImportStatus.DISPATCHED,
|
|
247
|
+
dispatched_at__lt=orphan_cutoff,
|
|
248
|
+
).update(status=JiraImportStatus.PENDING, celery_task_id="")
|
|
249
|
+
if recovered:
|
|
250
|
+
logger.warning("drain_jira_import_queue: recovered %d orphaned row(s)", recovered)
|
|
251
|
+
|
|
252
|
+
pending = list(JiraImportRequest.objects.filter(status=JiraImportStatus.PENDING))
|
|
253
|
+
dispatched = 0
|
|
254
|
+
for req in pending:
|
|
255
|
+
try:
|
|
256
|
+
result = import_jira.delay(
|
|
257
|
+
project_id=str(req.project_id),
|
|
258
|
+
file_content_b64=req.file_content_b64,
|
|
259
|
+
filename=req.filename,
|
|
260
|
+
initiated_by_id=req.initiated_by_id,
|
|
261
|
+
import_request_id=str(req.id),
|
|
262
|
+
)
|
|
263
|
+
except Exception:
|
|
264
|
+
logger.warning(
|
|
265
|
+
"drain_jira_import_queue: broker unavailable — JiraImportRequest %s stays pending",
|
|
266
|
+
req.id,
|
|
267
|
+
)
|
|
268
|
+
continue
|
|
269
|
+
JiraImportRequest.objects.filter(id=req.id, status=JiraImportStatus.PENDING).update(
|
|
270
|
+
status=JiraImportStatus.DISPATCHED,
|
|
271
|
+
celery_task_id=result.id,
|
|
272
|
+
dispatched_at=now,
|
|
273
|
+
)
|
|
274
|
+
dispatched += 1
|
|
275
|
+
|
|
276
|
+
if dispatched or recovered:
|
|
277
|
+
logger.info("drain_jira_import_queue: dispatched=%d recovered=%d", dispatched, recovered)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""URL routing for offline Jira import."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from django.urls import path
|
|
6
|
+
|
|
7
|
+
from trueppm_api.apps.jiraimport.views import JiraImportView
|
|
8
|
+
|
|
9
|
+
urlpatterns = [
|
|
10
|
+
path(
|
|
11
|
+
"projects/<project_pk>/import/jira/",
|
|
12
|
+
JiraImportView.as_view(),
|
|
13
|
+
name="project-import-jira",
|
|
14
|
+
),
|
|
15
|
+
]
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""REST API view for offline Jira XML import."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
from django.conf import settings
|
|
11
|
+
from django.db import transaction
|
|
12
|
+
from drf_spectacular.types import OpenApiTypes
|
|
13
|
+
from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer
|
|
14
|
+
from rest_framework import serializers, status
|
|
15
|
+
from rest_framework.exceptions import PermissionDenied
|
|
16
|
+
from rest_framework.parsers import MultiPartParser
|
|
17
|
+
from rest_framework.permissions import IsAuthenticated
|
|
18
|
+
from rest_framework.request import Request
|
|
19
|
+
from rest_framework.response import Response
|
|
20
|
+
from rest_framework.views import APIView
|
|
21
|
+
|
|
22
|
+
from trueppm_api.apps.access.models import ProjectMembership, Role
|
|
23
|
+
from trueppm_api.apps.access.permissions import IsProjectAdmin, IsProjectNotArchived
|
|
24
|
+
from trueppm_api.apps.idempotency.mixins import IdempotencyMixin
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
# Only XML is accepted: it is the sole Jira export that carries <issuelinks>
|
|
29
|
+
# (CSV does not), and links are what make the network CPM-computable (ADR-0259).
|
|
30
|
+
_ALLOWED_EXTENSIONS = {"xml"}
|
|
31
|
+
# Allow-list (mirrors the hardened MS Project sanitizer, #816): substitute
|
|
32
|
+
# anything outside a conservative safe set so an attacker-controlled
|
|
33
|
+
# UploadedFile.name can never carry an HTML/Content-Disposition-injection
|
|
34
|
+
# payload into a future provenance/list surface. os.path.basename below blocks
|
|
35
|
+
# path traversal.
|
|
36
|
+
_FILENAME_ALLOWED = re.compile(r"[^A-Za-z0-9._\- ()]")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _sanitize_filename(raw: str) -> str:
|
|
40
|
+
name = os.path.basename(raw or "")
|
|
41
|
+
name = _FILENAME_ALLOWED.sub("_", name)
|
|
42
|
+
name = re.sub(r"\s+", " ", name).strip()
|
|
43
|
+
return name[:255] or "upload.xml"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _require_project_admin(user: object, project_pk: str) -> None:
|
|
47
|
+
"""Authoritative in-body Admin check (defense-in-depth behind IsProjectAdmin)."""
|
|
48
|
+
role = (
|
|
49
|
+
ProjectMembership.objects.filter(
|
|
50
|
+
project_id=project_pk,
|
|
51
|
+
user=user, # type: ignore[misc]
|
|
52
|
+
is_deleted=False,
|
|
53
|
+
)
|
|
54
|
+
.values_list("role", flat=True)
|
|
55
|
+
.first()
|
|
56
|
+
)
|
|
57
|
+
if role is None or role < Role.ADMIN:
|
|
58
|
+
raise PermissionDenied("You need at least Project Admin role to import.")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _read_validated_upload(request: Request) -> tuple[str, str] | Response:
|
|
62
|
+
"""Read the multipart ``file`` field; validate extension + size.
|
|
63
|
+
|
|
64
|
+
Returns ``(filename, file_content_b64)`` or a 400 ``Response``.
|
|
65
|
+
"""
|
|
66
|
+
uploaded_file = request.FILES.get("file")
|
|
67
|
+
if not uploaded_file:
|
|
68
|
+
return Response(
|
|
69
|
+
{"detail": "No file provided. Send a multipart form with a 'file' field."},
|
|
70
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
filename = _sanitize_filename(uploaded_file.name or "")
|
|
74
|
+
ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else ""
|
|
75
|
+
if ext not in _ALLOWED_EXTENSIONS:
|
|
76
|
+
return Response(
|
|
77
|
+
{"detail": f"Unsupported file type: .{ext}. Export your Jira issues as XML."},
|
|
78
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
max_bytes = settings.JIRA_IMPORT_MAX_UPLOAD_MB * 1024 * 1024
|
|
82
|
+
if uploaded_file.size and uploaded_file.size > max_bytes:
|
|
83
|
+
return Response(
|
|
84
|
+
{
|
|
85
|
+
"detail": (
|
|
86
|
+
f"File too large ({uploaded_file.size} bytes). "
|
|
87
|
+
f"Maximum: {settings.JIRA_IMPORT_MAX_UPLOAD_MB} MB."
|
|
88
|
+
)
|
|
89
|
+
},
|
|
90
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
file_content_b64 = base64.b64encode(uploaded_file.read()).decode("ascii")
|
|
94
|
+
return filename, file_content_b64
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class JiraImportView(IdempotencyMixin, APIView):
|
|
98
|
+
"""Upload and import a Jira XML export into an existing project.
|
|
99
|
+
|
|
100
|
+
Requires project Admin role. The import runs asynchronously via Celery; the
|
|
101
|
+
202 response carries the ``import_request_id`` for progress tracking.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
# Multipart upload, deduped at the table level via JiraImportRequest.
|
|
105
|
+
idempotency_exempt = True
|
|
106
|
+
# IsProjectAdmin gates at the DRF layer (OpenAPI-visible, enforced before the
|
|
107
|
+
# body); the in-body _require_project_admin below stays authoritative.
|
|
108
|
+
permission_classes = [IsAuthenticated, IsProjectAdmin, IsProjectNotArchived]
|
|
109
|
+
parser_classes = [MultiPartParser]
|
|
110
|
+
|
|
111
|
+
@extend_schema(
|
|
112
|
+
summary="Import a Jira XML export into an existing project",
|
|
113
|
+
request=OpenApiTypes.BINARY,
|
|
114
|
+
responses={
|
|
115
|
+
202: inline_serializer(
|
|
116
|
+
name="JiraImportQueuedResponse",
|
|
117
|
+
fields={
|
|
118
|
+
"detail": serializers.CharField(),
|
|
119
|
+
"import_request_id": serializers.UUIDField(),
|
|
120
|
+
},
|
|
121
|
+
),
|
|
122
|
+
400: OpenApiResponse(description="Missing or invalid upload (wrong type/too large)."),
|
|
123
|
+
403: OpenApiResponse(description="Caller lacks the Admin role on the project."),
|
|
124
|
+
},
|
|
125
|
+
)
|
|
126
|
+
def post(self, request: Request, project_pk: str) -> Response:
|
|
127
|
+
_require_project_admin(request.user, project_pk)
|
|
128
|
+
|
|
129
|
+
validated = _read_validated_upload(request)
|
|
130
|
+
if isinstance(validated, Response):
|
|
131
|
+
return validated
|
|
132
|
+
filename, file_content_b64 = validated
|
|
133
|
+
|
|
134
|
+
from trueppm_api.apps.jiraimport.models import JiraImportRequest
|
|
135
|
+
from trueppm_api.apps.jiraimport.services import enqueue_jira_import
|
|
136
|
+
|
|
137
|
+
# Commit the outbox row before any dispatch attempt so a broker outage
|
|
138
|
+
# cannot lose the import — the row stays PENDING and the drain retries.
|
|
139
|
+
with transaction.atomic():
|
|
140
|
+
req = JiraImportRequest.objects.create(
|
|
141
|
+
project_id=project_pk,
|
|
142
|
+
filename=filename,
|
|
143
|
+
file_content_b64=file_content_b64,
|
|
144
|
+
initiated_by_id=request.user.pk,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
req_id = str(req.pk)
|
|
148
|
+
transaction.on_commit(lambda: enqueue_jira_import(req_id))
|
|
149
|
+
|
|
150
|
+
return Response(
|
|
151
|
+
{"detail": "Import queued.", "import_request_id": req_id},
|
|
152
|
+
status=status.HTTP_202_ACCEPTED,
|
|
153
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Django app config for MS Project import/export."""
|
|
2
|
+
|
|
3
|
+
from django.apps import AppConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MsProjectConfig(AppConfig):
|
|
7
|
+
"""MS Project import/export app."""
|
|
8
|
+
|
|
9
|
+
name = "trueppm_api.apps.msproject"
|
|
10
|
+
verbose_name = "MS Project Import/Export"
|
|
11
|
+
default_auto_field = "django.db.models.BigAutoField"
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Data structures for MS Project import/export interchange."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
|
|
7
|
+
#: MSPDI ``<ConstraintType>`` codes the importer can express, mapped onto
|
|
8
|
+
#: ``Task.planned_start`` — which *is* a start-no-earlier-than floor applied by
|
|
9
|
+
#: the CPM forward pass (#2891).
|
|
10
|
+
#:
|
|
11
|
+
#: - **4, Start No Earlier Than** — exactly ``planned_start``'s semantics. Carried
|
|
12
|
+
#: across losslessly.
|
|
13
|
+
#: - **2, Must Start On** — floors the start on the same date, but TruePPM has no
|
|
14
|
+
#: way to enforce the "and no later" half. Carried across as the floor *and*
|
|
15
|
+
#: warned, because a half-enforced commitment the operator believes is whole is
|
|
16
|
+
#: worse than one they know is advisory.
|
|
17
|
+
#:
|
|
18
|
+
#: Code 0 (As Soon As Possible) is TruePPM's own default and therefore loses
|
|
19
|
+
#: nothing when dropped. Every remaining code (1 ALAP, 3 MFO, 5 SNLT, 6 FNET,
|
|
20
|
+
#: 7 FNLT) has no model home at all and is reported in the import warnings.
|
|
21
|
+
#:
|
|
22
|
+
#: Lives here rather than in ``parser`` because it is a claim about the
|
|
23
|
+
#: *interchange contract* — what a ``TaskData.constraint_type`` value causes the
|
|
24
|
+
#: importer to do — which every adapter that fills the field needs to know.
|
|
25
|
+
CONSTRAINT_TYPES_APPLIED_AS_SNET: frozenset[int] = frozenset({2, 4})
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class TaskData:
|
|
30
|
+
"""Parsed task from an MS Project file."""
|
|
31
|
+
|
|
32
|
+
uid: int
|
|
33
|
+
name: str
|
|
34
|
+
duration_days: int = 1
|
|
35
|
+
outline_number: str = ""
|
|
36
|
+
outline_level: int = 0
|
|
37
|
+
is_milestone: bool = False
|
|
38
|
+
percent_complete: float = 0.0
|
|
39
|
+
notes: str = ""
|
|
40
|
+
start: str | None = None # ISO date string (YYYY-MM-DD)
|
|
41
|
+
# Canonical TaskStatus *value* string (e.g. "complete"), or None when the
|
|
42
|
+
# source did not supply an explicit status. The Jira parser maps the issue's
|
|
43
|
+
# status name onto this field; the MS Project importer derives it from the
|
|
44
|
+
# clamped percent-complete when None (#1768). Keeping the interchange value a
|
|
45
|
+
# plain string keeps this dataclass free of a Django-model import.
|
|
46
|
+
status: str | None = None
|
|
47
|
+
# Task-level <CalendarUID> (#1769). TruePPM has no per-task calendars, so
|
|
48
|
+
# the importer only uses this to warn when a task references a calendar
|
|
49
|
+
# other than the project calendar. None covers both "element absent" and
|
|
50
|
+
# the MSPDI sentinel -1 ("no task calendar").
|
|
51
|
+
calendar_uid: int | None = None
|
|
52
|
+
# Three-point / PERT estimate fields (#798, ADR-0093). Working days,
|
|
53
|
+
# nullable, all-or-none: the importer sets all three to None unless the
|
|
54
|
+
# source file supplied all three for a leaf (non-summary, non-milestone)
|
|
55
|
+
# task. The fields map to Task.optimistic_duration / most_likely_duration /
|
|
56
|
+
# pessimistic_duration (IntegerField, working days) on the model.
|
|
57
|
+
optimistic_duration_days: int | None = None
|
|
58
|
+
most_likely_duration_days: int | None = None
|
|
59
|
+
pessimistic_duration_days: int | None = None
|
|
60
|
+
# MSPDI <ConstraintType> code, 0-7 (#2891). This is the mechanism a PM uses
|
|
61
|
+
# to *pin a committed date*, so dropping it silently turned a defensible
|
|
62
|
+
# commitment into a CPM-derived guess. TruePPM models exactly one constraint
|
|
63
|
+
# — the start-no-earlier-than floor `Task.planned_start` — so only the two
|
|
64
|
+
# codes that floor a start (4 SNET, 2 MSO) reach the model; the parser warns
|
|
65
|
+
# for every other code rather than pretending it was carried over. See
|
|
66
|
+
# `MSPDI_CONSTRAINT_NAMES` / `CONSTRAINT_TYPES_APPLIED_AS_SNET` in parser.py.
|
|
67
|
+
constraint_type: int | None = None
|
|
68
|
+
# <ConstraintDate> as an ISO date string, or None. Only meaningful alongside
|
|
69
|
+
# a constraint_type that carries a date (codes 2-7).
|
|
70
|
+
constraint_date: str | None = None
|
|
71
|
+
# <ActualStart> / <ActualFinish> as ISO date strings (#2891). These map
|
|
72
|
+
# one-to-one onto Task.actual_start / Task.actual_finish, so unlike the
|
|
73
|
+
# constraint family they are carried across exactly.
|
|
74
|
+
actual_start: str | None = None
|
|
75
|
+
actual_finish: str | None = None
|
|
76
|
+
predecessor_links: list[PredecessorLinkData] = field(default_factory=list)
|
|
77
|
+
resource_assignments: list[AssignmentData] = field(default_factory=list)
|
|
78
|
+
# Label *names* (not slugs or ids) this task carries, in first-seen order
|
|
79
|
+
# (ADR-0400, #2406). Deliberately names: the parser that fills this cannot
|
|
80
|
+
# see the database, so resolving a name to a catalog entry — matching an
|
|
81
|
+
# existing one case-insensitively, or creating it — belongs to
|
|
82
|
+
# ``import_project``. Populated by the CSV/Excel adapter today; the Jira
|
|
83
|
+
# adapter's components/labels, dropped since #743, are the obvious next
|
|
84
|
+
# producer.
|
|
85
|
+
labels: list[str] = field(default_factory=list)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class PredecessorLinkData:
|
|
90
|
+
"""Parsed predecessor link from an MS Project file."""
|
|
91
|
+
|
|
92
|
+
predecessor_uid: int
|
|
93
|
+
dep_type: str = "FS" # FS, SS, FF, SF
|
|
94
|
+
lag_days: int = 0
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class ResourceData:
|
|
99
|
+
"""Parsed resource from an MS Project file."""
|
|
100
|
+
|
|
101
|
+
uid: int
|
|
102
|
+
name: str
|
|
103
|
+
max_units: float = 1.0
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass
|
|
107
|
+
class AssignmentData:
|
|
108
|
+
"""Parsed resource assignment from an MS Project file."""
|
|
109
|
+
|
|
110
|
+
task_uid: int
|
|
111
|
+
resource_uid: int
|
|
112
|
+
units: float = 1.0
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass
|
|
116
|
+
class CalendarExceptionData:
|
|
117
|
+
"""Parsed non-working exception (holiday, shutdown) from a calendar (#1769)."""
|
|
118
|
+
|
|
119
|
+
start: str # ISO date string (YYYY-MM-DD)
|
|
120
|
+
end: str # ISO date string (YYYY-MM-DD), inclusive
|
|
121
|
+
name: str = ""
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass
|
|
125
|
+
class CalendarData:
|
|
126
|
+
"""Parsed base calendar from an MS Project file (#1769).
|
|
127
|
+
|
|
128
|
+
``working_days`` uses the TruePPM ``Calendar.working_days`` bitmask
|
|
129
|
+
(Mon=1, Tue=2, Wed=4, Thu=8, Fri=16, Sat=32, Sun=64) — the parser converts
|
|
130
|
+
from MSPDI ``DayType`` (1=Sunday … 7=Saturday). Only base calendars are
|
|
131
|
+
parsed; resource calendars (``IsBaseCalendar=0``) have no TruePPM home.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
uid: int
|
|
135
|
+
name: str
|
|
136
|
+
working_days: int = 31 # Mon–Fri
|
|
137
|
+
hours_per_day: float = 8.0
|
|
138
|
+
exceptions: list[CalendarExceptionData] = field(default_factory=list)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@dataclass
|
|
142
|
+
class ProjectData:
|
|
143
|
+
"""Complete parsed project data from an MS Project file."""
|
|
144
|
+
|
|
145
|
+
name: str = ""
|
|
146
|
+
start_date: str | None = None # ISO date string
|
|
147
|
+
# Project-level <CalendarUID> — which parsed calendar the plan is scheduled
|
|
148
|
+
# on (#1769). None when the header omits it.
|
|
149
|
+
calendar_uid: int | None = None
|
|
150
|
+
calendars: list[CalendarData] = field(default_factory=list)
|
|
151
|
+
tasks: list[TaskData] = field(default_factory=list)
|
|
152
|
+
resources: list[ResourceData] = field(default_factory=list)
|
|
153
|
+
warnings: list[str] = field(default_factory=list)
|