Browse Source

Issue #0 by fgm: added UnfuddleCase migration

- added MigrateDestinationNodeCase to prepare() casetracker case nodes
- added UnfudleTicketMigration to import tickets
- added dependencies
- fixed project and people reference fields
Frederic G. MARAND 12 years ago
parent
commit
468b8b8299
2 changed files with 215 additions and 4 deletions
  1. 194 3
      UnfuddleMigration.inc
  2. 21 1
      migrateunfuddle.module

+ 194 - 3
UnfuddleMigration.inc

@@ -125,6 +125,8 @@ class UnfuddlePeopleMigration extends UnfuddleMigration {
       MigrateDestinationUser::getKeySchema());
 
     // Do not map uid: this prevents user_save from creating the accounts.
+    $this->addFieldMapping('uid', NULL)
+      ->defaultValue(0);
 
     $this->addXPathFieldMapping('created',  'created-at');
     $this->addXPathFieldMapping('mail',     'email');
@@ -255,6 +257,7 @@ class UnfuddleProjectMigration extends UnfuddleMigration {
       )
     );
 
+    $this->dependencies = array('UnfuddlePeople');
     $this->destination = new MigrateDestinationNode('casetracker_basic_project');
     $this->map = new MigrateSQLMap('unfuddle_project',
       $source_key,
@@ -263,7 +266,6 @@ class UnfuddleProjectMigration extends UnfuddleMigration {
     $this->addXPathFieldMapping('created',  'created-at');
     $this->addXPathFieldMapping('status',   'archived')
       ->description(t('Inverted when converting to node.status'));
-      $this->afterConstruct();
     $this->addXPathFieldMapping('body',     'description')
       ->description(t('Applying site default input format: will often be wrong'));
     $this->addXPathFieldMapping('teaser',   node_teaser('description', FILTER_FORMAT_DEFAULT))
@@ -271,14 +273,203 @@ class UnfuddleProjectMigration extends UnfuddleMigration {
     $this->addXPathFieldMapping('changed',  'updated-at');
     $this->addXPathFieldMapping('title',    'title');
 
-    $this->addXPathFieldMapping('uid',           NULL)->defaultValue(1);
-    $this->addXPathFieldMapping('revision_uid',  NULL)->defaultValue(1);
+    $this->addXPathFieldMapping('uid',           NULL)
+      ->sourceMigration('UnfuddlePeople')
+      ->defaultValue(1);
+    $this->addXPathFieldMapping('revision_uid',  NULL)
+      ->sourceMigration('UnfuddlePeople')
+      ->defaultValue(1);
     $this->addXPathFieldMapping('path',      'short-name')
       ->description(t('Auto-aliased to project/(short-name)'));
+    $this->afterConstruct();
+  }
+
+  public function preparerow(stdClass $row) {
+    $row->xml->{'archived'} = ((int) $row->xml->{'archived'}) ? 0 : 1;
+    $row->xml->{'short-name'} = 'project/'. drupal_clean_css_identifier($row->xml->{'short-name'});
+  }
+}
+
+class MigrateDestinationNodeCase extends MigrateDestinationNode {
+  public function __construct() {
+    parent::__construct('casetracker_basic_case');
+  }
+
+  /**
+   * Could also be implemented as a migrate hook_migrate_fields() in module.
+   * Which is best ?
+   *
+   * (non-PHPdoc)
+   * @see MigrateDestinationNode::fields()
+   */
+  public function fields() {
+    $fields = parent::fields();
+    $fields += array(
+      'pid'              => t('Case project'),
+      'case_number'      => t('Case number'),
+      'assign_to'        => t('Case assigned to'),
+      'case_priority_id' => t('Case priority'),
+      'case_type_id'     => t('Case type'),
+      'case_status_id'   => t('Case status'),
+    );
+    return $fields;
+  }
+
+  /**
+   * Casetracker saves its fields in a separate stdClass extra field.
+   *
+   * (non-PHPdoc)
+   * @see MigrateDestinationEntity::prepare()
+   */
+  public function prepare($entity, stdClass $source_row) {
+    $extraFields = array(
+      'pid',
+      'case_priority_id',
+      'case_type_id',
+      'assign_to',
+      'case_status_id',
+    );
+
+    $entity->casetracker = new stdClass();
+    foreach ($extraFields as $extra) {
+      if (isset($entity->$extra)) {
+        $entity->casetracker->$extra = $entity->$extra;
+        unset($entity->extra);
+      }
+    }
+
+    $entity->casetracker->case_type_id = 9; // Bug. Case type is not a default Unfuddle field
+  }
+}
+
+/**
+ * Import "project" ticket data from Unfuddle into CaseTracker project nodes
+ */
+class UnfuddleTicketMigration extends UnfuddleMigration {
+  public $unmigratedDestinations = array(
+    'status',
+    'promote',
+    'sticky',
+    'revision',
+    'language',
+  );
+  public $unmigratedSources = array(
+    'component-id',
+    'description-format',
+    'due-on',
+    'hours-estimate-current',
+    'hours-estimate-initial',
+    'milestone-id',
+    'resolution',
+    'resolution-description',
+    'severity-id',
+    'version-id',
+  );
+
+  protected static function getFieldInfo() {
+    self::$fields = array(
+      'assignee-id'                         => t('User Id'),
+      'component-id'                        => t('The project component (unused)'),
+      'created-at'                          => t('Ticket creation timestamp'),
+      'description'                         => t('Description'),
+      'description-format'                  => t('The ticket body format - does not match Drupal formats'),
+      'due-on'                              => t('Timestamp the ticket should be resolved by'),
+    /*
+      'fieldn-value-id' => t('The value for custom field n'),
+     */
+      'hours-estimate-current'              => t('The current estimate for ticket resolution'),
+      'hours-estimate-initial'              => t('The initial estimate for ticket resolution'),
+      'milestone-id'                        => t('The milestone for which resolution of this ticket is due'),
+      'number'                              => t('An apparent duplicate of the ticket id'),
+      'priority'                            => t('The priority level for the ticket'),
+      'project-id'                          => t('The project this ticket belongs to'),
+      'reporter-id'                         => t('The user reporting the ticket'),
+      'resolution'                          => t('The way the ticket was resolved'),
+      'resolution-description'              => t('Details about the way the ticket was resolved'),
+      'severity-id'                         => t('The ticket severity level'),
+      'status'                              => t('The ticket status'),
+      'summary'                             => t('The ticket summary'),
+      'updated-at'                          => t('Timestamp of latest ticket change'),
+      'version-id'                          => t('The ticket version'),
+      'slug'                                => t('Virtual: ticket summary, converted to slug'),
+    );
+
+    return self::$fields;
+  }
+
+  public function __construct() {
+    parent::__construct(MigrateGroup::getInstance('Unfuddle'));
+    $item_xpath = '/account/projects/project/tickets/ticket';
+    $items_class = new MigrateItemsXML($this->items_url, $item_xpath, $this->item_ID_xpath);
+    $this->source = new MigrateSourceMultiItems($items_class, self::getFieldInfo());
+    $source_key = array(
+      'id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      )
+    );
+
+    $this->dependencies = array('UnfuddlePeople', 'UnfuddleProject');
+    $this->destination = new MigrateDestinationNodeCase();
+    $this->map = new MigrateSQLMap('unfuddle_ticket',
+      $source_key,
+      MigrateDestinationNode::getKeySchema());
+
+
+    $this->addXPathFieldMapping('assign_to',        'assignee-id')
+      ->sourceMigration('UnfuddlePeople');
+    $this->addXPathFieldMapping('created',          'created-at');
+    $this->addXPathFieldMapping('body',             'description')
+      ->description(t('Applying site default input format: will often be wrong'));
+    $this->addXPathFieldMapping('teaser',   node_teaser('description', FILTER_FORMAT_DEFAULT))
+      ->description(t('Applying site default input format: will often be wrong'));
+    $this->addXPathFieldMapping('case_priority_id', 'priority');
+    $this->addXPathFieldMapping('pid',              'project-id')
+      ->sourceMigration('UnfuddleProject');
+    $this->addXPathFieldMapping('uid',              'reporter-id')
+      ->sourceMigration('UnfuddlePeople');
+    $this->addXPathFieldMapping('revision_uid',     'reporter-id')
+      ->sourceMigration('UnfuddlePeople');
+    $this->addXPathFieldMapping('case_status_id',   'status');
+    $this->addXPathFieldMapping('case_number',      'number');
+    $this->addXPathFieldMapping('title',            'summary');
+    $this->addXPathFieldMapping('path',             'slug'); // slugged
+    $this->addXPathFieldMapping('changed',          'updated-at');
+
+    $this->addFieldMapping('case_type_id', NULL);
+
+    $this->afterConstruct();
   }
 
   public function preparerow(stdClass $row) {
+    /*
     $row->xml->{'archived'} = ((int) $row->xml->{'archived'}) ? 0 : 1;
     $row->xml->{'short-name'} = 'project/'. $row->xml->{'short-name'};
+    */
+    $row->xml->{'priority'} = 2; // "Normal"
+
+    $status = (string) $row->xml->{'status'};
+    $ct_status_map = array(
+      'Open'      => 4,
+      'Resolved'  => 5,
+      'Deferred'  => 6, // Is not a status in Unfuddle
+      'Duplicate' => 7, // Is not a status in Unfuddle
+      'Closed'    => 8,
+    );
+
+    $u_status_map = array(
+      'fixed'      => $ct_status_map['Resolved'],
+      'closed'     => $ct_status_map['Closed'],
+      'reopened'   => $ct_status_map['Open'],
+      'reassigned' => $ct_status_map['Open'],
+      'new'        => $ct_status_map['Open'],
+      'accepted'   => $ct_status_map['Open'],
+    );
+  $row->xml->{'status'} = isset($u_status_map[$status])
+    ? $u_status_map[$status]
+    : $ct_status_map['Open'];
+
+  $row->xml->{'slug'} = 'case/'. drupal_strtolower(drupal_clean_css_identifier((string) $row->xml->{'summary'}));
   }
 }

+ 21 - 1
migrateunfuddle.module

@@ -15,4 +15,24 @@ function migrateunfuddle_migrate_api() {
 
   // dsm(get_defined_vars(), __FUNCTION__);
   return $api;
-}
+}
+
+/**
+ * Lifted from Drupal 7
+ */
+function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_' => '-', '/' => '-', '[' => '-', ']' => '')) {
+  // By default, we filter using Drupal's coding standards.
+  $identifier = strtr($identifier, $filter);
+
+  // Valid characters in a CSS identifier are:
+  // - the hyphen (U+002D)
+  // - a-z (U+0030 - U+0039)
+  // - A-Z (U+0041 - U+005A)
+  // - the underscore (U+005F)
+  // - 0-9 (U+0061 - U+007A)
+  // - ISO 10646 characters U+00A1 and higher
+  // We strip out any character not in the above list.
+  $identifier = preg_replace('/[^\x{002D}\x{0030}-\x{0039}\x{0041}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]/u', '', $identifier);
+
+  return $identifier;
+}