/// <summary> /// Creates a work item in memory. /// </summary> /// <param name="title">Work item title.</param> public int CreateWorkItem(string title) { var workItem = new InMemoryWorkItem { Title = title }; this.workItems.Add(workItem); return(workItem.Id); }
/// <inheritdoc/> public TfsWorkItem GetWorkItemForIssue(string issueId, int issueActivityCount) { bool isNew = false; if (string.IsNullOrEmpty(issueId)) { throw new ArgumentNullException(nameof(issueId)); } if (this.parentWorkItemId == -1) { throw new Exception("Please run ConfigureAsync first."); } var hasChange = false; var parentWorkItem = this.workItems.Single(w => w.Id == this.parentWorkItemId); string[] commentParts = null; var workItemLink = parentWorkItem.Links.FirstOrDefault(l => { if (l.Comment.StartsWith(issueId)) { commentParts = l.Comment.Split(':'); return(true); } return(false); }); InMemoryWorkItem workItem = null; if (workItemLink != null) { if (!commentParts[1].Equals(issueActivityCount.ToString(CultureInfo.InvariantCulture))) { hasChange = true; } workItem = this.workItems.Single(w => w.Id == workItemLink.RelatedWorkItemId); } else { workItem = new InMemoryWorkItem(); hasChange = true; isNew = true; } var issueSignature = $"{issueId}:{issueActivityCount}"; var imtw = new InMemoryTfsWorkItem(workItem) { HasChange = hasChange, IssueSignature = issueSignature }; imtw.IsNew = isNew; return(imtw); }
/// <summary> /// Process all the history revisions /// </summary> /// <param name="sourceWIId"></param> /// <param name="imWorkItem"></param> /// <param name="setMigStatusDone"></param> /// <returns></returns> public bool WriteHistoryItems(string sourceWIId, InMemoryWorkItem imWorkItem, bool setMigStatusDone) { m_revision = m_currentWorkItem.Rev - 1; m_vstsWorkItem.Id = m_currentWorkItem.Id; m_vstsWorkItem.areaId = m_currentWorkItem.AreaId; m_sourceWorkItemId = sourceWIId; string currentMigStatus = (string)m_currentWorkItem[m_migrationStatusFieldName]; int revCount = 0; bool isMigStatusInt = int.TryParse(currentMigStatus, out revCount); Debug.Assert(isMigStatusInt); // should be able to parse always.. otherwise a code bug // process all history revisions and save in each call for (int historyIdx = 0; historyIdx < imWorkItem.HistoryItems.Count; historyIdx++) { // create new webs service call xml fragment WSHelper webServiceHelper = new WSHelper(WSHelper.WebServiceType.UpdateWorkItem); // set the required attributes webServiceHelper.SetWorkItemAndRevision(m_currentWorkItem.Id, ++m_revision); InMemoryHistoryItem imHistoryItem = (InMemoryHistoryItem)imWorkItem.HistoryItems[historyIdx]; if (historyIdx == imWorkItem.HistoryItems.Count - 1 && // last history item imWorkItem.Attachments.Count == 0 && // no attachments imWorkItem.Links.Count == 0 && // no links setMigStatusDone) // caller asked for it { // last item.. set mig status to done ProcessRevision(webServiceHelper, imHistoryItem.UpdatedView, "Done"); } else { ProcessRevision(webServiceHelper, imHistoryItem.UpdatedView, (++revCount).ToString()); } try { webServiceHelper.Save(); } finally { } } if (imWorkItem.Attachments.Count > 0 || imWorkItem.Links.Count > 0) { return(ProcessAttachmentsAndLinks(imWorkItem, setMigStatusDone)); } return(true); }
} // end of CQEntityRec CTor /// <summary> /// Populate the current record from CQ if its not already in Currituck /// and also process all its references (recursively), Links, History and Attachments /// Else just sets the currituck id for future reference /// </summary> public bool Populate() { bool partiallyMigrated = false; // first check if it exists in the memory cache CQEntity currentEntityRecords = m_cqParams.entityRecords[m_entityName]; CQEntityRec lookupEntity = currentEntityRecords.FindEntityRec(m_entityName, m_dbid); if (lookupEntity != null) { // record already populated.. Logger.Write(LogSource.CQ, TraceLevel.Verbose, "Already populated record '{0}' for Entity '{1}', DBID:{2}", lookupEntity.SourceId, lookupEntity.EntityName, lookupEntity.DBID); return(true); } m_CQEntity = CQWrapper.GetEntityByDbId(m_cqParams.cqSession, m_entityName, m_dbid); // get the source id m_sourceId = CQWrapper.GetEntityDisplayName(m_CQEntity); Logger.Write(LogSource.CQ, TraceLevel.Verbose, UtilityMethods.Format(CQResource.CQ_PROCESSING_REC, m_sourceId)); // check if it exist in currituck using static API VSTSWorkItemHelper wiHelper = (VSTSWorkItemHelper)m_MySchemaMap.vstsHelper; ArrayList checkList = new ArrayList(); checkList.Add(new WorkItemNameValueRelation(CommonConstants.VSTSSrcIdField, m_sourceId)); checkList.Add(new WorkItemNameValueRelation(CommonConstants.VSTSSrcDbField, m_cqParams.uniqueInstId)); wiHelper = (VSTSWorkItemHelper)m_MySchemaMap.vstsHelper; if (wiHelper.IsWIMigrated(checkList) == true) { // need not to load the data from CQ.. // just set the currituck id // not going to update this bug from CQ->Currituck even // if it is updated.. just get out from here as my population is done // with minimal required stuff string warningMsg = UtilityMethods.Format(CQResource.CQ_REC_MIGRATED, m_sourceId); Logger.Write(LogSource.CQ, TraceLevel.Warning, warningMsg); PostMigrationReport.WriteIssue(m_MySchemaMap.entity, m_MySchemaMap.WIT, Stats.MigrationStatus.Skipped, ReportIssueType.Warning, String.Empty, m_sourceId, IssueGroup.Wi, warningMsg); m_WITId = wiHelper.WorkItemId; //compact current object CompactMe(); return(true); } else if (wiHelper.IsCurrentWorkItemValid() == true) { // work item is already there.. partially migrated partiallyMigrated = true; } #if DEBUG CommonConstants.NoOfBugs++; #endif // create the required data structures m_imWorkItem = new InMemoryWorkItem(); string fldName; OAdEntityDef curEntityDef = CQWrapper.GetEntityDef(m_cqParams.cqSession, m_entityName); Logger.Write(LogSource.CQ, TraceLevel.Verbose, "Adding record for Entity {0}, Record {1}", m_entityName, CQWrapper.GetEntityDisplayName(m_CQEntity)); object[] fields = (object[])CQWrapper.GetEntityFieldNames(m_CQEntity); foreach (object fld in fields) { fldName = (string)fld; if (CQConstants.InternalFieldTypes.ContainsKey(fldName)) { // these are internal clearquest fields // we dont want to migrate these Logger.Write(LogSource.CQ, TraceLevel.Info, "Skipping Internal Field '{0}' while migrating data for entity {1}", fldName, m_entityName); continue; } { // process this field only if it exists in the "from" side of Field Map OAdFieldInfo fldInfo = CQWrapper.GetEntityFieldValue(m_CQEntity, fldName); int cqFieldType = CQWrapper.GetFieldType(fldInfo); switch (cqFieldType) { case CQConstants.FIELD_ID: case CQConstants.FIELD_SHORT_STRING: case CQConstants.FIELD_INT: { string fldValue = CQWrapper.GetFieldValue(fldInfo); if (fldValue != null) { m_imWorkItem.InitialView.Add(fldName, fldValue); } } break; case CQConstants.FIELD_MULTILINE_STRING: { string fldValue = CQWrapper.GetFieldValue(fldInfo); if (currentEntityRecords.Entity == null) { // build entity to get the list of allowed/suggested values currentEntityRecords.Entity = CQWrapper.BuildEntity(m_cqParams.cqSession, currentEntityRecords.EntityName); } object[] choices = (object[])CQWrapper.GetFieldChoiceList(currentEntityRecords.Entity, fldName); if (choices != null && choices.Length > 0) { // Multi Line String with List of Allowed/Suggested Values.. replace all '\n' with comma // fix for bug# 429098 if (fldValue != null) { fldValue = fldValue.Replace("\n", ","); } } /* no conversion shall be required.. bug# 20219 - shall be rendered in HTML as it is * // hack for Notes_Log & Description field.. Shall be converted to HTML (bug#429032) * if (fldName.Equals("Notes_Log", StringComparison.OrdinalIgnoreCase) || * fldName.Equals("Description", StringComparison.OrdinalIgnoreCase)) * { * fldValue = VSTSUtil.ConvertTextToHtml(fldValue); * } */ m_imWorkItem.InitialView.Add(fldName, fldValue); } break; case CQConstants.FIELD_DATE_TIME: { string fldValue = CQWrapper.GetFieldValue(fldInfo); if (fldValue != null) { // the time returned from CQ API is the local time.. DateTime fldVal = DateTime.Parse(fldValue, CultureInfo.CurrentCulture); //convert it in UTC DateTime utcTime = CQConverterUtil.ConvertLocalToUTC(fldVal); Logger.Write(LogSource.CQ, TraceLevel.Verbose, "Field [{0}], CQ Time [{1}], UTC Time [{2}]", fldName, fldVal.ToString(), utcTime.ToString()); m_imWorkItem.InitialView.Add(fldName, utcTime); } else { Logger.Write(LogSource.CQ, TraceLevel.Info, "Got null value for field {0}", fldName); } } break; case CQConstants.FIELD_REFERENCE: { // get the current entity def handle OAdEntityDef refEntityDef = CQWrapper.GetFieldReferenceEntityDef(curEntityDef, fldName); string refEntityName = CQWrapper.GetEntityDefName(refEntityDef); // special handling for users.. add the user field value also.. // we dont want to create a link in this case.. // just add the field value pair in IMWorkItem.. and // user map will be applied while saving if (TFStringComparer.WorkItemType.Equals(refEntityName, "users")) { if (CQWrapper.GetFieldValueStatus(fldInfo) == (int)CQConstants.FieldStatus.HAS_VALUE) { // single value required string refFldVal = CQWrapper.GetFieldValue(fldInfo); m_imWorkItem.InitialView.Add(fldName, refFldVal); } } else if (m_cqParams.allowedEntities.ContainsKey(refEntityName)) { int valueStatus = CQWrapper.GetFieldValueStatus(fldInfo); Logger.WriteIf((valueStatus != (int)CQConstants.FieldStatus.HAS_VALUE), LogSource.CQ, TraceLevel.Info, "No Value for Referenced field {0} in Entity {1}", refEntityName, m_entityName); if (valueStatus == (int)CQConstants.FieldStatus.HAS_VALUE) { // single value required string refFldVal = CQWrapper.GetFieldValue(fldInfo); if (String.Equals(refFldVal, SourceId, StringComparison.Ordinal)) { // reference to self.. cannot have a link on to self string warningMsg = UtilityMethods.Format(CQResource.CQ_SELF_REFERENCE, SourceId, EntityName, fldName); Logger.Write(LogSource.CQ, TraceLevel.Warning, warningMsg); PostMigrationReport.WriteIssue(m_MySchemaMap.entity, m_MySchemaMap.WIT, Stats.MigrationStatus.Warning, ReportIssueType.Warning, String.Empty, m_sourceId, IssueGroup.Wi, warningMsg ); } else { m_referencedEntities.Add(new LinkRecord(refEntityName, refFldVal)); } } } } break; case CQConstants.FIELD_REFERENCE_LIST: { // get the current entity def handle OAdEntityDef refEntityDef = CQWrapper.GetFieldReferenceEntityDef(curEntityDef, fldName); string refEntityName = CQWrapper.GetEntityDefName(refEntityDef); // special handling for user list // we dont want to create a link in this case.. // concatenate all the user names separated by comma // NO USER MAP WILL BE APPLIED WHILE SAVING (bug#400276) if (TFStringComparer.WorkItemType.Equals(refEntityName, "users")) { if (CQWrapper.GetFieldValueStatus(fldInfo) == (int)CQConstants.FieldStatus.HAS_VALUE) { object[] refFldValues = CQWrapper.GetFieldValueAsList(fldInfo); StringBuilder userList = new StringBuilder(); for (int valueIndex = 0; valueIndex < refFldValues.Length; valueIndex++) { object refFldObj = refFldValues[valueIndex]; if (valueIndex > 0) { userList.Append(","); } userList.Append((string)refFldObj); } m_imWorkItem.InitialView.Add(fldName, userList.ToString()); } } else if (m_cqParams.allowedEntities.ContainsKey(refEntityName)) { int valueStatus = CQWrapper.GetFieldValueStatus(fldInfo); Logger.WriteIf((valueStatus != (int)CQConstants.FieldStatus.HAS_VALUE), LogSource.CQ, TraceLevel.Info, "No Value for Referenced field {0} in Entity {1}", fldName, m_entityName); if (valueStatus == (int)CQConstants.FieldStatus.HAS_VALUE) { // value list expected object[] refFldValues = CQWrapper.GetFieldValueAsList(fldInfo); foreach (object refFldObj in refFldValues) { string refFldVal = (string)refFldObj; if (String.Equals(refFldVal, SourceId, StringComparison.Ordinal)) { // reference to self.. cannot have a link on to self string warningMsg = UtilityMethods.Format(CQResource.CQ_SELF_REFERENCE, SourceId, EntityName, fldName); Logger.Write(LogSource.CQ, TraceLevel.Warning, warningMsg); PostMigrationReport.WriteIssue(m_MySchemaMap.entity, m_MySchemaMap.WIT, Stats.MigrationStatus.Warning, ReportIssueType.Warning, String.Empty, m_sourceId, IssueGroup.Wi, warningMsg); } else { m_referencedEntities.Add(new LinkRecord(refEntityName, refFldVal)); } } } } } break; case CQConstants.FIELD_ATTACHMENT_LIST: case CQConstants.FIELD_STATE: case CQConstants.FIELD_JOURNAL: case CQConstants.FIELD_DBID: case CQConstants.FIELD_STATETYPE: case CQConstants.FIELD_RECORDTYPE: Logger.Write(LogSource.CQ, TraceLevel.Info, "Skipping the Field migration for Internal Field Type '{0}'", cqFieldType); // not migrating these fields as they are CQ internal fields continue; default: Logger.Write(LogSource.CQ, TraceLevel.Info, "Skipping the Field migration for Unkknown Field Type '{0}'", cqFieldType); break; } } } // end of foreachfields // add the source id and db separately m_imWorkItem.InitialView.Add(CommonConstants.VSTSSrcIdField, m_sourceId); m_imWorkItem.InitialView.Add(CommonConstants.VSTSSrcDbField, m_cqParams.uniqueInstId); // use vstshelper to migrate the data wiHelper = (VSTSWorkItemHelper)m_MySchemaMap.vstsHelper; wiHelper.IsWIMigrated(checkList); // get attachments in the imworkitem ProcessAttachments(); // history processing will use same imWorkItem for first history info // and create other history indexes int migratedHistory = 0; if (wiHelper.IsCurrentWorkItemValid()) { migratedHistory = wiHelper.GetCurrentWorkItemHistoryCount(); if (migratedHistory > 0) { // We are going for incremental migration. And as we stuff first history item of a CQBug // into InitialView itself, actual no. of migrated history is one more than the value of // the "Migration Status" field. So increment by one. ++migratedHistory; } } ArrayList historyItems = ProcessHistory(m_imWorkItem.InitialView, migratedHistory); Logger.Write(LogSource.CQ, TraceLevel.Verbose, "Dumping initial view for {0}", m_sourceId); foreach (object key in m_imWorkItem.InitialView.Keys) { Logger.Write(LogSource.CQ, TraceLevel.Verbose, "{0} - {1}", key, m_imWorkItem.InitialView[key]); } bool initialViewStatus = true; try { if (!partiallyMigrated) { // if some history items or links are left to be migrated.. leave the bug as opened.. if (historyItems.Count > 0 || m_referencedEntities.Count > 0) { Logger.Write(LogSource.CQ, TraceLevel.Verbose, "Creating initial view of {0} .. {1} Histories, {2} Links pending", SourceId, historyItems.Count, m_referencedEntities.Count); // create the record and keep it open for history editing initialViewStatus = wiHelper.CreateInitialViewOfWorkItem(m_sourceId, m_imWorkItem, false); } else { Logger.Write(LogSource.CQ, TraceLevel.Verbose, "Creating initial view of {0}", SourceId); // create all the entries in the record and set the status to done initialViewStatus = wiHelper.CreateInitialViewOfWorkItem(m_sourceId, m_imWorkItem, true); } } } catch (Exception ex) { // creation of work item failed string errMsg = UtilityMethods.Format(CQResource.CQ_WI_CREATION_FAILED, SourceId, ex.Message); CQConverter.ReportWorkItemFailure(errMsg, SourceId, m_MySchemaMap.entity, m_MySchemaMap.WIT, m_cqParams.exitOnError); if (m_cqParams.exitOnError == true) { throw new ConverterException(errMsg); } else { // continue with another work item // need to skip this work item.. m_WITId = -1; CompactMe(); return(false); } } finally { } // get back currituck id and store in this m_WITId = wiHelper.WorkItemId; // store the handle of work item to restore the state of work item helper back to // working work item which may get changed because of processing links recursively object workItem = wiHelper.GetCurrentWorkItem(); // before processing history, clean out attachments.. only if its already migrated if (wiHelper.GetCurrentWorkItemAttachmentsCount() == m_imWorkItem.Attachments.Count) { m_imWorkItem.Attachments.Clear(); } // add all the links now so that they go as part of history bool refRecordStatus = true; foreach (LinkRecord linkRec in m_referencedEntities) { if (AddReferenceRecord(linkRec) == false) { refRecordStatus = false; // once false always false } } // process duplicate records if (ProcessDuplicates(m_cqParams) == false) { refRecordStatus = false; } bool writeHistoryPassed = true; wiHelper.SetCurrentWorkItem(workItem); if (historyItems.Count > 0 || m_imWorkItem.Links.Count > 0 || m_imWorkItem.Attachments.Count > 0) { m_imWorkItem.HistoryItems = historyItems; try { writeHistoryPassed = wiHelper.WriteHistoryItems(m_sourceId, m_imWorkItem, refRecordStatus && initialViewStatus); if (!writeHistoryPassed) { // Bug#59861: In the case of the partially migrated bug, // converter says all bugs migrated successfully in // summary, but in error section it says one bug failed // due to attachment size issue. This issue has already // been written to the report. Just need to update the // statistics info. PostMigrationReport.WriteIssue(m_MySchemaMap.entity, m_MySchemaMap.WIT, Stats.MigrationStatus.Failed, ReportIssueType.Info, null, m_sourceId, IssueGroup.Wi, null); } // set the bug migration status to done only if there were no // problems with initial view and any of the references if ((!writeHistoryPassed || !refRecordStatus || !initialViewStatus) && m_cqParams.exitOnError) { // stop processing more records CompactMe(); return(false); } } catch (Exception ex) { // creation of history failed string errMsg = UtilityMethods.Format(CQResource.CQ_WI_MIG_FAILED, SourceId, ex.Message); CQConverter.ReportWorkItemFailure(errMsg, SourceId, m_MySchemaMap.entity, m_MySchemaMap.WIT, m_cqParams.exitOnError); if (m_cqParams.exitOnError == true) { throw new ConverterException(errMsg); } else { // continue with another work item.. reporting this failure CompactMe(); return(false); } } // end of catch finally { } } // end of history items processing // add to pass count ConverterMain.MigrationReport.Statistics.NumberOfItems++; // add to per work item type section if (writeHistoryPassed) { PostMigrationReport.WriteIssue(m_MySchemaMap.entity, m_MySchemaMap.WIT, Stats.MigrationStatus.Passed, ReportIssueType.Info, null, m_sourceId, IssueGroup.Wi, null); } //compact current object CompactMe(); return(true); } // end of Populate()
/// <inheritdoc/> public TfsWorkItem GetWorkItemForIssue(string issueId, int issueActivityCount) { if (string.IsNullOrEmpty(issueId)) { throw new ArgumentNullException(nameof(issueId)); } if (this.parentWorkItemId == -1) { throw new Exception("Please run ConfigureAsync first."); } var hasChange = false; var parentWorkItem = this.workItems.Single(w => w.Id == this.parentWorkItemId); string[] commentParts = null; var workItemLink = parentWorkItem.Links.FirstOrDefault(l => { if (l.Comment.StartsWith(issueId)) { commentParts = l.Comment.Split(':'); return true; } return false; }); InMemoryWorkItem workItem = null; if (workItemLink != null) { if (!commentParts[1].Equals(issueActivityCount.ToString(CultureInfo.InvariantCulture))) { hasChange = true; } workItem = this.workItems.Single(w => w.Id == workItemLink.RelatedWorkItemId); } else { workItem = new InMemoryWorkItem(); hasChange = true; } var issueSignature = $"{issueId}:{issueActivityCount}"; return new InMemoryTfsWorkItem(workItem) { HasChange = hasChange, IssueSignature = issueSignature }; }
public InMemoryTfsWorkItem(InMemoryWorkItem item) : base(item, typeof(InMemoryWorkItem)) { }
/// <summary> /// Creates a work item in memory. /// </summary> /// <param name="title">Work item title.</param> public int CreateWorkItem(string title) { var workItem = new InMemoryWorkItem { Title = title }; this.workItems.Add(workItem); return workItem.Id; }
/// <summary> /// Create work item in the Currituck corresponding to the given memory work item /// </summary> /// <param name="sourceWIId">Source Work Item ID</param> /// <param name="imWorkItem">In Memory Work Item containing Initial View, Attachments and Links</param> /// <param name="setMigStatus">Set the Migration Status field to Done or not</param> /// <returns>true if it is able to save all fields, attachments and links, else false</returns> public bool CreateInitialViewOfWorkItem(string sourceWIId, InMemoryWorkItem imWorkItem, bool setMigStatusDone) { bool retVal = true; m_vstsWorkItem = new VSTSWorkItem(); try { m_sourceWorkItemId = sourceWIId; m_vstsWorkItem.sourceId = sourceWIId; // create new webs service call xml fragment WSHelper webServiceHelper = new WSHelper(WSHelper.WebServiceType.InsertWorkItem); // push the initial snapshot IDictionaryEnumerator enumerator = m_baseWiSnapShot.GetEnumerator(); while (enumerator.MoveNext()) { webServiceHelper.AddColumn(enumerator.Key.ToString(), enumerator.Value.ToString()); } // set the default value of area id to root node initially m_vstsWorkItem.areaId = m_wi.AreaId; // first set the initial required fields.. if some save happens in // state processing, bug will be created with minimal information foreach (string fldName in VSTSUtil.InitialFields[m_convSourceIndex]) { if (imWorkItem.InitialView[fldName] != null) { UpdateWorkItemField(webServiceHelper, fldName, imWorkItem.InitialView[fldName]); } } // while creating initial view the fields with no values does not // makes any sense... they are required for further revisions where // the values would have been removed.. // filter the initial view to remove all the null values ArrayList nullFields = new ArrayList(); foreach (DictionaryEntry de in imWorkItem.InitialView) { if (de.Value == null) { nullFields.Add(de.Key); } else { // see if it is empty string if (de.Value is string) { if (String.IsNullOrEmpty((string)de.Value)) { nullFields.Add(de.Key); } } } } foreach (object toRemove in nullFields) { imWorkItem.InitialView.Remove(toRemove); } ProcessRevision(webServiceHelper, imWorkItem.InitialView, "0"); // Set migration status field only if no links and attachments exist if (setMigStatusDone && imWorkItem.Attachments.Count == 0 && imWorkItem.Links.Count == 0) { // completed the migration for initial view webServiceHelper.AddColumn(VSTSConstants.MigrationStatusField, "Done"); } int workItemId = webServiceHelper.Save(); Debug.Assert(workItemId != 0, "Work Item save returned ID as 0"); // set the m_currentworkitem context m_currentWorkItem = SetCurrentWorkItem(workItemId); m_revision = m_currentWorkItem.Rev - 1; m_vstsWorkItem.Id = workItemId; } finally { } // process attachments and links only if SetMigStatus is true.. // i.e. this is the only revision to be created if (setMigStatusDone && (imWorkItem.Attachments.Count > 0 || imWorkItem.Links.Count > 0)) { retVal = ProcessAttachmentsAndLinks(imWorkItem, setMigStatusDone); } Logger.Write(LogSource.WorkItemTracking, TraceLevel.Verbose, "Created new work item : {0}", m_currentWorkItem.Id); return(retVal); }
/// <summary> /// Process Attachments and Links creation. Has to be at the end /// </summary> /// <param name="imWorkItem"></param> /// <param name="setMigStatus"></param> private bool ProcessAttachmentsAndLinks(InMemoryWorkItem imWorkItem, bool setMigStatus) { bool retVal = true; Logger.Write(LogSource.WorkItemTracking, TraceLevel.Verbose, "[{0}] attachments for work item [{1}]", imWorkItem.Attachments.Count, m_sourceWorkItemId); Logger.Write(LogSource.WorkItemTracking, TraceLevel.Verbose, "[{0}] links for work item [{1}]", imWorkItem.Links.Count, m_sourceWorkItemId); try { // create new web service call xml fragment WSHelper webServiceHelper = new WSHelper(WSHelper.WebServiceType.UpdateWorkItem); webServiceHelper.SetWorkItemAndRevision(m_currentWorkItem.Id, ++m_revision); // process links if (imWorkItem.Links.Count > 0) { foreach (InMemoryLinkItem imLink in imWorkItem.Links) { if (!IsLinkMigrated(imLink)) { if (imLink.CurrituckLinkedId == -1) { // link cannot be set as well as description cannot be found in history webServiceHelper.AddDescriptiveField(VSTSConstants.HistoryFieldRefName, String.Concat(VSTSUtil.ConverterComment, imLink.LinkDescription), true); } else { // set the link information in work item.. as a related link webServiceHelper.AddLink(imLink.CurrituckLinkedId, imLink.LinkDescription); // check if it is duplicate link and setting Duplicate WI is allowed if (m_canSetDuplicateWiId && imLink is InMemoryDuplicateLinkItem && m_vstsConnection.store.FieldDefinitions.Contains(m_duplicateWiId)) { Logger.Write(LogSource.WorkItemTracking, TraceLevel.Info, "Creating Duplicate Link as Related Link from {0} to {1} with comment {2}", m_currentWorkItem.Id, imLink.CurrituckLinkedId, imLink.LinkDescription); webServiceHelper.AddColumn(m_wi.Fields[m_duplicateWiId].ReferenceName, imLink.CurrituckLinkedId.ToString(CultureInfo.InvariantCulture)); } } // end of else } // end of isLinkMigrated() else { Logger.Write(LogSource.WorkItemTracking, TraceLevel.Warning, "Cannot add link as it already exists: {0}", imLink.CurrituckLinkedId); } } } // process attachments if (imWorkItem.Attachments.Count > 0) { int noOfAttachmentsProcessed = 0; string areaNodeUri = GetAreaNodeUri(m_vstsWorkItem.areaId); Debug.Assert(!String.IsNullOrEmpty(areaNodeUri), "No area node uri found"); foreach (InMemoryAttachment attach in imWorkItem.Attachments) { if (IsAttachmentMigrated(attach)) { continue; } try { webServiceHelper.AddAttachment(attach.FileName, attach.Comment, attach.IsLinkedFile, areaNodeUri); } catch (ConverterException conEx) { // attachment upload failed.. add into migration report string errMsg = UtilityMethods.Format( VSTSResource.VstsAttachmentUploadFailed, Path.GetFileName(attach.FileName), m_sourceWorkItemId, conEx.Message); ConverterMain.MigrationReport.WriteIssue(String.Empty, ReportIssueType.Error, errMsg, m_sourceWorkItemId.ToString(CultureInfo.InvariantCulture)); Display.DisplayError(errMsg); // and make sure that Migration Status is not set setMigStatus = false; retVal = false; } noOfAttachmentsProcessed++; if (noOfAttachmentsProcessed % 32 == 0) { // save at every 32nd attachment webServiceHelper.AddDescriptiveField( VSTSConstants.HistoryFieldRefName, UtilityMethods.Format( VSTSResource.VstsAttachmentLinkHistory), true); Logger.Write(LogSource.WorkItemTracking, TraceLevel.Warning, "Performing interim save for work item {0} since no of attachments exceeds 32", m_sourceWorkItemId); if (imWorkItem.Attachments.Count == noOfAttachmentsProcessed) { // boundary case .. this is the last attachment.. // set the Migration Status also if (setMigStatus) { webServiceHelper.AddColumn(VSTSConstants.MigrationStatusField, "Done"); } } webServiceHelper.Save(); if (noOfAttachmentsProcessed < imWorkItem.Attachments.Count) { // some attachemnts left.. reset the webserviceHelper handle webServiceHelper = new WSHelper(WSHelper.WebServiceType.UpdateWorkItem); webServiceHelper.SetWorkItemAndRevision(m_currentWorkItem.Id, ++m_revision); } else { // no more save required for the current work item webServiceHelper = null; } } // end of if (noOfAttachmentsProcessed % 32 == 0) } // end of foreach attachments } if (webServiceHelper != null) { webServiceHelper.AddDescriptiveField( VSTSConstants.HistoryFieldRefName, UtilityMethods.Format( VSTSResource.VstsAttachmentLinkHistory), true); // Set migration status field if (setMigStatus) { webServiceHelper.AddColumn(VSTSConstants.MigrationStatusField, "Done"); } webServiceHelper.Save(); } SetCurrentWorkItem(m_currentWorkItem.Id); } finally { } return(retVal); }