Статьи

Перенос данных из FogBugz в TFS 2012 с использованием платформы интеграции TFS

В рамках моей текущей работы я буду перемещать данные из FogBugz с помощью специального CSV-адаптера для платформы интеграции TFS. Этот адаптер я написал некоторое время назад, чтобы облегчить перемещение данных из Excel в TFS, и я просто хочу использовать его повторно. Первое, что мне нужно, это среда разработки, так как мне может понадобиться настроить этот древний код.

Этот пост является частью серии постов, в которых описывается обновление TFS 2010 до TFS 2012 с миграцией VSS, консолидацией шаблонов процессов, консолидацией командных проектов и миграцией FogBugz:

  1. Часть 1. Обновление TFS 2010 до TFS 2012 с миграцией VSS и консолидацией шаблонов процессов
    1. VSS Converter — выпуск: TF60014 и TF60087: не удалось инициализировать маппер пользователя
    2. VSS Converter — выпуск: TF54000: невозможно обновить данные, поскольку часы сервера могли быть установлены неправильно
  2. Часть 2: одна коллекция командных проектов, чтобы управлять ими всеми — консолидация командных проектов
    1. Инструменты интеграции TFS — проблема: доступ к программным файлам запрещен
    2. Средства интеграции TFS — проблема: ошибка возникла во время проверки кода группы изменений
    3. Инструменты интеграции TFS — проблема: «невозможно найти уникальный локальный путь»
    4. TFS 2012 Проблема: Get Workspace уже существует, соединяясь с VS 2008 или VS 2010
  3. Часть 3. Перенос данных из FogBugz в TFS 2012 с использованием платформы интеграции TFS
    1. Инструменты интеграции TFS — проблема: AnalysisProvider не найден
    2. Инструменты интеграции TFS: TF237165: Team Foundation Server не удалось обновить рабочий элемент


Рисунок: завершение консолидации данных

Мы завершили обновление до TFS 2012 и консолидировали шаблоны процессов, а затем объединили все командные проекты в единую коллекцию, и теперь последняя часть этого марафона веселья — собрать все существующие данные FogBugz в TFS. Мы могли бы предположительно использовать простой импорт в Excel, но это оставляет нас открытыми для кошмара времени, через который проходят все переходы между состояниями.

Настройка среды разработки для настраиваемого адаптера для платформы интеграции TFS

Не используйте руководство рейнджера для этого. Этот путь причиняет боль и страдания, поскольку предполагает, что вам нужно иметь возможность компилировать всю интеграционную платформу TFS. Я просто установил нужные биты, так что нам нужно. Ну, для начала нам нужно установить пару вещей:

  • Windows Sever 2012
  • SQL Server 2012
  • Visual Studio 2012 Team Foundation Server
  • Visual Studio 2012 Ultimate
  • Платформа интеграции TFS

 

SNAGHTML3073bc7
Рисунок: убедитесь, что вы снимок

Поскольку я всегда использую виртуальную машину для разработки, я использую возможности моментальных снимков, чтобы поддерживать чистоту и работу своей среды. Улыбка

Далее вам нужно создать решение и проект для хранения вашего собственного адаптера. Используйте любой макет, который вы хотите, но убедитесь, что ваш проект имеет следующие ссылки:

  • Microsoft.TeamFoundation.Migration.EntityModel.dll
  • Microsoft.TeamFoundation.Migration.Toolkit.dll

И то, и другое вы найдете в «C: \ Program Files (x86) \ Средства интеграции с Microsoft Team Foundation Server \». Но для того, чтобы пройти цикл отладки (на платформе интеграции TFS нет модульных тестов), вам нужно немного поработать.

  1. Выберите «Проект правой кнопкой мыши | Компилировать | Строить события »

    образ
    Рисунок: открыть свойства проекта

  2. Затем в событиях после сборки введите несколько операторов xcopy.

    образ
    Рисунок: редактирование событий сборки

xcopy "$(TargetDir)$(TargetName)*" "$(SolutionDir)..\Binaries\MyAdapter\Plugins\*" /y
xcopy "$(ProjectDir)Configuration\*" "$(SolutionDir)..\Binaries\MyAdapter\Configurations\*" /y /s
xcopy "$(SolutionDir)..\Binaries\MyAdapter\*" "%ProgramFiles(x86)%\Microsoft Team Foundation Server Integration Tools\*" /y /s
  1. Рисунок: Добавьте несколько операторов xcopy 

     

  2. Выберите вкладку отладки

    образ
    Рисунок:

  3. Выберите «Запустить внешнюю программу» и введите путь к консоли миграции
C:\Program Files (x86)\Microsoft Team Foundation Server Integration Tools\MigrationConsole.exe
  1. Добавьте аргумент командной строки файла конфигурации xml для запуска
  2. Добавить рабочий каталог
C:\Program Files (x86)\Microsoft Team Foundation Server Integration Tools\

Теперь, когда вы отлаживаете свою библиотеку классов, она откроет MigrationConsole.exe с правильной конфигурацией теста и подключится к процессу, что позволит вам пройтись по коду.

Настройка вашего клиентского адаптера для запуска

Если вы правильно настроили вышеуказанные шаги, то все, что вам нужно, это выбрать правильный файл конфигурации из пользовательского интерфейса, и он предварительно заполнит все значения для вас.

образ
Рисунок: выберите файл конфигурации для вашего адаптера

Для достижения этой цели в файле конфигурации есть некоторый интересный код и способ установки значений по умолчанию.

< ?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<configuration uniqueid="191B2CD3-CC5F-4433-ADA6-D2FB4C305969" friendlyname="Northwest Cadence CSV to TFS">
  <providers>
    <provider referencename="06A2457F-EBBA-4979-BC5F-0F5006B8B4E6" friendlyname="Northwest Cadence CSV Adapter">
    <provider referencename="b84b30dd-1496-462a-bd9d-5a078a617779" friendlyname="TFS 11 Migration WIT Provider">
  </provider></provider></providers>
  <sessiongroup friendlyname="CSV to TFS Sync Sessions" sessiongroupguid="DE6896E7-22E1-4618-A70B-CD36E18EC9D6">
    <workflowtype frequency="ContinuousManual" directionofflow="Unidirectional" synccontext="Disabled">
    <migrationsources>
      <migrationsource internaluniqueid="BB2BD2C6-92B5-4817-AB51-A087B6532F0D" friendlyname="Northwest Cadence CSV Adapter" serveridentifier="Server identifier" serverurl="DatabaseSetName" sourceidentifier="c:\temp\FBMigrationSample.csv" endpointsystemname="CSV" providerreferencename="06A2457F-EBBA-4979-BC5F-0F5006B8B4E6">
        <settings>
          <useridentitylookup>
        </useridentitylookup></settings>
        <customsettings>
          <customsetting settingkey="FieldDelimiter" settingvalue="~#~,">
          <customsetting settingkey="RowDelimiter" settingvalue="~#~,###">
          <customsetting settingkey="IdColumn" settingvalue="WorkItemId">
          <customsetting settingkey="DeltaDateColumn" settingvalue="LastChangedDate">
          <customsetting settingkey="ChangedByColumn" settingvalue="OpendedBy">
          <customsetting settingkey="WorkItemTypeColumn" settingvalue="Type">
        </customsetting></customsetting></customsetting></customsetting></customsetting></customsetting></customsettings>
      </migrationsource>
      <!--  TFS WIT migration source   -->
      <migrationsource internaluniqueid="F4741818-F14C-458F-BB20-4BBAC20F95C3" friendlyname="kraken (WIT)" serveridentifier="6410f982-788f-4c55-9ac9-02519482c510" serverurl="http://kraken:8080/tfs/defaultcollection" sourceidentifier="TestProject1" providerreferencename="b84b30dd-1496-462a-bd9d-5a078a617779" endpointsystemname="TFS">
        <customsettings>
          <customsetting settingkey="EnableBypassRuleDataSubmission" settingvalue="True">
          <customsetting settingkey="DisableAreaPathAutoCreation" settingvalue="False">
          <customsetting settingkey="DisableIterationPathAutoCreation" settingvalue="False">
        </customsetting></customsetting></customsetting></customsettings>
      </migrationsource>
    </migrationsources>
    <sessions>
      <session sessionuniqueid="882d715f-214a-4901-aefb-a309ed4a8bd2" friendlyname="Work Item Tracking Session" leftmigrationsourceuniqueid="BB2BD2C6-92B5-4817-AB51-A087B6532F0D" rightmigrationsourceuniqueid="F4741818-F14C-458F-BB20-4BBAC20F95C3" sessiontype="WorkItemTracking">
        <eventsinks>
 
        <customsettings>
          <settingxml>
            <witsessioncustomsetting>
              <workitemtypes>
                <workitemtype leftworkitemtypename="Feature" rightworkitemtypename="Product Backlog Item" fieldmap="DefaultFieldMap">
                <workitemtype leftworkitemtypename="Inquiry" rightworkitemtypename="Product Backlog Item" fieldmap="DefaultFieldMap">
                <workitemtype leftworkitemtypename="Bug" rightworkitemtypename="Bug" fieldmap="BugFieldMap">
                <workitemtype leftworkitemtypename="Work Item" rightworkitemtypename="Product Backlog Item" fieldmap="DefaultFieldMap">
                <workitemtype leftworkitemtypename="Schedule Item" rightworkitemtypename="Product Backlog Item" fieldmap="DefaultFieldMap">
                <workitemtype leftworkitemtypename="Task" rightworkitemtypename="Task" fieldmap="TaskFieldMap">
              </workitemtype></workitemtype></workitemtype></workitemtype></workitemtype></workitemtype></workitemtypes>
              <fieldmaps>
                <fieldmap name="DefaultFieldMap">
                  <mappedfields>
                    <mappedfield leftname="Description" rightname="System.Description" mapfromside="Left" valuemap="">
                    <mappedfield leftname="History" rightname="System.History" mapfromside="Left">
                    <mappedfield leftname="AssigedTo" rightname="System.AssignedTo" mapfromside="Left" valuemap="AssigedToValueMap">
                    <mappedfield leftname="Status" rightname="System.State" mapfromside="Left" valuemap="PbiStatusValueMap">
                    <mappedfield leftname="@@MISSINGFIELD@@" rightname="System.Reason" mapfromside="Left" valuemap="PbiReasonValueMap">
                    <mappedfield leftname="Priority" rightname="Microsoft.VSTS.Common.BacklogPriority" mapfromside="Left" valuemap="PriorityValueMap">
                    <mappedfield leftname="EstimatedPoints" rightname="Microsoft.VSTS.Scheduling.Effort" mapfromside="Left" valuemap="">
                    <mappedfield leftname="OwningDepartment" rightname="Efinancial.Department" mapfromside="Left" valuemap="OwningDepartmentValueMap">
                    <!--                                     
                    <MappedField LeftName="EstHours" RightName="" MapFromSide="Left" valueMap="" />
                    <mappedfield LeftName="LeftHours" RightName="" MapFromSide="Left" valueMap="" />
                    <mappedfield LeftName="SpentHours" RightName="" MapFromSide="Left" valueMap="" />
                    -->
                  </mappedfield></mappedfield></mappedfield></mappedfield></mappedfield></mappedfield></mappedfield></mappedfield></mappedfields>
                  <aggregatedfields>
                    <fieldsaggregationgroup mapfromside="Left" targetfieldname="System.AreaPath" format="FogBugz\{0}\{1}">
                      <sourcefield index="0" sourcefieldname="Project" valuemap="">
                      <sourcefield index="1" sourcefieldname="Area" valuemap="AreaValueMap">
                    </sourcefield></sourcefield></fieldsaggregationgroup>
                    <fieldsaggregationgroup mapfromside="Left" targetfieldname="System.Title" format="{0}: {1}">
                      <sourcefield index="0" sourcefieldname="Type" valuemap="">
                      <sourcefield index="1" sourcefieldname="Title" valuemap="">
                    </sourcefield></sourcefield></fieldsaggregationgroup>
                  </aggregatedfields>
                </fieldmap>
                <fieldmap name="BugFieldMap">
                  <mappedfields>
                    <mappedfield leftname="Description" rightname="System.Description" mapfromside="Left" valuemap="">
                    <mappedfield leftname="History" rightname="System.History" mapfromside="Left">
                    <mappedfield leftname="AssigedTo" rightname="System.AssignedTo" mapfromside="Left" valuemap="AssigedToValueMap">
                    <mappedfield leftname="Status" rightname="System.State" mapfromside="Left" valuemap="PbiStatusValueMap">
                    <mappedfield leftname="@@MISSINGFIELD@@" rightname="System.Reason" mapfromside="Left" valuemap="PbiReasonValueMap">
                    <mappedfield leftname="Priority" rightname="Microsoft.VSTS.Common.Severity" mapfromside="Left" valuemap="PriorityBugValueMap">
                    <mappedfield leftname="@@MISSINGFIELD@@" rightname="Microsoft.VSTS.Common.BacklogPriority" mapfromside="Left" valuemap="PriorityToBacklogValueMap">
                    <mappedfield leftname="EstimatedPoints" rightname="Microsoft.VSTS.Scheduling.Effort" mapfromside="Left" valuemap="">
                    <mappedfield leftname="OwningDepartment" rightname="Efinancial.Department" mapfromside="Left" valuemap="OwningDepartmentValueMap">
                    <!--
                    <MappedField LeftName="EstHours" RightName="" MapFromSide="Left" valueMap="" />
                    <mappedfield LeftName="LeftHours" RightName="" MapFromSide="Left" valueMap="" />
                    <mappedfield LeftName="SpentHours" RightName="" MapFromSide="Left" valueMap="" />
                    -->
                  </mappedfield></mappedfield></mappedfield></mappedfield></mappedfield></mappedfield></mappedfield></mappedfield></mappedfield></mappedfields>
                  <aggregatedfields>
                    <fieldsaggregationgroup mapfromside="Left" targetfieldname="System.AreaPath" format="FogBugz\{0}\{1}">
                      <sourcefield index="0" sourcefieldname="Project" valuemap="">
                      <sourcefield index="1" sourcefieldname="Area" valuemap="AreaValueMap">
                    </sourcefield></sourcefield></fieldsaggregationgroup>
                    <fieldsaggregationgroup mapfromside="Left" targetfieldname="System.Title" format="{0}: {1}">
                      <sourcefield index="0" sourcefieldname="Type" valuemap="">
                      <sourcefield index="1" sourcefieldname="Title" valuemap="">
                    </sourcefield></sourcefield></fieldsaggregationgroup>
                  </aggregatedfields>
                </fieldmap>
                <fieldmap name="TaskFieldMap">
                  <mappedfields>
                    <mappedfield leftname="Description" rightname="System.Description" mapfromside="Left" valuemap="">
                    <mappedfield leftname="History" rightname="System.History" mapfromside="Left">
                    <mappedfield leftname="AssigedTo" rightname="System.AssignedTo" mapfromside="Left" valuemap="AssigedToValueMap">
                    <mappedfield leftname="Status" rightname="System.State" mapfromside="Left" valuemap="TaskStatusValueMap">
                    <mappedfield leftname="@@MISSINGFIELD@@" rightname="System.Reason" mapfromside="Left" valuemap="TaskReasonValueMap">
                    <mappedfield leftname="Priority" rightname="Microsoft.VSTS.Common.BacklogPriority" mapfromside="Left" valuemap="PriorityValueMap">
                    <mappedfield leftname="LeftHours" rightname="Microsoft.VSTS.Scheduling.RemainingWork" mapfromside="Left" valuemap="">
                    <!--                                     
                    <MappedField LeftName="EstHours" RightName="" MapFromSide="Left" valueMap="" />
                    <mappedfield LeftName="LeftHours" RightName="" MapFromSide="Left" valueMap="" />
                    <mappedfield LeftName="SpentHours" RightName="" MapFromSide="Left" valueMap="" />
                    -->
                  </mappedfield></mappedfield></mappedfield></mappedfield></mappedfield></mappedfield></mappedfield></mappedfields>
                  <aggregatedfields>
                    <fieldsaggregationgroup mapfromside="Left" targetfieldname="System.AreaPath" format="FogBugz\{0}\{1}">
                      <sourcefield index="0" sourcefieldname="Project" valuemap="">
                      <sourcefield index="1" sourcefieldname="Area" valuemap="AreaValueMap">
                    </sourcefield></sourcefield></fieldsaggregationgroup>
                    <fieldsaggregationgroup mapfromside="Left" targetfieldname="System.Title" format="{0}: {1}">
                      <sourcefield index="0" sourcefieldname="Type" valuemap="">
                      <sourcefield index="1" sourcefieldname="Title" valuemap="">
                    </sourcefield></sourcefield></fieldsaggregationgroup>
                  </aggregatedfields>
                </fieldmap>
              </fieldmaps>
              <valuemaps>
                <valuemap name="AreaValueMap">
                  <value leftvalue="*" rightvalue="*">
                  <value leftvalue="Forms / App Pages" rightvalue="Forms - App Pages">
                </value></value></valuemap>
                <valuemap name="OpendedByValueMap">
                  <value leftvalue="*" rightvalue="*">
                  <value leftvalue="Unassigned" rightvalue="">
                </value></value></valuemap>
                <valuemap name="AssigedToValueMap">
                  <value leftvalue="*" rightvalue="*">
                  <value leftvalue="Unassigned" rightvalue="">
                </value></value></valuemap>
                <valuemap name="PriorityValueMap">
                  <value leftvalue="--" rightvalue="5">
                  <value leftvalue="P4" rightvalue="4">
                  <value leftvalue="P2" rightvalue="2">
                  <value leftvalue="P3" rightvalue="3">
                  <value leftvalue="P1" rightvalue="1">
                </value></value></value></value></value></valuemap>
                <valuemap name="PriorityBugValueMap">
                  <value leftvalue="*" rightvalue="4 - Low">
                  <value leftvalue="P4" rightvalue="4 - Low">
                  <value leftvalue="P2" rightvalue="2 - High">
                  <value leftvalue="P3" rightvalue="3 - Medium">
                  <value leftvalue="P1" rightvalue="1 - Critical">
                </value></value></value></value></value></valuemap>
                <valuemap name="PriorityToBacklogValueMap">
                  <value leftvalue="" rightvalue="5">
                    <when conditionalsrcfieldname="Priority" conditionalsrcfieldvalue="*">
                  </when></value>
                  <value leftvalue="" rightvalue="4">
                    <when conditionalsrcfieldname="Priority" conditionalsrcfieldvalue="P4">
                  </when></value>
                  <value leftvalue="" rightvalue="2">
                    <when conditionalsrcfieldname="Priority" conditionalsrcfieldvalue="P2">
                  </when></value>
                  <value leftvalue="" rightvalue="3">
                    <when conditionalsrcfieldname="Priority" conditionalsrcfieldvalue="P3">
                  </when></value>
                  <value leftvalue="" rightvalue="1">
                    <when conditionalsrcfieldname="Priority" conditionalsrcfieldvalue="P1">
                  </when></value>
                </valuemap>
 
                <valuemap name="OwningDepartmentValueMap">
                  <value leftvalue="*" rightvalue="">
                  <value leftvalue="Department1" rightvalue="Department1">
                  <value leftvalue="Department2" rightvalue="Department2">
                  <value leftvalue="department2" rightvalue="Department2">
                </value></value></value></value></valuemap>
 
                <valuemap name="PbiStatusValueMap">
                  <value leftvalue="*" rightvalue="New">
                  <value leftvalue="Planning" rightvalue="New">
                  <value leftvalue="Active" rightvalue="Committed">
                  <value leftvalue="Resolved (Not Reproducible)" rightvalue="Removed">
                  <value leftvalue="Resolved (Duplicate)" rightvalue="Removed">
                  <value leftvalue="Resolved (Responded)" rightvalue="Done">
                  <value leftvalue="Resolved (Fixed)" rightvalue="Done">
                  <value leftvalue="Resolved (Implemented)" rightvalue="Done">
                  <value leftvalue="Resolved (Completed)" rightvalue="Done">
                  <value leftvalue="Resolved (By Design)" rightvalue="Removed">
                </value></value></value></value></value></value></value></value></value></value></valuemap>
                <valuemap name="PbiReasonValueMap">
                  <value leftvalue="" rightvalue="Planning">
                    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Planning">
                  </when></value>
                  <value leftvalue="" rightvalue="Active">
                    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Active">
                  </when></value>
                  <value leftvalue="" rightvalue="Not Reproducible">
                    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (Not Reproducible)">
                  </when></value>
                  <value leftvalue="" rightvalue="Duplicate">
                    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (Duplicate)">
                  </when></value>
                  <value leftvalue="" rightvalue="Responded">
                    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (Responded)">
                  </when></value>
                  <value leftvalue="" rightvalue="Fixed">
                    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (Fixed)">
                  </when></value>
                  <value leftvalue="" rightvalue="Implemented">
                    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (Implemented)">
                  </when></value>
                  <value leftvalue="" rightvalue="Completed">
                    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (Completed)">
                  </when></value>
                  <value leftvalue="" rightvalue="By Design">
                    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (By Design)">
                  </when></value>
                </valuemap>
 
                <valuemap name="TaskStatusValueMap">
                  <value leftvalue="Planning" rightvalue="ToDo">
                  <value leftvalue="Active" rightvalue="To Do">
                  <value leftvalue="Resolved (Not Reproducible)" rightvalue="Removed">
                  <value leftvalue="Resolved (Duplicate)" rightvalue="Removed">
                  <value leftvalue="Resolved (Responded)" rightvalue="Done">
                  <value leftvalue="Resolved (Fixed)" rightvalue="Done">
                  <value leftvalue="Resolved (Implemented)" rightvalue="Done">
                  <value leftvalue="Resolved (Completed)" rightvalue="Done">
                  <value leftvalue="Resolved (By Design)" rightvalue="Removed">
                </value></value></value></value></value></value></value></value></value></valuemap>
                <valuemap name="TaskReasonValueMap">
                  <value leftvalue="" rightvalue="Planning">
                    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Planning">
                  </when></value>
                  <value leftvalue="" rightvalue="Active">
                    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Active">
                  </when></value>
                  <value leftvalue="*" rightvalue="Not Reproducible">
                    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (Not Reproducible)">
                  </when></value>
                  <value leftvalue="*" rightvalue="Duplicate">
                    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (Duplicate)">
                  </when></value>
                  <value leftvalue="*" rightvalue="Responded">
                    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (Responded)">
                  </when></value>
                  <value leftvalue="*" rightvalue="Fixed">
                    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (Fixed)">
                  </when></value>
                  <value leftvalue="*" rightvalue="Implemented">
                    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (Implemented)">
                  </when></value>
                  <value leftvalue="*" rightvalue="Completed">
                    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (Completed)">
                  </when></value>
                  <value leftvalue="*" rightvalue="By Design">
                    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (By Design)">
                  </when></value>
                </valuemap>
 
              </valuemaps>
            </witsessioncustomsetting>
          </settingxml>
        </customsettings>
        <filters>
          <filterpair neglect="false">
            <filteritem migrationsourceuniqueid="BB2BD2C6-92B5-4817-AB51-A087B6532F0D" filterstring="MIGRATEONLY">
            <filteritem migrationsourceuniqueid="F4741818-F14C-458F-BB20-4BBAC20F95C3" filterstring="[System.Id] = 0">
          </filteritem></filteritem></filterpair>
        </filters>
      </eventsinks></session>
    </sessions>
    <linking>
      <customsettings>
      <linktypemappings>
    </linktypemappings></customsettings></linking>
  </workflowtype></sessiongroup>
</configuration>

Рисунок: файлы конфигурации могут быть очень подробными

Этот файл представляет данные, которые должны быть сопоставлены. Все, что делает адаптер CSV, — это передает данные в платформу интеграции TFS, а файл конфигурации выполняет всю тяжелую работу по отображению данных в TFS. Если вы заметили наши сумасшедшие EOL и разделитель, вы также должны заметить, что у нас есть большое количество истории и описания, которые могут содержать обычные разделители, поэтому нам нужно что-то, что вряд ли будет использоваться.

образ
Рисунок: теперь он будет отображать ваш адаптер на левой стороне

Теперь, когда вы можете выбрать адаптер, мы готовы качаться …

Построение конфигурации: сопоставление состояний

Настоящее преимущество по сравнению с Excel заключается в том, что мы можем обходить правила API и записывать данные непосредственно в TFS. Это позволяет нам записывать данные непосредственно в определенное состояние, даже если это состояние не существует. Несмотря на то, что в большинстве случаев это плохо, это очень важно при переносе данных, в противном случае нам нужно пройти рабочий элемент по его состояниям, чтобы получить «Готово» или «Закрыто».

образ
Рисунок: некоторые данные могут потребовать обновления, чтобы включить сохранение

Чтобы помочь с этим, я создал файл сопоставления, но на самом деле это не так просто. Нам нужно подумать о нескольких вещах как о части картирования. Что такое старые штаты, когда новые и сколько они совпадают.

<valuemap name="PbiStatusValueMap">
  <value leftvalue="*" rightvalue="New">
  <value leftvalue="Planning" rightvalue="New">
  <value leftvalue="Active" rightvalue="Committed">
  <value leftvalue="Resolved (Not Reproducible)" rightvalue="Removed">
  <value leftvalue="Resolved (Duplicate)" rightvalue="Removed">
  <value leftvalue="Resolved (Responded)" rightvalue="Done">
  <value leftvalue="Resolved (Fixed)" rightvalue="Done">
  <value leftvalue="Resolved (Implemented)" rightvalue="Done">
  <value leftvalue="Resolved (Completed)" rightvalue="Done">
  <value leftvalue="Resolved (By Design)" rightvalue="Removed">
</value></value></value></value></value></value></value></value></value></value></valuemap>
<valuemap name="PbiReasonValueMap">
  <value leftvalue="" rightvalue="Planning">
    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Planning">
  </when></value>
  <value leftvalue="" rightvalue="Active">
    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Active">
  </when></value>
  <value leftvalue="" rightvalue="Not Reproducible">
    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (Not Reproducible)">
  </when></value>
  <value leftvalue="" rightvalue="Duplicate">
    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (Duplicate)">
  </when></value>
  <value leftvalue="" rightvalue="Responded">
    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (Responded)">
  </when></value>
  <value leftvalue="" rightvalue="Fixed">
    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (Fixed)">
  </when></value>
  <value leftvalue="" rightvalue="Implemented">
    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (Implemented)">
  </when></value>
  <value leftvalue="" rightvalue="Completed">
    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (Completed)">
  </when></value>
  <value leftvalue="" rightvalue="By Design">
    <when conditionalsrcfieldname="Status" conditionalsrcfieldvalue="Resolved (By Design)">
  </when></value>
</valuemap>

Рисунок: Отображение из штата в штат

Отображения состояний проще всего испортить, так что не торопитесь с ними. Мне нравится, когда все мои рабочие элементы находятся в рабочем состоянии, но некоторые клиенты либо не заботятся, либо не могут решить, каким должно быть отображение. Не запутайтесь в добавлении большого количества избыточных состояний в TFS…. почему вы можете спросить?

Что ж, если вы меняете состояния или рабочий процесс для состояний, которые вы принимаете, это немного больше, чем просто обновление состояний. О чем отчеты Reporting Services, которые полагаются на них, или отчеты Excel, или даже запросы. От состояний зависит многое, и при их изменении вы берете на себя все это обслуживание…

Я не говорю, что вы не должны менять штаты (когда на самом деле я), но вы должны учитывать все последствия.

При импорте данных из другой системы у меня всегда есть данные, которые я хотел бы получить как часть областей и итераций, но получить решение о формате и отображении в то время было бы довольно сложно, поэтому я склоняюсь к префиксу всех областей и итераций с именем система, из которой я получаю данные. После этого клиент может переместиться и переставить факт.

<aggregatedfields>
  <fieldsaggregationgroup mapfromside="Left" targetfieldname="System.AreaPath" format="FogBugz\{0}\{1}">
    <sourcefield index="0" sourcefieldname="Project" valuemap="">
    <sourcefield index="1" sourcefieldname="Area" valuemap="AreaValueMap">
  </sourcefield></sourcefield></fieldsaggregationgroup>
  <fieldsaggregationgroup mapfromside="Left" targetfieldname="System.Title" format="{0}: {1}">
    <sourcefield index="0" sourcefieldname="Type" valuemap="">
    <sourcefield index="1" sourcefieldname="Title" valuemap="">
  </sourcefield></sourcefield></fieldsaggregationgroup>
</aggregatedfields>

Рисунок: агрегированные поля

Для этого вы можете использовать функцию Aggregate fields и объединить одно или несколько полей. Они даже поддерживают сопоставления, так что вы можете выполнять некоторую очистку или манипуляции во время прохождения.

Экспорт данных FogBugz в CSV

Это оказалось случаем SQL Jujitzu и даже лучше, мне не пришлось это делать. Мой клиент предоставил мне полный файл CSV с пользовательским разделителем, чтобы я мог импортировать его в TFS.

Мне пришлось сделать кучу изменений в моем адаптере CSV, так как раньше я его не получал. Теперь, когда он работает, он будет работать для импорта любых табличных данных с разделителями. Помните, что это адаптер Tip и напрямую не импортирует историю, однако знает об изменениях. Если вы напишете обновленный файл, в котором поле «DeltaDateColumn» было изменено, данные для этого рабочего элемента будут повторно применены поверх старого (да, это заменит все изменения), но это допускает поэтапную миграцию.

Что-то, что мы делали, это преобразование данных как часть вывода. Мы эффективно создали временную таблицу для хранения данных, загрузили и обработали данные в этом формате для экспорта в CSV, но вы можете просто сделать дамп данных в зависимости от ваших требований…

Импортирование Иерархии

Адаптер CSV по умолчанию не поддерживает иерархию. Возможно, но я не исследовал эту часть платформы интеграции TFS, поэтому я просто написал небольшую утилиту командной строки для импорта из CSV.

Console.WriteLine(string.Format("Connecting to {0}", args[0]));
TfsTeamProjectCollection tfs = new TfsTeamProjectCollection(new Uri(args[0]));
 
Console.WriteLine(string.Format("Loading {0}", @"c:\temp\ParentChild.csv"));
StreamReader SR = new StreamReader(@"c:\temp\ParentChild.csv");
Dictionary<int ,="" list<int="">> relates = new Dictionary</int><int ,="" list<int="">>();
while (!SR.EndOfStream)
{
    string rowstring = SR.ReadLine();
    string[] row = rowstring.Split(',');
    if (!relates.ContainsKey(int.Parse(row[1])))
    {
        relates.Add(int.Parse(row[1]), new List</int><int>());
    }
    relates[int.Parse(row[1])].Add(int.Parse(row[0]));
}
 
WorkItemStore store = tfs.GetService<workitemstore>();
WorkItemCollection wic = store.Query(@"SELECT [System.Id] FROM WorkItems WHERE [System.AreaPath] UNDER 'TestProject2\FogBugz' ORDER BY [System.Id]");
foreach (WorkItem sourcew in wic)
{
    // TASKS
    if (sourcew.Type.Name == "Task")
    {
        decimal parent = decimal.Parse((string)sourcew.Fields["TfsMigrationTool.ReflectedWorkItemId"].Value);
        parent = decimal.Negate(parent);
 
        WorkItem parentw = store.Query(string.Format(@"SELECT [System.Id] FROM WorkItems WHERE [TfsMigrationTool.ReflectedWorkItemId] = '{0}' ORDER BY [System.Id]", parent))[0];
        parentw.Open();
 
        WorkItemLinkTypeEnd linkTypeEnd = store.WorkItemLinkTypes.LinkTypeEnds["Child"];
        parentw.Links.Add(new RelatedLink(linkTypeEnd, sourcew.Id));
        parentw.Save();
 
        Console.WriteLine(parent);
    }
    else
    {
        int parentIsMe = int.Parse((string)sourcew.Fields["TfsMigrationTool.ReflectedWorkItemId"].Value);
        sourcew.Open();
        Console.WriteLine(string.Format("Processing Parent {0}", parentIsMe));
        if (relates.ContainsKey(parentIsMe))
        {
            foreach (int child in relates[parentIsMe])
            {
                WorkItem childw = store.Query(string.Format(@"SELECT [System.Id] FROM WorkItems WHERE [TfsMigrationTool.ReflectedWorkItemId] = '{0}' ORDER BY [System.Id]", child))[0];
                childw.Open();
 
                WorkItemLinkTypeEnd linkTypeEnd = store.WorkItemLinkTypes.LinkTypeEnds["Child"];
                sourcew.Links.Add(new RelatedLink(linkTypeEnd, childw.Id));
                sourcew.Save();
                Console.WriteLine(string.Format("Adding child if {0} to {1}", childw.Id, parentIsMe));
            }
        }
    }
}
Console.ReadKey();</workitemstore></int>

Рисунок: грубый, но эффективный

По большей части вам даже не нужно знать название проекта, так как все это в идентификаторах.

Импорт данных ForgBugz в TFS

Это самая простая часть сейчас, когда у нас есть формат данных и наша конфигурация, но у нас все еще был кошмар, когда платформа интеграции TFS распознала новый адаптер. Вам нужно убедиться, что вы перезапускаете «Службу Windows», если она у вас запущена, но в остальном все прошло гладко.

Практика делает совершенным Улыбка