Skip to content

Project import endpoint #7127

@khvn26

Description

@khvn26

Scope

  • New model: ProjectImport
    • Inherits abstract_base_auditable_model_factory() for audit log entries.
    • history_record_class_path, related_object_type (new RelatedObjectType.PROJECT_IMPORT)
    • project FK
    • created_by FK to FFAdminUser (nullable)
    • created_by_api_key FK to MasterAPIKey (nullable)
    • strategy (SKIP / OVERWRITE_DESTRUCTIVE)
    • status (PROCESSING / SUCCESS / FAILED)
    • dataTextField, 10MB max.
    • result_summaryJSONField (nullable). Populated by the import task on completion:
      class ProjectImportResultSummary(TypedDict):
          environments_created: int
          environments_updated: int
          tags_created: int
          segments_created: int
          segments_deleted: int
          features_created: int
          features_deleted: int
          feature_states_created: int
          feature_segments_created: int
          identity_overrides_created: int
          identity_overrides_deleted: int
    • created_at
    • Audit log messages:
      • get_create_log_message: "Project import requested (strategy: Skip)" or "Project import requested (strategy: Overwrite Destructive)"
      • get_update_log_message: on SUCCESS, formats result_summary into a readable message, e.g. "Project import completed — created 5 features, 3 segments, 1 environment". On FAILED, returns "Project import failed".
    • get_audit_log_author returns self.created_by
    • _get_project returns self.project
  • Import serializers — mirror the export serializers but for deserialization:
    • Accept name-based references, resolve to DB objects.
    • Reuse FeatureStateValueSerializer for value deserialization.
    • Reuse SegmentRuleSerializer / ConditionSerializer for segment rule deserialization.
  • Entity resolution service, structured as clean functions with clear signatures for future extraction into a shared provisioning core (Spike: Unified provisioning system #6797):
    • resolve_or_create_environment(project, name, settings, strategy) -> Environment
      • SKIP: create if missing, leave existing settings unchanged.
      • OVERWRITE_DESTRUCTIVE: create if missing, update existing settings to match.
    • resolve_or_create_tag(project, label, defaults) -> Tag
      • Always create-or-skip (tags are lightweight, no destructive override needed).
    • resolve_or_create_segment(project, name, rules_data, strategy) -> Segment
      • SKIP: create if missing, leave existing unchanged.
      • OVERWRITE_DESTRUCTIVE: delete matched segment (cascade deletes rules, conditions, and all FeatureSegment/FeatureState overrides), recreate from data. Reuses SegmentRuleSerializer for nested rules.
    • resolve_or_create_feature(project, name, feature_data, strategy) -> Feature
      • SKIP: create if missing, leave existing unchanged.
      • OVERWRITE_DESTRUCTIVE: delete matched feature (cascade), recreate from data. If project uses feature versioning, create a new EnvironmentFeatureVersion via versioning_service.update_flag.
    • create_feature_state(environment, feature, state_data) -> FeatureState
      • Always creates (called after features are resolved, for each environment in the export).
    • create_feature_segment(environment, feature, segment, priority, state_data) -> FeatureSegment
      • Always creates (called after features and segments are resolved).
  • New async task: import_project(project_import_id)
    • Parse JSON, detect format (array = env-level → reject 400, object = project-level).
    • For project-level format, call the service functions above in order:
      1. Segments
      2. Features
      3. Environments (+ feature states and feature segments per environment)
      4. Tags
    • Silently ignores identity_overrides in the export data (handled separately).
    • Each step runs in its own DB transaction — if step 3 fails, segments and features from steps 1-2 are already committed. The result_summary tracks progress so partial imports are visible.
  • New views:
    • POST /projects/{project_id}/project-imports/ — upload JSON + strategy.
    • GET /projects/{project_id}/project-imports/ — list imports.
  • Request/response serializers for create (file upload + strategy) and list.
  • Permissions: organisation admin (destructive operation, matching env-level import precedent).
  • Recurring cleanup: extend existing task.
  • Migration for new model.

Acceptance criteria (write as tests first)

  • POST with file + strategy creates an async import, returns 202.
  • SKIP with no conflicts — all entities created.
  • SKIP with existing features/segments — existing entities untouched, new ones created.
  • OVERWRITE_DESTRUCTIVE with existing features/segments — matched entities deleted and recreated.
  • OVERWRITE_DESTRUCTIVE with feature versioning enabled — new versions created for modified features.
  • Missing environments created with correct settings from export.
  • Existing environment settings updated in OVERWRITE_DESTRUCTIVE, unchanged in SKIP.
  • Tags matched by label, created if missing.
  • Segments matched by name, rules deserialized via existing serializer.
  • Feature states correctly associated with environments.
  • Feature segments correctly link features, segments, and environments.
  • identity_overrides key in export data is silently ignored.
  • Invalid/malformed JSON returns 400.
  • Env-level format (array) returns 400 with clear error message.

Metadata

Metadata

Assignees

Labels

apiIssue related to the REST API

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions