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,519 @@
|
|
|
1
|
+
"""OSS ``TASK_LINK_PROVIDERS`` registrations (ADR-0049 §3).
|
|
2
|
+
|
|
3
|
+
Three providers are registered in 0.2 so the credentials viewset has a
|
|
4
|
+
known set of provider keys to validate against:
|
|
5
|
+
|
|
6
|
+
- ``gitlab`` — GitLab.com plus self-hosted via ``base_url``
|
|
7
|
+
- ``github`` — GitHub.com plus GitHub Enterprise Server via ``base_url``
|
|
8
|
+
- ``generic`` — fallback for any HTTP URL the user wants on a task
|
|
9
|
+
|
|
10
|
+
The ``matches()`` / ``fetch_metadata()`` implementations (URL host detection,
|
|
11
|
+
status fetch with 5-second SSRF-protected timeout) land with #637 — registry
|
|
12
|
+
membership is the only behavior 0.2 needs.
|
|
13
|
+
|
|
14
|
+
``verify_token()`` lands here with #677: GitLab and GitHub ping their ``/user``
|
|
15
|
+
endpoint through the SSRF-guarded :mod:`~trueppm_api.apps.integrations.http`
|
|
16
|
+
helper so a wrong / expired / wrong-host PAT is rejected at connect time rather
|
|
17
|
+
than silently accepted and discovered later by #637's status fetch. The generic
|
|
18
|
+
provider inherits the base class's no-op verifier (accepted, unverified).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import re
|
|
24
|
+
from typing import Any, ClassVar, cast
|
|
25
|
+
from urllib.parse import quote, urlparse
|
|
26
|
+
|
|
27
|
+
from django.conf import settings
|
|
28
|
+
|
|
29
|
+
from . import http
|
|
30
|
+
from .encryption import decrypt_secret
|
|
31
|
+
from .opengraph import classify_preview_type, parse_preview
|
|
32
|
+
from .registry import (
|
|
33
|
+
LINK_STATUS_CLOSED,
|
|
34
|
+
LINK_STATUS_DRAFT,
|
|
35
|
+
LINK_STATUS_MERGED,
|
|
36
|
+
LINK_STATUS_OPEN,
|
|
37
|
+
LINK_STATUS_UNKNOWN,
|
|
38
|
+
PREVIEW_TYPE_IMAGE,
|
|
39
|
+
TASK_LINK_PROVIDERS,
|
|
40
|
+
LinkMetadata,
|
|
41
|
+
TaskLinkProvider,
|
|
42
|
+
VerifyResult,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class BaseUrlNotAllowed(ValueError):
|
|
47
|
+
"""Raised when a credential's ``base_url`` host is not permitted for its
|
|
48
|
+
provider (#902). The message is user-facing."""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# Per-provider SaaS hosts a ``base_url`` may target without operator opt-in.
|
|
52
|
+
# An empty base_url uses the provider's built-in SaaS default and is always
|
|
53
|
+
# allowed; any other host must be one of these, match the provider's self-hosted
|
|
54
|
+
# shape (Jira Cloud ``*.atlassian.net``), or be listed in the operator-set
|
|
55
|
+
# ``TRUEPPM_INTEGRATION_ALLOWED_HOSTS``.
|
|
56
|
+
_PROVIDER_DEFAULT_HOSTS: dict[str, frozenset[str]] = {
|
|
57
|
+
"github": frozenset({"api.github.com", "github.com", "www.github.com"}),
|
|
58
|
+
"gitlab": frozenset({"gitlab.com", "www.gitlab.com"}),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def assert_base_url_allowed(provider_key: str, base_url: str) -> None:
|
|
63
|
+
"""Reject a ``base_url`` host the provider must never ship its PAT to (#902).
|
|
64
|
+
|
|
65
|
+
Without this, a user could register e.g. ``provider="github",
|
|
66
|
+
base_url="https://attacker.example.com"`` — the github.com PAT is then sent
|
|
67
|
+
in an ``Authorization`` header to the attacker host on the very first verify.
|
|
68
|
+
The SSRF guard only blocks private/internal hosts; a public attacker host
|
|
69
|
+
passes it. So restrict ``base_url`` to known SaaS hosts plus an
|
|
70
|
+
operator-controlled allowlist for self-hosted GitHub Enterprise / GitLab CE.
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
BaseUrlNotAllowed: the host is not permitted for ``provider_key``.
|
|
74
|
+
"""
|
|
75
|
+
if not base_url:
|
|
76
|
+
return
|
|
77
|
+
# ``generic`` is a link-only provider — it stores no secret and sends no PAT,
|
|
78
|
+
# so an arbitrary base_url carries no exfiltration risk.
|
|
79
|
+
if provider_key == "generic":
|
|
80
|
+
return
|
|
81
|
+
host = (urlparse(base_url).hostname or "").lower()
|
|
82
|
+
if not host:
|
|
83
|
+
raise BaseUrlNotAllowed("Host URL must include a hostname.")
|
|
84
|
+
# Settings name drops the TRUEPPM_ env prefix, matching every other binding
|
|
85
|
+
# (EGRESS_ALLOWLISTED_HOSTS <- TRUEPPM_EGRESS_ALLOWLISTED_HOSTS). Reading the
|
|
86
|
+
# prefixed name here is why this allow-list was dead until #2860: no settings
|
|
87
|
+
# module ever defined that attribute, so the getattr always fell back to None
|
|
88
|
+
# and an operator's documented env var did nothing.
|
|
89
|
+
operator_allow = {
|
|
90
|
+
h.lower() for h in (getattr(settings, "INTEGRATION_ALLOWED_HOSTS", None) or [])
|
|
91
|
+
}
|
|
92
|
+
if host in operator_allow:
|
|
93
|
+
return
|
|
94
|
+
if provider_key == "jira":
|
|
95
|
+
# Jira Cloud (ADR-0097): only the tenant's atlassian.net host.
|
|
96
|
+
if host == "atlassian.net" or host.endswith(".atlassian.net"):
|
|
97
|
+
return
|
|
98
|
+
elif host in _PROVIDER_DEFAULT_HOSTS.get(provider_key, frozenset()):
|
|
99
|
+
return
|
|
100
|
+
raise BaseUrlNotAllowed(
|
|
101
|
+
f"Host URL {host!r} is not an allowed host for the {provider_key!r} provider. "
|
|
102
|
+
"A self-hosted instance must be added to TRUEPPM_INTEGRATION_ALLOWED_HOSTS by an operator."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# GitLab resource segments that carry a fetchable open/closed/merged state.
|
|
107
|
+
# commit / tree (branch) URLs are valid links but have no such lifecycle, so
|
|
108
|
+
# they resolve to "unknown" without a fetch.
|
|
109
|
+
_GITLAB_KINDS = {"merge_requests", "issues"}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _credential_secret(credential: Any) -> str | None:
|
|
113
|
+
"""Decrypt a credential's PAT, or return ``None`` if there is no credential."""
|
|
114
|
+
if credential is None:
|
|
115
|
+
return None
|
|
116
|
+
return decrypt_secret(credential.secret_ciphertext)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _verify_via_user_endpoint(
|
|
120
|
+
plaintext: str,
|
|
121
|
+
*,
|
|
122
|
+
url: str,
|
|
123
|
+
headers: dict[str, str],
|
|
124
|
+
username_key: str,
|
|
125
|
+
) -> VerifyResult:
|
|
126
|
+
"""Ping a provider ``/user``-style endpoint and map the result to ``VerifyResult``.
|
|
127
|
+
|
|
128
|
+
Shared by GitLab and GitHub — only the URL, auth header, and the JSON key
|
|
129
|
+
that carries the account name differ. A 200 means the token authenticates;
|
|
130
|
+
401/403 (or any other 4xx) means the token is wrong, expired, or scoped for
|
|
131
|
+
a different host (a github.com PAT pasted into the gitlab slot fails its
|
|
132
|
+
``/user`` auth). 5xx and transport failures are reported as unreachable so
|
|
133
|
+
the user can retry rather than assuming a dead token.
|
|
134
|
+
"""
|
|
135
|
+
try:
|
|
136
|
+
response = http.get(url, headers=headers)
|
|
137
|
+
except http.EgressTimeout:
|
|
138
|
+
return VerifyResult(ok=False, reason="provider_timeout")
|
|
139
|
+
except http.EgressBlocked:
|
|
140
|
+
return VerifyResult(ok=False, reason="blocked_host")
|
|
141
|
+
except http.EgressError:
|
|
142
|
+
return VerifyResult(ok=False, reason="provider_unreachable")
|
|
143
|
+
|
|
144
|
+
if response.status == 200:
|
|
145
|
+
payload = response.json()
|
|
146
|
+
username = payload.get(username_key) if isinstance(payload, dict) else None
|
|
147
|
+
# GitHub exposes granted scopes in a response header; GitLab does not on
|
|
148
|
+
# /user, so scopes stays None there.
|
|
149
|
+
raw_scopes = response.headers.get("x-oauth-scopes")
|
|
150
|
+
scopes = (
|
|
151
|
+
[s.strip() for s in raw_scopes.split(",") if s.strip()]
|
|
152
|
+
if raw_scopes is not None
|
|
153
|
+
else None
|
|
154
|
+
)
|
|
155
|
+
return VerifyResult(ok=True, username=username, scopes=scopes)
|
|
156
|
+
if response.status >= 500:
|
|
157
|
+
return VerifyResult(ok=False, reason="provider_unreachable")
|
|
158
|
+
# 401 / 403 / 404 / other 4xx — the token did not authenticate.
|
|
159
|
+
return VerifyResult(ok=False, reason="invalid_token")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class GitLabTaskLinkProvider(TaskLinkProvider):
|
|
163
|
+
"""GitLab task-link provider (gitlab.com + self-hosted)."""
|
|
164
|
+
|
|
165
|
+
key: ClassVar[str] = "gitlab"
|
|
166
|
+
label: ClassVar[str] = "GitLab"
|
|
167
|
+
requires_credential: ClassVar[bool] = True
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def matches(cls, url: str) -> bool:
|
|
171
|
+
"""Auto-detect gitlab.com links. Self-hosted hosts are matched by the
|
|
172
|
+
viewset against the user's stored ``base_url`` (see ``resolve_provider_key``)."""
|
|
173
|
+
host = (urlparse(url).hostname or "").lower()
|
|
174
|
+
return host in ("gitlab.com", "www.gitlab.com")
|
|
175
|
+
|
|
176
|
+
def fetch_metadata(self, url: str, credential: Any) -> LinkMetadata:
|
|
177
|
+
secret = _credential_secret(credential)
|
|
178
|
+
if secret is None:
|
|
179
|
+
return LinkMetadata(status=LINK_STATUS_UNKNOWN)
|
|
180
|
+
parsed = _parse_gitlab_url(url)
|
|
181
|
+
if parsed is None:
|
|
182
|
+
return LinkMetadata(status=LINK_STATUS_UNKNOWN)
|
|
183
|
+
project_path, kind, ref = parsed
|
|
184
|
+
base_url = getattr(credential, "base_url", "") or ""
|
|
185
|
+
api_root = base_url.rstrip("/") if base_url else "https://gitlab.com"
|
|
186
|
+
enc_path = quote(project_path, safe="")
|
|
187
|
+
api_url = f"{api_root}/api/v4/projects/{enc_path}/{kind}/{ref}"
|
|
188
|
+
payload = _fetch_json(api_url, {"PRIVATE-TOKEN": secret, "Accept": "application/json"})
|
|
189
|
+
if payload is None:
|
|
190
|
+
return LinkMetadata(status=LINK_STATUS_UNKNOWN)
|
|
191
|
+
title = payload.get("title")
|
|
192
|
+
if kind == "issues":
|
|
193
|
+
status = LINK_STATUS_OPEN if payload.get("state") == "opened" else LINK_STATUS_CLOSED
|
|
194
|
+
return LinkMetadata(status=status, title=title)
|
|
195
|
+
# merge request
|
|
196
|
+
state = payload.get("state")
|
|
197
|
+
if state == "merged":
|
|
198
|
+
status = LINK_STATUS_MERGED
|
|
199
|
+
elif state in ("closed", "locked"):
|
|
200
|
+
status = LINK_STATUS_CLOSED
|
|
201
|
+
elif payload.get("draft") or payload.get("work_in_progress"):
|
|
202
|
+
status = LINK_STATUS_DRAFT
|
|
203
|
+
else:
|
|
204
|
+
status = LINK_STATUS_OPEN
|
|
205
|
+
return LinkMetadata(status=status, title=title)
|
|
206
|
+
|
|
207
|
+
@classmethod
|
|
208
|
+
def verify_token(cls, plaintext: str, *, base_url: str | None = None) -> VerifyResult:
|
|
209
|
+
"""Verify a GitLab PAT by calling ``GET /api/v4/user`` with it.
|
|
210
|
+
|
|
211
|
+
GitLab authenticates PATs via the ``PRIVATE-TOKEN`` header. ``base_url``
|
|
212
|
+
targets a self-hosted CE/EE instance; empty means gitlab.com.
|
|
213
|
+
"""
|
|
214
|
+
api_root = base_url.rstrip("/") if base_url else "https://gitlab.com"
|
|
215
|
+
return _verify_via_user_endpoint(
|
|
216
|
+
plaintext,
|
|
217
|
+
url=f"{api_root}/api/v4/user",
|
|
218
|
+
headers={"PRIVATE-TOKEN": plaintext, "Accept": "application/json"},
|
|
219
|
+
username_key="username",
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class GitHubTaskLinkProvider(TaskLinkProvider):
|
|
224
|
+
"""GitHub task-link provider (github.com + GitHub Enterprise Server)."""
|
|
225
|
+
|
|
226
|
+
key: ClassVar[str] = "github"
|
|
227
|
+
label: ClassVar[str] = "GitHub"
|
|
228
|
+
requires_credential: ClassVar[bool] = True
|
|
229
|
+
|
|
230
|
+
@classmethod
|
|
231
|
+
def matches(cls, url: str) -> bool:
|
|
232
|
+
"""Auto-detect github.com links. GitHub Enterprise Server hosts are
|
|
233
|
+
matched by the viewset against the user's stored ``base_url``."""
|
|
234
|
+
host = (urlparse(url).hostname or "").lower()
|
|
235
|
+
return host in ("github.com", "www.github.com")
|
|
236
|
+
|
|
237
|
+
def fetch_metadata(self, url: str, credential: Any) -> LinkMetadata:
|
|
238
|
+
secret = _credential_secret(credential)
|
|
239
|
+
if secret is None:
|
|
240
|
+
return LinkMetadata(status=LINK_STATUS_UNKNOWN)
|
|
241
|
+
parsed = _parse_github_url(url)
|
|
242
|
+
if parsed is None:
|
|
243
|
+
return LinkMetadata(status=LINK_STATUS_UNKNOWN)
|
|
244
|
+
owner, repo, kind, ref = parsed
|
|
245
|
+
base_url = getattr(credential, "base_url", "") or ""
|
|
246
|
+
api_root = f"{base_url.rstrip('/')}/api/v3" if base_url else "https://api.github.com"
|
|
247
|
+
# GitHub's PR endpoint is /pulls/{n}; issues is /issues/{n}.
|
|
248
|
+
api_kind = "pulls" if kind == "pull" else "issues"
|
|
249
|
+
api_url = f"{api_root}/repos/{owner}/{repo}/{api_kind}/{ref}"
|
|
250
|
+
payload = _fetch_json(
|
|
251
|
+
api_url,
|
|
252
|
+
{
|
|
253
|
+
"Authorization": f"Bearer {secret}",
|
|
254
|
+
"Accept": "application/vnd.github+json",
|
|
255
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
256
|
+
},
|
|
257
|
+
)
|
|
258
|
+
if payload is None:
|
|
259
|
+
return LinkMetadata(status=LINK_STATUS_UNKNOWN)
|
|
260
|
+
title = payload.get("title")
|
|
261
|
+
if kind == "issues":
|
|
262
|
+
status = LINK_STATUS_OPEN if payload.get("state") == "open" else LINK_STATUS_CLOSED
|
|
263
|
+
return LinkMetadata(status=status, title=title)
|
|
264
|
+
# pull request
|
|
265
|
+
if payload.get("merged"):
|
|
266
|
+
status = LINK_STATUS_MERGED
|
|
267
|
+
elif payload.get("state") == "closed":
|
|
268
|
+
status = LINK_STATUS_CLOSED
|
|
269
|
+
elif payload.get("draft"):
|
|
270
|
+
status = LINK_STATUS_DRAFT
|
|
271
|
+
else:
|
|
272
|
+
status = LINK_STATUS_OPEN
|
|
273
|
+
return LinkMetadata(status=status, title=title)
|
|
274
|
+
|
|
275
|
+
@classmethod
|
|
276
|
+
def verify_token(cls, plaintext: str, *, base_url: str | None = None) -> VerifyResult:
|
|
277
|
+
"""Verify a GitHub PAT by calling ``GET /user`` with it.
|
|
278
|
+
|
|
279
|
+
GitHub authenticates PATs via ``Authorization: Bearer``. github.com's
|
|
280
|
+
API lives at ``api.github.com``; a GitHub Enterprise Server ``base_url``
|
|
281
|
+
(``https://ghe.example.com``) serves its API under ``/api/v3``.
|
|
282
|
+
"""
|
|
283
|
+
api_root = f"{base_url.rstrip('/')}/api/v3" if base_url else "https://api.github.com"
|
|
284
|
+
return _verify_via_user_endpoint(
|
|
285
|
+
plaintext,
|
|
286
|
+
url=f"{api_root}/user",
|
|
287
|
+
headers={
|
|
288
|
+
"Authorization": f"Bearer {plaintext}",
|
|
289
|
+
"Accept": "application/vnd.github+json",
|
|
290
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
291
|
+
},
|
|
292
|
+
username_key="login",
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class GenericTaskLinkProvider(TaskLinkProvider):
|
|
297
|
+
"""Fallback provider for any non-GitLab/GitHub URL pasted on a task.
|
|
298
|
+
|
|
299
|
+
Credentials are still accepted for the generic provider so a user with
|
|
300
|
+
a self-hosted Gitea or Forgejo instance can stash a PAT they will refer
|
|
301
|
+
to when configuring the matching custom workflow.
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
key: ClassVar[str] = "generic"
|
|
305
|
+
label: ClassVar[str] = "Generic"
|
|
306
|
+
requires_credential: ClassVar[bool] = False
|
|
307
|
+
|
|
308
|
+
@classmethod
|
|
309
|
+
def matches(cls, url: str) -> bool:
|
|
310
|
+
# Generic is the explicit fallback chosen by ``resolve_provider_key``
|
|
311
|
+
# when nothing else matches — it never auto-matches a URL itself.
|
|
312
|
+
return False
|
|
313
|
+
|
|
314
|
+
def fetch_metadata(self, url: str, credential: Any) -> LinkMetadata:
|
|
315
|
+
# No known API shape for an arbitrary host — the link is still useful
|
|
316
|
+
# to humans, it just has no fetchable lifecycle status.
|
|
317
|
+
return LinkMetadata(status=LINK_STATUS_UNKNOWN)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class FileLinkProvider(TaskLinkProvider):
|
|
321
|
+
"""Base for cloud-file OpenGraph preview providers (#571, ADR-0163).
|
|
322
|
+
|
|
323
|
+
Unlike the git providers these need **no** credential — they anonymously GET
|
|
324
|
+
the pasted URL itself through the SSRF-guarded :mod:`http` helper and scrape
|
|
325
|
+
its OpenGraph / Twitter-Card metadata into a rich :class:`LinkMetadata`
|
|
326
|
+
(``status`` stays ``unknown`` — a file has no PR/MR lifecycle; the preview
|
|
327
|
+
fields carry the signal). Each subclass declares only its ``hosts``; the
|
|
328
|
+
match, fetch, and classify logic is shared here.
|
|
329
|
+
|
|
330
|
+
Security: ``http.get`` validates the URL against the SSRF deny-list (private /
|
|
331
|
+
loopback / link-local / cloud-metadata) and disables redirects, so a public
|
|
332
|
+
file host can neither reach an internal address nor be bounced to one, and a
|
|
333
|
+
private file returns its provider's login wall (a generic app title), never
|
|
334
|
+
the private document's name.
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
requires_credential: ClassVar[bool] = False
|
|
338
|
+
# Anonymous unfurl — never stores a PAT, so it stays off the Connected
|
|
339
|
+
# Accounts page (#587). See TaskLinkProvider.connectable.
|
|
340
|
+
connectable: ClassVar[bool] = False
|
|
341
|
+
# Exact hosts and their subdomains this provider claims, lower-cased.
|
|
342
|
+
hosts: ClassVar[frozenset[str]] = frozenset()
|
|
343
|
+
|
|
344
|
+
@classmethod
|
|
345
|
+
def matches(cls, url: str) -> bool:
|
|
346
|
+
host = (urlparse(url).hostname or "").lower()
|
|
347
|
+
if not host:
|
|
348
|
+
return False
|
|
349
|
+
# Exact host or a subdomain of a claimed host (``acme.box.com`` →
|
|
350
|
+
# ``box.com``). The leading-dot guard prevents ``box.com.evil.com`` from
|
|
351
|
+
# matching ``box.com``.
|
|
352
|
+
return any(host == h or host.endswith(f".{h}") for h in cls.hosts)
|
|
353
|
+
|
|
354
|
+
def fetch_metadata(self, url: str, credential: Any) -> LinkMetadata:
|
|
355
|
+
try:
|
|
356
|
+
response = http.get(url, headers={"Accept": "text/html,application/xhtml+xml"})
|
|
357
|
+
except (http.EgressBlocked, http.EgressTimeout, http.EgressError):
|
|
358
|
+
return LinkMetadata(status=LINK_STATUS_UNKNOWN)
|
|
359
|
+
if response.status != 200:
|
|
360
|
+
# A 4xx/5xx (private file login wall, dead link) has no useful preview;
|
|
361
|
+
# leave the row's last-good preview untouched by returning bare unknown.
|
|
362
|
+
return LinkMetadata(status=LINK_STATUS_UNKNOWN)
|
|
363
|
+
preview = parse_preview(response.body, base_url=url)
|
|
364
|
+
preview_type = classify_preview_type(url, response.headers.get("content-type"))
|
|
365
|
+
thumbnail = preview.thumbnail_url
|
|
366
|
+
# A direct image URL (no OpenGraph markup) is its own thumbnail when https.
|
|
367
|
+
if (
|
|
368
|
+
thumbnail is None
|
|
369
|
+
and preview_type == PREVIEW_TYPE_IMAGE
|
|
370
|
+
and url.lower().startswith("https://")
|
|
371
|
+
):
|
|
372
|
+
thumbnail = url
|
|
373
|
+
return LinkMetadata(
|
|
374
|
+
status=LINK_STATUS_UNKNOWN,
|
|
375
|
+
title=preview.title,
|
|
376
|
+
description=preview.description,
|
|
377
|
+
thumbnail_url=thumbnail,
|
|
378
|
+
preview_type=preview_type,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
class GoogleDriveTaskLinkProvider(FileLinkProvider):
|
|
383
|
+
"""Google Drive / Docs / Sheets / Slides file preview (#571)."""
|
|
384
|
+
|
|
385
|
+
key: ClassVar[str] = "google_drive"
|
|
386
|
+
label: ClassVar[str] = "Google Drive"
|
|
387
|
+
hosts: ClassVar[frozenset[str]] = frozenset(
|
|
388
|
+
{"drive.google.com", "docs.google.com", "sheets.google.com", "slides.google.com"}
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
class DropboxTaskLinkProvider(FileLinkProvider):
|
|
393
|
+
"""Dropbox shared-file preview (#571)."""
|
|
394
|
+
|
|
395
|
+
key: ClassVar[str] = "dropbox"
|
|
396
|
+
label: ClassVar[str] = "Dropbox"
|
|
397
|
+
hosts: ClassVar[frozenset[str]] = frozenset({"dropbox.com"})
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
class BoxTaskLinkProvider(FileLinkProvider):
|
|
401
|
+
"""Box shared-file preview (#571)."""
|
|
402
|
+
|
|
403
|
+
key: ClassVar[str] = "box"
|
|
404
|
+
label: ClassVar[str] = "Box"
|
|
405
|
+
hosts: ClassVar[frozenset[str]] = frozenset({"box.com"})
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
class OneDriveTaskLinkProvider(FileLinkProvider):
|
|
409
|
+
"""OneDrive / SharePoint shared-file preview (#571)."""
|
|
410
|
+
|
|
411
|
+
key: ClassVar[str] = "onedrive"
|
|
412
|
+
label: ClassVar[str] = "OneDrive"
|
|
413
|
+
hosts: ClassVar[frozenset[str]] = frozenset({"onedrive.live.com", "sharepoint.com", "1drv.ms"})
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
# Ordered list — apps.py iterates these in declaration order so the OSS
|
|
417
|
+
# registration order is deterministic for tests + the credentials list. File
|
|
418
|
+
# providers precede ``generic`` so their hosts win the ``matches()`` scan; the
|
|
419
|
+
# git providers stay first to preserve their existing resolution order.
|
|
420
|
+
OSS_TASK_LINK_PROVIDERS: tuple[type[TaskLinkProvider], ...] = (
|
|
421
|
+
GitLabTaskLinkProvider,
|
|
422
|
+
GitHubTaskLinkProvider,
|
|
423
|
+
GoogleDriveTaskLinkProvider,
|
|
424
|
+
DropboxTaskLinkProvider,
|
|
425
|
+
BoxTaskLinkProvider,
|
|
426
|
+
OneDriveTaskLinkProvider,
|
|
427
|
+
GenericTaskLinkProvider,
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
# ---------------------------------------------------------------------------
|
|
432
|
+
# URL parsing + provider resolution
|
|
433
|
+
# ---------------------------------------------------------------------------
|
|
434
|
+
|
|
435
|
+
# GitHub: https://host/{owner}/{repo}/(pull|issues)/{number}
|
|
436
|
+
_GITHUB_RE = re.compile(r"^/(?P<owner>[^/]+)/(?P<repo>[^/]+)/(?P<kind>pull|issues)/(?P<ref>\d+)")
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _parse_github_url(url: str) -> tuple[str, str, str, str] | None:
|
|
440
|
+
"""Parse a GitHub PR/issue URL into ``(owner, repo, kind, ref)``.
|
|
441
|
+
|
|
442
|
+
``kind`` is ``"pull"`` or ``"issues"``; commit/tree/other shapes return
|
|
443
|
+
``None`` (no fetchable status).
|
|
444
|
+
"""
|
|
445
|
+
match = _GITHUB_RE.match(urlparse(url).path)
|
|
446
|
+
if match is None:
|
|
447
|
+
return None
|
|
448
|
+
return (match["owner"], match["repo"], match["kind"], match["ref"])
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _parse_gitlab_url(url: str) -> tuple[str, str, str] | None:
|
|
452
|
+
"""Parse a GitLab MR/issue URL into ``(project_path, kind, ref)``.
|
|
453
|
+
|
|
454
|
+
GitLab paths put the (possibly nested) project before a ``/-/`` separator,
|
|
455
|
+
then ``merge_requests``/``issues`` and the IID, e.g.
|
|
456
|
+
``/group/sub/proj/-/merge_requests/42``. commit/tree/other return ``None``.
|
|
457
|
+
"""
|
|
458
|
+
path = urlparse(url).path
|
|
459
|
+
if "/-/" not in path:
|
|
460
|
+
return None
|
|
461
|
+
project_part, _, rest = path.partition("/-/")
|
|
462
|
+
project_path = project_part.strip("/")
|
|
463
|
+
segments = rest.strip("/").split("/")
|
|
464
|
+
if len(segments) < 2 or not project_path:
|
|
465
|
+
return None
|
|
466
|
+
kind, ref = segments[0], segments[1]
|
|
467
|
+
if kind not in _GITLAB_KINDS or not ref:
|
|
468
|
+
return None
|
|
469
|
+
return (project_path, kind, ref)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _fetch_json(url: str, headers: dict[str, str]) -> dict[str, Any] | None:
|
|
473
|
+
"""SSRF-guarded GET that returns a parsed JSON object, or ``None`` on any
|
|
474
|
+
failure (blocked host, timeout, transport error, non-200, non-object body).
|
|
475
|
+
|
|
476
|
+
fetch_metadata never raises for an unreachable provider — a failed fetch
|
|
477
|
+
leaves the link's cached status as "unknown" so the UI can prompt a retry.
|
|
478
|
+
"""
|
|
479
|
+
try:
|
|
480
|
+
response = http.get(url, headers=headers)
|
|
481
|
+
except (http.EgressBlocked, http.EgressTimeout, http.EgressError):
|
|
482
|
+
return None
|
|
483
|
+
if response.status != 200:
|
|
484
|
+
return None
|
|
485
|
+
payload = response.json()
|
|
486
|
+
return payload if isinstance(payload, dict) else None
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def resolve_provider_key(url: str, *, user: Any) -> str:
|
|
490
|
+
"""Pick the provider key for ``url`` for the given user.
|
|
491
|
+
|
|
492
|
+
Order: a SaaS host match from ``matches()`` (gitlab.com / github.com), then
|
|
493
|
+
the user's connected self-hosted credentials (a link whose host equals a
|
|
494
|
+
stored ``base_url`` host routes to that provider), then ``"generic"``. The
|
|
495
|
+
self-hosted step is what lets a CE/EE or GHES URL fetch real status.
|
|
496
|
+
"""
|
|
497
|
+
for key in TASK_LINK_PROVIDERS:
|
|
498
|
+
handler = TASK_LINK_PROVIDERS.get(key)
|
|
499
|
+
if handler is not None and cast("type[TaskLinkProvider]", handler).matches(url):
|
|
500
|
+
return key
|
|
501
|
+
host = (urlparse(url).hostname or "").lower()
|
|
502
|
+
if host:
|
|
503
|
+
# Lazy import to avoid a models import at app-load / registry time.
|
|
504
|
+
from .models import IntegrationCredential
|
|
505
|
+
|
|
506
|
+
# ``IntegrationCredential`` is shared by two provider namespaces: git-link
|
|
507
|
+
# PATs (TASK_LINK_PROVIDERS) and user-scoped external task sources
|
|
508
|
+
# (EXTERNAL_TASK_SOURCES, e.g. a ``jira`` connection on an atlassian.net
|
|
509
|
+
# base_url — ADR-0097). Only the former resolve a task link; skip a row
|
|
510
|
+
# whose provider isn't a registered TaskLinkProvider, otherwise a stored
|
|
511
|
+
# Jira connection would route an atlassian.net link to ``"jira"``, which
|
|
512
|
+
# is not a TASK_LINK_PROVIDERS key → TaskLink.clean() would 400 a link
|
|
513
|
+
# that previously resolved to ``"generic"`` (#1418 cross-registry guard).
|
|
514
|
+
for cred in IntegrationCredential.objects.filter(user=user).exclude(base_url=""):
|
|
515
|
+
if TASK_LINK_PROVIDERS.get(cred.provider) is None:
|
|
516
|
+
continue
|
|
517
|
+
if (urlparse(cred.base_url).hostname or "").lower() == host:
|
|
518
|
+
return cred.provider
|
|
519
|
+
return "generic"
|