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,8 @@
|
|
|
1
|
+
"""Offline Jira import — issues to a CPM-schedulable network (#1664, ADR-0259).
|
|
2
|
+
|
|
3
|
+
Parses a Jira Server / Data Center XML export (the ``Export → XML`` from an
|
|
4
|
+
issue-navigator / filter view) into a project-scoped Task + Dependency network
|
|
5
|
+
the CPM / what-if engine can compute on. Deliberately offline (a parsed file
|
|
6
|
+
upload, exactly how MS Project import works) so it sidesteps SSRF / host-allowlist
|
|
7
|
+
/ OAuth entirely — the minimal path to getting a prospect's real data computable.
|
|
8
|
+
"""
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Django app config for offline Jira import."""
|
|
2
|
+
|
|
3
|
+
from django.apps import AppConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class JiraImportConfig(AppConfig):
|
|
7
|
+
"""Offline Jira XML import app."""
|
|
8
|
+
|
|
9
|
+
name = "trueppm_api.apps.jiraimport"
|
|
10
|
+
verbose_name = "Jira Import"
|
|
11
|
+
default_auto_field = "django.db.models.BigAutoField"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Generated by Django 5.2.15 on 2026-07-06 16:36
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
|
|
5
|
+
import django.db.models.deletion
|
|
6
|
+
from django.conf import settings
|
|
7
|
+
from django.db import migrations, models
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Migration(migrations.Migration):
|
|
11
|
+
initial = True
|
|
12
|
+
|
|
13
|
+
dependencies = [
|
|
14
|
+
("projects", "0111_projectcalendarlayer"),
|
|
15
|
+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
operations = [
|
|
19
|
+
migrations.CreateModel(
|
|
20
|
+
name="JiraImportRequest",
|
|
21
|
+
fields=[
|
|
22
|
+
(
|
|
23
|
+
"id",
|
|
24
|
+
models.UUIDField(
|
|
25
|
+
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
|
26
|
+
),
|
|
27
|
+
),
|
|
28
|
+
(
|
|
29
|
+
"status",
|
|
30
|
+
models.CharField(
|
|
31
|
+
choices=[
|
|
32
|
+
("pending", "Pending"),
|
|
33
|
+
("dispatched", "Dispatched"),
|
|
34
|
+
("done", "Done"),
|
|
35
|
+
("dead", "Dead"),
|
|
36
|
+
],
|
|
37
|
+
default="pending",
|
|
38
|
+
max_length=16,
|
|
39
|
+
),
|
|
40
|
+
),
|
|
41
|
+
("filename", models.CharField(max_length=255)),
|
|
42
|
+
("file_content_b64", models.TextField()),
|
|
43
|
+
("requested_at", models.DateTimeField(auto_now_add=True)),
|
|
44
|
+
("dispatched_at", models.DateTimeField(blank=True, null=True)),
|
|
45
|
+
("celery_task_id", models.CharField(blank=True, default="", max_length=255)),
|
|
46
|
+
(
|
|
47
|
+
"initiated_by",
|
|
48
|
+
models.ForeignKey(
|
|
49
|
+
blank=True,
|
|
50
|
+
null=True,
|
|
51
|
+
on_delete=django.db.models.deletion.SET_NULL,
|
|
52
|
+
related_name="+",
|
|
53
|
+
to=settings.AUTH_USER_MODEL,
|
|
54
|
+
),
|
|
55
|
+
),
|
|
56
|
+
(
|
|
57
|
+
"project",
|
|
58
|
+
models.ForeignKey(
|
|
59
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
60
|
+
related_name="jira_import_requests",
|
|
61
|
+
to="projects.project",
|
|
62
|
+
),
|
|
63
|
+
),
|
|
64
|
+
],
|
|
65
|
+
options={
|
|
66
|
+
"ordering": ["requested_at"],
|
|
67
|
+
"indexes": [
|
|
68
|
+
models.Index(fields=["status", "requested_at"], name="jira_import_status_idx")
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
),
|
|
72
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Outbox model for offline Jira import (mirrors msproject.ImportRequest).
|
|
2
|
+
|
|
3
|
+
A dedicated outbox — rather than a ``source`` discriminator on the MS Project
|
|
4
|
+
``ImportRequest`` — keeps the two drains independent (ADR-0259): the MSP drain
|
|
5
|
+
does not have to branch on file type, and the Jira import surface can evolve
|
|
6
|
+
without touching the MSP path.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import uuid
|
|
12
|
+
|
|
13
|
+
from django.conf import settings
|
|
14
|
+
from django.db import models
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class JiraImportStatus(models.TextChoices):
|
|
18
|
+
"""Lifecycle of a Jira import outbox row."""
|
|
19
|
+
|
|
20
|
+
PENDING = "pending", "Pending"
|
|
21
|
+
DISPATCHED = "dispatched", "Dispatched"
|
|
22
|
+
DONE = "done", "Done"
|
|
23
|
+
DEAD = "dead", "Dead"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class JiraImportRequest(models.Model):
|
|
27
|
+
"""A durable record of an uploaded Jira XML export awaiting / running import.
|
|
28
|
+
|
|
29
|
+
The file content is committed to the row before any Celery dispatch is
|
|
30
|
+
attempted, so a broker outage at upload time cannot lose the import — the row
|
|
31
|
+
stays PENDING and ``drain_jira_import_queue`` picks it up within 30 seconds.
|
|
32
|
+
The ~base64 payload is cleared once the row reaches a terminal state
|
|
33
|
+
(DONE / DEAD) since it is only needed for pre-terminal retry.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
37
|
+
project = models.ForeignKey(
|
|
38
|
+
"projects.Project",
|
|
39
|
+
on_delete=models.CASCADE,
|
|
40
|
+
related_name="jira_import_requests",
|
|
41
|
+
)
|
|
42
|
+
status = models.CharField(
|
|
43
|
+
max_length=16,
|
|
44
|
+
choices=JiraImportStatus.choices,
|
|
45
|
+
default=JiraImportStatus.PENDING,
|
|
46
|
+
)
|
|
47
|
+
filename = models.CharField(max_length=255)
|
|
48
|
+
file_content_b64 = models.TextField()
|
|
49
|
+
initiated_by = models.ForeignKey(
|
|
50
|
+
settings.AUTH_USER_MODEL,
|
|
51
|
+
on_delete=models.SET_NULL,
|
|
52
|
+
null=True,
|
|
53
|
+
blank=True,
|
|
54
|
+
related_name="+",
|
|
55
|
+
)
|
|
56
|
+
requested_at = models.DateTimeField(auto_now_add=True)
|
|
57
|
+
dispatched_at = models.DateTimeField(null=True, blank=True)
|
|
58
|
+
celery_task_id = models.CharField(max_length=255, blank=True, default="")
|
|
59
|
+
|
|
60
|
+
class Meta:
|
|
61
|
+
ordering = ["requested_at"]
|
|
62
|
+
indexes = [
|
|
63
|
+
models.Index(
|
|
64
|
+
fields=["status", "requested_at"],
|
|
65
|
+
name="jira_import_status_idx",
|
|
66
|
+
),
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
def __str__(self) -> str:
|
|
70
|
+
return f"JiraImportRequest({self.project_id}, {self.filename}, {self.status})"
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"""Parse a Jira Server / Data Center XML export into computable ProjectData.
|
|
2
|
+
|
|
3
|
+
Jira's ``Export → XML`` produces an RSS-style document — one ``<item>`` per
|
|
4
|
+
issue carrying ``<key>``, ``<summary>``, ``<timeoriginalestimate seconds=...>``,
|
|
5
|
+
and an ``<issuelinks>`` block. We read the *smallest computable set* (ADR-0259,
|
|
6
|
+
#1664): a task per issue, a duration from the original estimate, and an FS
|
|
7
|
+
dependency per ``Blocks`` link. Everything else (sprints, assignees, subtask
|
|
8
|
+
hierarchy, custom fields, start-date constraints) is deferred — CPM derives
|
|
9
|
+
dates from durations + dependencies, so a critical path needs nothing more.
|
|
10
|
+
|
|
11
|
+
The output is the shared ``msproject`` interchange dataclass (``ProjectData``)
|
|
12
|
+
so the existing, battle-tested ``msproject.importer.import_project`` persists it
|
|
13
|
+
— no duplicate task/dependency creation logic.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import math
|
|
19
|
+
from xml.etree.ElementTree import Element
|
|
20
|
+
|
|
21
|
+
# defusedxml forbids entity expansion and external-entity resolution, closing
|
|
22
|
+
# the XXE / billion-laughs vector on this untrusted-file parse (mirrors the MS
|
|
23
|
+
# Project parser, #771). A prospect's export is untrusted input.
|
|
24
|
+
from defusedxml.ElementTree import fromstring as _safe_fromstring
|
|
25
|
+
from django.conf import settings
|
|
26
|
+
|
|
27
|
+
from trueppm_api.apps.msproject.dataclasses import (
|
|
28
|
+
PredecessorLinkData,
|
|
29
|
+
ProjectData,
|
|
30
|
+
TaskData,
|
|
31
|
+
)
|
|
32
|
+
from trueppm_api.apps.projects.models import TaskStatus
|
|
33
|
+
from trueppm_api.apps.scheduling.units import SECONDS_PER_WORKING_DAY
|
|
34
|
+
|
|
35
|
+
# Jira status *name* (lower-cased) → TaskStatus value (#1768). The reliable
|
|
36
|
+
# signal is Jira's status *category* (To Do / In Progress / Done), but the basic
|
|
37
|
+
# ``Export → XML`` only carries the status name, so we map the common Jira / Jira
|
|
38
|
+
# Agile status names onto TruePPM's board columns — mirroring the status-map
|
|
39
|
+
# concept in ``projects.inbound_sync.DEFAULT_STATUS_MAP``. An unknown or missing
|
|
40
|
+
# status maps to None, and the importer falls back to NOT_STARTED — but a known
|
|
41
|
+
# "Done"/"In Progress" is no longer silently dropped as unstarted future work.
|
|
42
|
+
_STATUS_MAP: dict[str, str] = {
|
|
43
|
+
"backlog": TaskStatus.BACKLOG.value,
|
|
44
|
+
"to do": TaskStatus.NOT_STARTED.value,
|
|
45
|
+
"todo": TaskStatus.NOT_STARTED.value,
|
|
46
|
+
"open": TaskStatus.NOT_STARTED.value,
|
|
47
|
+
"reopened": TaskStatus.NOT_STARTED.value,
|
|
48
|
+
"selected for development": TaskStatus.NOT_STARTED.value,
|
|
49
|
+
"in progress": TaskStatus.IN_PROGRESS.value,
|
|
50
|
+
"in development": TaskStatus.IN_PROGRESS.value,
|
|
51
|
+
"in review": TaskStatus.REVIEW.value,
|
|
52
|
+
"review": TaskStatus.REVIEW.value,
|
|
53
|
+
"in test": TaskStatus.REVIEW.value,
|
|
54
|
+
"done": TaskStatus.COMPLETE.value,
|
|
55
|
+
"closed": TaskStatus.COMPLETE.value,
|
|
56
|
+
"resolved": TaskStatus.COMPLETE.value,
|
|
57
|
+
"complete": TaskStatus.COMPLETE.value,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _map_status(item: Element) -> str | None:
|
|
62
|
+
"""Map a Jira ``<item>``'s ``<status>`` name onto a TaskStatus value.
|
|
63
|
+
|
|
64
|
+
Returns None when the export carries no status or an unrecognized one, in
|
|
65
|
+
which case the importer applies its NOT_STARTED default.
|
|
66
|
+
"""
|
|
67
|
+
raw = (item.findtext("status") or "").strip().lower()
|
|
68
|
+
if not raw:
|
|
69
|
+
return None
|
|
70
|
+
return _STATUS_MAP.get(raw)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ``Task.percent_complete`` is a 0-100 percent, not a 0-1 fraction (#1759,
|
|
74
|
+
# #2889). Named rather than inlined so the scale is stated once and the
|
|
75
|
+
# cross-importer scale test can assert against the same constant.
|
|
76
|
+
_COMPLETE_PERCENT = 100.0
|
|
77
|
+
|
|
78
|
+
# Jira stores the original estimate in seconds; convert on the nominal
|
|
79
|
+
# working-day length from the unit seam (#2290) rather than a bare ``8 * 60 * 60``
|
|
80
|
+
# so a 0.5 canonical-unit change re-homes in one place. A per-instance
|
|
81
|
+
# working-day length remains a later concern.
|
|
82
|
+
_SECONDS_PER_DAY = SECONDS_PER_WORKING_DAY
|
|
83
|
+
|
|
84
|
+
# The Jira issue-link type whose direction encodes a schedule dependency. The
|
|
85
|
+
# outward direction ("blocks") makes this issue the predecessor; the inward
|
|
86
|
+
# direction ("is blocked by") makes it the successor.
|
|
87
|
+
_BLOCKS_LINK_NAME = "blocks"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class JiraImportError(ValueError):
|
|
91
|
+
"""The uploaded file is not a parseable Jira XML export."""
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _seconds_to_days(raw: str | None) -> int:
|
|
95
|
+
"""Convert a Jira ``timeoriginalestimate`` seconds value to whole days.
|
|
96
|
+
|
|
97
|
+
Rounds up so a sub-day estimate still yields a schedulable duration, and
|
|
98
|
+
floors at 1 so a task with no (or an unparseable) estimate is never
|
|
99
|
+
zero-length — a zero-duration task is invisible to CPM float/critical-path
|
|
100
|
+
math, which is the whole reason this importer reads estimates at all.
|
|
101
|
+
"""
|
|
102
|
+
if not raw:
|
|
103
|
+
return 1
|
|
104
|
+
try:
|
|
105
|
+
seconds = int(raw)
|
|
106
|
+
except (TypeError, ValueError):
|
|
107
|
+
return 1
|
|
108
|
+
if seconds <= 0:
|
|
109
|
+
return 1
|
|
110
|
+
return max(1, math.ceil(seconds / _SECONDS_PER_DAY))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _issue_key(item: Element) -> str | None:
|
|
114
|
+
key = (item.findtext("key") or "").strip()
|
|
115
|
+
return key or None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _issue_name(item: Element, key: str) -> str:
|
|
119
|
+
summary = (item.findtext("summary") or "").strip()
|
|
120
|
+
if summary:
|
|
121
|
+
return summary
|
|
122
|
+
# Fall back to the RSS <title> ("[PROJ-1] Summary"), stripping the key
|
|
123
|
+
# prefix Jira prepends; last resort is the bare key so the row is never
|
|
124
|
+
# nameless.
|
|
125
|
+
title = (item.findtext("title") or "").strip()
|
|
126
|
+
prefix = f"[{key}]"
|
|
127
|
+
if title.startswith(prefix):
|
|
128
|
+
title = title[len(prefix) :].strip()
|
|
129
|
+
return title or key
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _blocks_edges(item: Element, key: str) -> list[tuple[str, str]]:
|
|
133
|
+
"""Extract ``(blocker_key, blocked_key)`` edges from one issue's links.
|
|
134
|
+
|
|
135
|
+
Reads both directions of every ``Blocks`` link type: an *outward* link makes
|
|
136
|
+
``key`` the blocker (predecessor); an *inward* link makes ``key`` the blocked
|
|
137
|
+
issue (successor). Both are normalized to the same blocker→blocked shape so a
|
|
138
|
+
later dedupe collapses the two halves of a single link that appears on both
|
|
139
|
+
endpoints' exports.
|
|
140
|
+
"""
|
|
141
|
+
links = item.find("issuelinks")
|
|
142
|
+
if links is None:
|
|
143
|
+
return []
|
|
144
|
+
edges: list[tuple[str, str]] = []
|
|
145
|
+
for link_type in links.findall("issuelinktype"):
|
|
146
|
+
name = (link_type.findtext("name") or "").strip().lower()
|
|
147
|
+
if name != _BLOCKS_LINK_NAME:
|
|
148
|
+
continue
|
|
149
|
+
outward = link_type.find("outwardlinks")
|
|
150
|
+
if outward is not None:
|
|
151
|
+
for other in _linked_keys(outward):
|
|
152
|
+
edges.append((key, other)) # this issue blocks `other`
|
|
153
|
+
inward = link_type.find("inwardlinks")
|
|
154
|
+
if inward is not None:
|
|
155
|
+
for other in _linked_keys(inward):
|
|
156
|
+
edges.append((other, key)) # `other` blocks this issue
|
|
157
|
+
return edges
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _linked_keys(container: Element) -> list[str]:
|
|
161
|
+
keys: list[str] = []
|
|
162
|
+
for link in container.findall("issuelink"):
|
|
163
|
+
other = (link.findtext("issuekey") or "").strip()
|
|
164
|
+
if other:
|
|
165
|
+
keys.append(other)
|
|
166
|
+
return keys
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _parse_channel(content: bytes) -> Element:
|
|
170
|
+
"""Parse the export bytes and return its ``<channel>`` element.
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
JiraImportError: If the bytes are not well-formed XML or carry no
|
|
174
|
+
``<channel>`` element.
|
|
175
|
+
"""
|
|
176
|
+
try:
|
|
177
|
+
# _safe_fromstring is untyped (Any); pin to Element so the resolved
|
|
178
|
+
# channel keeps its type through the return.
|
|
179
|
+
root: Element = _safe_fromstring(content)
|
|
180
|
+
except Exception as exc: # defusedxml raises various parse/entity errors
|
|
181
|
+
raise JiraImportError(f"Not a parseable Jira XML export: {exc}") from exc
|
|
182
|
+
|
|
183
|
+
channel = root.find("channel") if root.tag != "channel" else root
|
|
184
|
+
if channel is None:
|
|
185
|
+
raise JiraImportError("Jira XML export has no <channel> element.")
|
|
186
|
+
return channel
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _enforce_row_cap(items: list[Element]) -> None:
|
|
190
|
+
"""Reject an export whose issue count exceeds the import row cap.
|
|
191
|
+
|
|
192
|
+
The upload SIZE is bounded but the issue count is not (#1721) — a large
|
|
193
|
+
export would build one Task object per issue and bulk-create the lot through
|
|
194
|
+
the shared importer, a worker-memory / transaction-time DoS within the byte
|
|
195
|
+
cap. Reject outright before building anything.
|
|
196
|
+
"""
|
|
197
|
+
max_rows = getattr(settings, "JIRA_IMPORT_MAX_ROWS", 20_000)
|
|
198
|
+
if len(items) > max_rows:
|
|
199
|
+
raise JiraImportError(
|
|
200
|
+
f"Jira export has too many issues ({len(items)}); the import limit is "
|
|
201
|
+
f"{max_rows}. Split the export and import in batches."
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _collect_ordered_issues(
|
|
206
|
+
items: list[Element], warnings: list[str]
|
|
207
|
+
) -> tuple[dict[str, int], list[tuple[str, Element]]]:
|
|
208
|
+
"""Collect keyed issues in document order and assign each a synthetic uid.
|
|
209
|
+
|
|
210
|
+
The ProjectData interchange keys by int uid; Jira keys are strings, so a
|
|
211
|
+
stable ``key_to_uid`` map is built here. Keyless and duplicate-key issues are
|
|
212
|
+
dropped with a warning (keeping the first of a duplicate pair).
|
|
213
|
+
"""
|
|
214
|
+
key_to_uid: dict[str, int] = {}
|
|
215
|
+
ordered: list[tuple[str, Element]] = []
|
|
216
|
+
for item in items:
|
|
217
|
+
key = _issue_key(item)
|
|
218
|
+
if key is None:
|
|
219
|
+
warnings.append("Skipped an issue with no key.")
|
|
220
|
+
continue
|
|
221
|
+
if key in key_to_uid:
|
|
222
|
+
warnings.append(f"Duplicate issue key {key} skipped.")
|
|
223
|
+
continue
|
|
224
|
+
key_to_uid[key] = len(key_to_uid) + 1
|
|
225
|
+
ordered.append((key, item))
|
|
226
|
+
return key_to_uid, ordered
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _collect_blocks_edges(
|
|
230
|
+
ordered: list[tuple[str, Element]], key_to_uid: dict[str, int], warnings: list[str]
|
|
231
|
+
) -> set[tuple[str, str]]:
|
|
232
|
+
"""Collect and dedupe ``(blocker, blocked)`` Blocks edges across all issues.
|
|
233
|
+
|
|
234
|
+
Self-loops and edges touching an issue absent from this export can't be
|
|
235
|
+
scheduled and are dropped with a warning; the returned set is deduped so the
|
|
236
|
+
two halves of a link that appears on both endpoints' exports collapse.
|
|
237
|
+
"""
|
|
238
|
+
edge_set: set[tuple[str, str]] = set()
|
|
239
|
+
for key, item in ordered:
|
|
240
|
+
for blocker, blocked in _blocks_edges(item, key):
|
|
241
|
+
if blocker == blocked:
|
|
242
|
+
warnings.append(f"Self-referential Blocks link on {blocker} skipped.")
|
|
243
|
+
continue
|
|
244
|
+
if blocker not in key_to_uid or blocked not in key_to_uid:
|
|
245
|
+
missing = blocker if blocker not in key_to_uid else blocked
|
|
246
|
+
warnings.append(f"Blocks link to {missing} (not in this export) skipped.")
|
|
247
|
+
continue
|
|
248
|
+
edge_set.add((blocker, blocked))
|
|
249
|
+
return edge_set
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _group_predecessors_by_successor(
|
|
253
|
+
edge_set: set[tuple[str, str]], key_to_uid: dict[str, int]
|
|
254
|
+
) -> dict[str, list[int]]:
|
|
255
|
+
"""Group predecessor uids by successor key for the ProjectData shape.
|
|
256
|
+
|
|
257
|
+
Each ``TaskData`` carries the links for which it is the successor, so the
|
|
258
|
+
blocker→blocked edges are inverted into ``{blocked_key: [blocker_uid, ...]}``.
|
|
259
|
+
"""
|
|
260
|
+
predecessors_by_successor: dict[str, list[int]] = {}
|
|
261
|
+
for blocker, blocked in edge_set:
|
|
262
|
+
predecessors_by_successor.setdefault(blocked, []).append(key_to_uid[blocker])
|
|
263
|
+
return predecessors_by_successor
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _build_task(
|
|
267
|
+
index: int,
|
|
268
|
+
key: str,
|
|
269
|
+
item: Element,
|
|
270
|
+
key_to_uid: dict[str, int],
|
|
271
|
+
predecessors_by_successor: dict[str, list[int]],
|
|
272
|
+
) -> TaskData:
|
|
273
|
+
"""Build one ``TaskData`` from a Jira ``<item>`` and its resolved predecessors."""
|
|
274
|
+
estimate = item.find("timeoriginalestimate")
|
|
275
|
+
seconds = estimate.get("seconds") if estimate is not None else None
|
|
276
|
+
predecessor_links = [
|
|
277
|
+
PredecessorLinkData(predecessor_uid=pred_uid, dep_type="FS", lag_days=0)
|
|
278
|
+
for pred_uid in sorted(predecessors_by_successor.get(key, []))
|
|
279
|
+
]
|
|
280
|
+
mapped_status = _map_status(item)
|
|
281
|
+
# A terminal status (Done/Closed → COMPLETE, or Review) means the work is
|
|
282
|
+
# 100% delivered, but the basic Jira XML export carries no percent field, so
|
|
283
|
+
# bulk_create would persist COMPLETE at 0% — an incoherent card and a task
|
|
284
|
+
# the Monte Carlo completion check still treats as unstarted.
|
|
285
|
+
#
|
|
286
|
+
# ``Task.percent_complete`` is a 0-100 percent (validators [0,100]; EVM/SPI
|
|
287
|
+
# and the rollup SQL read it as one), so write 100.0, not a 0-1 fraction
|
|
288
|
+
# (#2889). This is the same defect #1759 fixed on the MS Project side: a
|
|
289
|
+
# fraction written straight into the 0-100 field landed a delivered issue at
|
|
290
|
+
# 1%, so every progress ring, Gantt fill and parent rollup disagreed with the
|
|
291
|
+
# board column it came from. Both importers are now on the one 0-100 scale.
|
|
292
|
+
percent = (
|
|
293
|
+
_COMPLETE_PERCENT
|
|
294
|
+
if mapped_status in (TaskStatus.COMPLETE.value, TaskStatus.REVIEW.value)
|
|
295
|
+
else 0.0
|
|
296
|
+
)
|
|
297
|
+
return TaskData(
|
|
298
|
+
uid=key_to_uid[key],
|
|
299
|
+
name=_issue_name(item, key)[:512],
|
|
300
|
+
duration_days=_seconds_to_days(seconds),
|
|
301
|
+
# Flat WBS: sequential top-level outline numbers. Subtask / parent
|
|
302
|
+
# hierarchy is deferred (ADR-0259 out-of-scope).
|
|
303
|
+
outline_number=str(index + 1),
|
|
304
|
+
outline_level=0,
|
|
305
|
+
# #1768: carry the source status so completed/in-flight issues do not
|
|
306
|
+
# re-import as NOT_STARTED and inflate the forecast.
|
|
307
|
+
status=mapped_status,
|
|
308
|
+
percent_complete=percent,
|
|
309
|
+
predecessor_links=predecessor_links,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def parse_jira_xml(content: bytes) -> ProjectData:
|
|
314
|
+
"""Parse Jira XML export bytes into ``ProjectData`` (tasks + FS dependencies).
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
content: Raw bytes of a Jira Server / DC XML export.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
``ProjectData`` with one ``TaskData`` per issue (flat WBS, duration from
|
|
321
|
+
the original estimate) and ``PredecessorLinkData`` FS edges derived from
|
|
322
|
+
``Blocks`` issue links. Self-referential links and links to issues absent
|
|
323
|
+
from the export are dropped and noted in ``warnings`` (quarantine, not
|
|
324
|
+
crash) — a cyclic *set* of edges is left intact for the caller's graph
|
|
325
|
+
guard to reject before anything is persisted (#1665).
|
|
326
|
+
|
|
327
|
+
Raises:
|
|
328
|
+
JiraImportError: If the bytes are not a well-formed Jira XML export
|
|
329
|
+
(unparseable XML, or no ``<channel>``/``<item>`` structure).
|
|
330
|
+
"""
|
|
331
|
+
channel = _parse_channel(content)
|
|
332
|
+
items = channel.findall("item")
|
|
333
|
+
_enforce_row_cap(items)
|
|
334
|
+
|
|
335
|
+
warnings: list[str] = []
|
|
336
|
+
key_to_uid, ordered = _collect_ordered_issues(items, warnings)
|
|
337
|
+
if not ordered:
|
|
338
|
+
raise JiraImportError("Jira XML export contains no importable issues.")
|
|
339
|
+
|
|
340
|
+
edge_set = _collect_blocks_edges(ordered, key_to_uid, warnings)
|
|
341
|
+
predecessors_by_successor = _group_predecessors_by_successor(edge_set, key_to_uid)
|
|
342
|
+
|
|
343
|
+
tasks = [
|
|
344
|
+
_build_task(index, key, item, key_to_uid, predecessors_by_successor)
|
|
345
|
+
for index, (key, item) in enumerate(ordered)
|
|
346
|
+
]
|
|
347
|
+
|
|
348
|
+
channel_title = (channel.findtext("title") or "").strip()
|
|
349
|
+
name = channel_title or "Imported from Jira"
|
|
350
|
+
return ProjectData(name=name[:255], start_date=None, tasks=tasks, warnings=warnings)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Service layer for the Jira import outbox (mirrors msproject.services)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from django.utils import timezone
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def enqueue_jira_import(import_request_id: str) -> None:
|
|
13
|
+
"""Attempt to dispatch a PENDING JiraImportRequest row to Celery.
|
|
14
|
+
|
|
15
|
+
Safe to call from ``transaction.on_commit()`` and from the drain body. On a
|
|
16
|
+
broker outage the row stays PENDING so ``drain_jira_import_queue`` retries
|
|
17
|
+
within 30 seconds; a no-op if the row is no longer PENDING.
|
|
18
|
+
"""
|
|
19
|
+
from trueppm_api.apps.jiraimport.models import JiraImportRequest, JiraImportStatus
|
|
20
|
+
from trueppm_api.apps.jiraimport.tasks import import_jira
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
req = JiraImportRequest.objects.get(id=import_request_id, status=JiraImportStatus.PENDING)
|
|
24
|
+
except JiraImportRequest.DoesNotExist:
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
result = import_jira.delay(
|
|
29
|
+
project_id=str(req.project_id),
|
|
30
|
+
file_content_b64=req.file_content_b64,
|
|
31
|
+
filename=req.filename,
|
|
32
|
+
initiated_by_id=req.initiated_by_id,
|
|
33
|
+
import_request_id=str(req.id),
|
|
34
|
+
)
|
|
35
|
+
except Exception:
|
|
36
|
+
logger.warning(
|
|
37
|
+
"enqueue_jira_import: could not dispatch JiraImportRequest %s — drain will retry",
|
|
38
|
+
import_request_id,
|
|
39
|
+
)
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
JiraImportRequest.objects.filter(id=req.id, status=JiraImportStatus.PENDING).update(
|
|
43
|
+
status=JiraImportStatus.DISPATCHED,
|
|
44
|
+
celery_task_id=result.id,
|
|
45
|
+
dispatched_at=timezone.now(),
|
|
46
|
+
)
|