/// <summary> /// /// </summary> /// <param name="entity"></param> /// <param name="action"></param> /// <param name="convRslt"></param> /// <param name="stateTransitFieldNode"></param> /// <param name="skipFields"></param> /// <returns></returns> private bool ChangeRecordState( OAdEntity entity, IMigrationAction action, ConversionResult convRslt, XmlNode stateTransitFieldNode, out List <string> processedFields) { processedFields = new List <string>(); string destState = UtilityMethods.ExtractSingleFieldValue(stateTransitFieldNode); Debug.Assert(!string.IsNullOrEmpty(destState), "string.IsNullOrEmpty(newState)"); // get the record's display name for updating conversion history string recordDisplayName = CQWrapper.GetEntityDisplayName(entity); string entityDefName = CQWrapper.GetEntityDefName(entity); // find the current state OAdFieldInfo aFldInfo = CQWrapper.GetEntityFieldValue(entity, m_migrationContext.GetStateField(entityDefName)); string srcState = CQWrapper.GetFieldValue(aFldInfo); if (CQStringComparer.StateName.Equals(srcState, destState)) { // state does not change, skip this action return(false); } // find action def name OAdEntityDef entityDef = CQWrapper.GetEntityDef(m_userSession, entityDefName); string[] changeActionNames = CQUtilityMethods.FindAllActionNameByTypeAndStateTransition(entityDef, srcState, destState, CQConstants.ACTION_CHANGE); if (changeActionNames.Length == 0) { // [teyang] todo error handling throw new InvalidOperationException(); } string changeActionName = changeActionNames[0]; // ********* // cache the current history count for all "history fields" // i.e. pairs of HistoryFieldName, count Dictionary <string, int> recordHistoryCountCache = new Dictionary <string, int>(); BuildRecordHistoryCountCache(entity, recordHistoryCountCache); StringBuilder updateLog = new StringBuilder(); PrintUpdateLogHeader(action, updateLog); // mark entity to be editable with the desired state-change action) CQWrapper.EditEntity(m_userSession, entity, changeActionName); XmlNodeList columns = action.MigrationActionDescription.SelectNodes("/WorkItemChanges/Columns/Column"); if (null == columns) { throw new MigrationException(ClearQuestResource.ClearQuest_Error_InvalidActionDescription, action.ActionId); } foreach (XmlNode columnData in columns) { string stringVal = columnData.FirstChild.InnerText; string fieldName = columnData.Attributes["ReferenceName"].Value; Debug.Assert(!string.IsNullOrEmpty(fieldName), "Field ReferenceName is absent in the Migration Description"); OAdFieldInfo aFieldInfo = CQWrapper.GetEntityFieldValue(entity, fieldName); int fieldRequiredness = CQWrapper.GetRequiredness(aFieldInfo); if (fieldRequiredness != CQConstants.MANDATORY) { // skipping all non-mandatory fields continue; } int attempt1Count = 0; if (!SetFieldValue(action, entity, fieldName, stringVal, ref attempt1Count)) { return(false); } AddFieldToUpdateLog(fieldName, stringVal, updateLog); processedFields.Add(fieldName); } AddLineToUpdateLog(updateLog); int attempt2Count = 0; if (!SetFieldValue(action, entity, NoteEntryFieldName, updateLog.ToString(), ref attempt2Count)) { return(false); } string retVal = CQWrapper.Validate(entity); if (!string.IsNullOrEmpty(retVal)) { // [teyang] TODO conflict handling throw new InvalidOperationException(retVal); } retVal = CQWrapper.Commmit(entity); if (!string.IsNullOrEmpty(retVal)) { // [teyang] TODO conflict handling throw new InvalidOperationException(retVal); } if (action.State == ActionState.Pending) { action.State = ActionState.Complete; } // ********* // now comparing to the cache, so that we can clearly identify the item:version pairs // e.g. TargetCQRecordDisplayName : HistoryFieldName::LatestHistoryIndex Dictionary <string, int[]> updatedHistoryIndices = new Dictionary <string, int[]>(); FindUpdatedHistoryIndices(entity, recordHistoryCountCache, updatedHistoryIndices); recordHistoryCountCache.Clear(); foreach (string histFieldName in updatedHistoryIndices.Keys) { foreach (int histIndex in updatedHistoryIndices[histFieldName]) { UpdateConversionHistory(action, recordDisplayName, CQHistoryMigrationItem.CreateHistoryItemVersion(histFieldName, histIndex), convRslt); } } return(true); }
private void AddAttachment(IMigrationAction action, ConversionResult convRslt) { string attName = UtilityMethods.ExtractAttachmentName(action); string lengthStr = UtilityMethods.ExtractAttachmentLength(action); string attComment = UtilityMethods.ExtractAttachmentComment(action); string ownerRecordDisplayName = FindTargetWorkItemId(action); string ownerRecordType = UtilityMethods.ExtractRecordType(action); string filePath = Path.Combine(m_configurationService.WorkspaceRoot, ownerRecordDisplayName + attName); try { // find the entity OAdEntity entity = CQWrapper.GetEntity(m_userSession, ownerRecordType, ownerRecordDisplayName); if (AttachmentExists(entity, attName, attComment, lengthStr)) { action.State = ActionState.Skipped; return; } // find the change action def name string entityDefName = CQWrapper.GetEntityDefName(entity); OAdEntityDef entityDef = CQWrapper.GetEntityDef(m_userSession, entityDefName); string modifyActionDefName = FindCQActionDefName(entityDef, CQConstants.ACTION_MODIFY); // mark entity to be editable CQWrapper.EditEntity(m_userSession, entity, modifyActionDefName); // cache the current history count for all "history fields" // i.e. pairs of HistoryFieldName, count Dictionary <string, int> recordHistoryCountCache = new Dictionary <string, int>(); BuildRecordHistoryCountCache(entity, recordHistoryCountCache); action.SourceItem.Download(filePath); string attachmentField = m_migrationContext.GetAttachmentSinkField(entityDefName); string retVal = CQWrapper.AddAttachmentFieldValue(entity, attachmentField, attName, filePath); if (!string.IsNullOrEmpty(retVal)) { // teyang_todo attachment conflict } retVal = CQWrapper.Validate(entity); if (!string.IsNullOrEmpty(retVal)) { // [teyang] TODO conflict management throw new InvalidOperationException(); } retVal = CQWrapper.Commmit(entity); if (!string.IsNullOrEmpty(retVal)) { // [teyang] TODO conflict management throw new InvalidOperationException(); } if (action.State == ActionState.Pending) { action.State = ActionState.Complete; } // ********* // now comparing to the cache, so that we can clearly identify the item:version pairs // e.g. TargetCQRecordDisplayName : HistoryFieldName::LatestHistoryIndex Dictionary <string, int[]> updatedHistoryIndices = new Dictionary <string, int[]>(); FindUpdatedHistoryIndices(entity, recordHistoryCountCache, updatedHistoryIndices); recordHistoryCountCache.Clear(); foreach (string histFieldName in updatedHistoryIndices.Keys) { foreach (int histIndex in updatedHistoryIndices[histFieldName]) { UpdateConversionHistory(action, ownerRecordDisplayName, CQHistoryMigrationItem.CreateHistoryItemVersion(histFieldName, histIndex), convRslt); } } } finally { if (File.Exists(filePath)) { File.Delete(filePath); } } }
private void ModifyRecordContent( OAdEntity entity, IMigrationAction action, ConversionResult convRslt, List <string> skipFields) { XmlNodeList columns = action.MigrationActionDescription.SelectNodes("/WorkItemChanges/Columns/Column"); if (null == columns) { throw new MigrationException(ClearQuestResource.ClearQuest_Error_InvalidActionDescription, action.ActionId); } // get the record's display name for updating conversion history string recordDisplayName = CQWrapper.GetEntityDisplayName(entity); // ********* // cache the current history count for all "history fields" // i.e. pairs of HistoryFieldName, count Dictionary <string, int> recordHistoryCountCache = new Dictionary <string, int>(); BuildRecordHistoryCountCache(entity, recordHistoryCountCache); SetRecordEditable(entity); StringBuilder updateLog = new StringBuilder(); PrintUpdateLogHeader(action, updateLog); string entityDefName = CQWrapper.GetEntityDefName(entity); string stateTransitionFieldDefName = m_migrationContext.GetStateField(entityDefName); string retVal; bool recordIsUpdated = false; foreach (XmlNode columnData in columns) { string stringVal = columnData.FirstChild.InnerText; string fieldName = columnData.Attributes["ReferenceName"].Value; Debug.Assert(!string.IsNullOrEmpty(fieldName), "Field ReferenceName is absent in the Migration Description"); if (CQStringComparer.FieldName.Equals(fieldName, stateTransitionFieldDefName) || (null != skipFields && skipFields.Contains(fieldName, CQStringComparer.FieldName))) { // skip or "State" field, as it has already been submitted in a separate history/revision continue; } bool setFieldValue = false; OAdFieldInfo aFieldInfo = CQWrapper.GetEntityFieldValue(entity, fieldName); int fieldRequiredness = CQWrapper.GetRequiredness(aFieldInfo); switch (fieldRequiredness) { case CQConstants.MANDATORY: case CQConstants.OPTIONAL: setFieldValue = true; break; case CQConstants.READONLY: // [teyang] TODO conflict handling TraceManager.TraceWarning("Field {0} is READONLY", fieldName); setFieldValue = false; break; case CQConstants.USEHOOK: // [teyang] TODO conflict handling TraceManager.TraceWarning("Field {0} is USEHOOK", fieldName); setFieldValue = false; break; default: throw new InvalidOperationException(); } if (setFieldValue) { int attempt1Count = 0; if (!SetFieldValue(action, entity, fieldName, stringVal, ref attempt1Count)) { return; } AddFieldToUpdateLog(fieldName, stringVal, updateLog); recordIsUpdated = true; } } if (!recordIsUpdated) { // no update has been made to the record, mark this action to be skipped CQWrapper.Revert(entity); if (action.State == ActionState.Pending) { action.State = ActionState.Skipped; } return; } AddLineToUpdateLog(updateLog); int attempt2Count = 0; if (!SetFieldValue(action, entity, NoteEntryFieldName, updateLog.ToString(), ref attempt2Count)) { return; } retVal = CQWrapper.Validate(entity); if (!string.IsNullOrEmpty(retVal)) { IEnumerable <Microsoft.TeamFoundation.Migration.ClearQuestAdapter.CQTextParser.RecordValidationResult> validationResults; if (CQTextParser.RecordValidationTextParser.TryParse(retVal, out validationResults)) { foreach (CQTextParser.RecordValidationResult rslt in validationResults) { MigrationConflict conflict = ClearQuestInvalidFieldValueConflictType.CreateConflict(rslt, action); List <MigrationAction> actions; var resolutionRslt = m_conflictManagerService.TryResolveNewConflict(m_conflictManagerService.SourceId, conflict, out actions); if (!resolutionRslt.Resolved) { action.ChangeGroup.ContainsBackloggedAction = true; return; } } } else { throw new InvalidOperationException(retVal); } } retVal = CQWrapper.Commmit(entity); if (!string.IsNullOrEmpty(retVal)) { // [teyang] TODO: invalid update conflict handling throw new InvalidOperationException(retVal); } if (action.State == ActionState.Pending) { action.State = ActionState.Complete; } // ********* // now comparing to the cache, so that we can clearly identify the item:version pairs // e.g. TargetCQRecordDisplayName : HistoryFieldName::LatestHistoryIndex Dictionary <string, int[]> updatedHistoryIndices = new Dictionary <string, int[]>(); FindUpdatedHistoryIndices(entity, recordHistoryCountCache, updatedHistoryIndices); recordHistoryCountCache.Clear(); foreach (string histFieldName in updatedHistoryIndices.Keys) { foreach (int histIndex in updatedHistoryIndices[histFieldName]) { UpdateConversionHistory(action, recordDisplayName, CQHistoryMigrationItem.CreateHistoryItemVersion(histFieldName, histIndex), convRslt); } } }
private void ComputeDeltaPerRecord( OAdEntity aRecord) { OAdEntityDef aEntityDef = CQWrapper.GetEntityDef(m_userSession, CQWrapper.GetEntityDefName(aRecord)); string recordDispName = CQWrapper.GetEntityDisplayName(aRecord); string recordEntDefName = CQWrapper.GetEntityDefName(aRecord); #region process history bool recordContentIsModified = false; bool maybeNewRecord = false; Dictionary <string, List <ClearQuestRecordItem> > historyDelta = new Dictionary <string, List <ClearQuestRecordItem> >(); Dictionary <string, int> perHistoryFieldLastIndex = new Dictionary <string, int>(); // needed for updating processed delta string lastHistoryVersion = null; // find all history fields OAdHistoryFields aHistFields = CQWrapper.GetHistoryFields(aRecord); int historyFldCount = CQWrapper.HistoryFieldsCount(aHistFields); bool containsNewHistory = false; for (int histFldIndex = 0; histFldIndex < historyFldCount; histFldIndex++) { object ob = (object)histFldIndex; OAdHistoryField aHistoryField = CQWrapper.HistoryFieldsItem(aHistFields, ref ob); string historyFieldName = CQWrapper.GetHistoryFieldName(aHistoryField); // find last processed history entry for this history field string lookupItemId = CQDeltaComputationProgressLookupService.CreateHistoryItemId(recordEntDefName, recordDispName, historyFieldName); int startHistIndex = 1 + DeltaComputeProgressService.GetLastProcessedItemVersion(lookupItemId); // find all history in a particular history field int historyCount = CQWrapper.HistoryFieldHistoriesCount(aHistoryField); TraceManager.TraceVerbose("ClearQuest Adapter ComputeDeltaPerRecord: {0}:{1}; historyCount={2}; startHistIndex={3}", recordEntDefName, recordDispName, historyCount, startHistIndex); for (int histIndex = startHistIndex; histIndex < historyCount; histIndex++) { object obHistIndex = (object)histIndex; OAdHistory aHistory = CQWrapper.HistoryFieldHistoriesItem(aHistoryField, ref obHistIndex); CQHistory cqHistory = new CQHistory(aHistory); CQMigrationItem migrationItem = new CQHistoryMigrationItem(recordDispName, historyFieldName, histIndex); if (TranslationService.IsSyncGeneratedItemVersion(ClearQuestRecordItem.GetMigrationRecordId(recordEntDefName, recordDispName), migrationItem.MigrationItemVersion, m_configurationService.SourceId)) { TraceManager.TraceVerbose("ClearQuest Adapter: Skipping version {0} of item {1}:{2} because it is a sync generated version", migrationItem.MigrationItemVersion, recordEntDefName, recordDispName); continue; } TraceManager.TraceVerbose("ClearQuest Adapter: Generating delta for version {0} of item {1}:{2}", migrationItem.MigrationItemVersion, recordEntDefName, recordDispName); if (histIndex == 0) { maybeNewRecord = true; } // add unprocessed history fields for processing if (!historyDelta.ContainsKey(historyFieldName)) { historyDelta.Add(historyFieldName, new List <ClearQuestRecordItem>(historyCount)); } historyDelta[aHistoryField.fieldname].Add(new ClearQuestRecordItem(aRecord, aHistory, historyFieldName, histIndex.ToString())); containsNewHistory = true; // based on action type, we decide whether content change is needed int actionType = CQWrapper.GetActionDefType(aEntityDef, cqHistory.Action); switch (actionType) { case CQConstants.ACTION_SUBMIT: break; case CQConstants.ACTION_MODIFY: recordContentIsModified = true; break; case CQConstants.ACTION_CHANGE_STATE: recordContentIsModified = true; break; case CQConstants.ACTION_DUPLICATE: break; case CQConstants.ACTION_UNDUPLICATE: break; case CQConstants.ACTION_IMPORT: break; case CQConstants.ACTION_DELETE: TraceManager.TraceInformation(ClearQuestResource.ClearQuest_Msg_RecordDeleted, recordEntDefName, recordDispName); break; case CQConstants.ACTION_BASE: break; case CQConstants.ACTION_RECORD_SCRIPT_ALIAS: break; } } perHistoryFieldLastIndex.Add(historyFieldName, historyCount - 1); lastHistoryVersion = CQHistoryMigrationItem.CreateHistoryItemVersion(historyFieldName, historyCount - 1); } #endregion #region generate delta for content and history if (maybeNewRecord || recordContentIsModified) { // the first revision, i.e. "Submit", of a CQ record is always hard-coded to be '1' CQMigrationItem contentMigrationAction = new CQMigrationItem(recordDispName, ClearQuestRecordItem.NewRecordVersion); bool isNewRecord = false; if (maybeNewRecord) { isNewRecord = !(DeltaComputeProgressService.IsMigrationItemProcessed(recordDispName, ClearQuestRecordItem.NewRecordVersionValue)); } if (!isNewRecord) { // all subsequent record "MODIFICATIONs" are hard-coded to be "update@<Now.Ticks>" contentMigrationAction.MigrationItemVersion = ClearQuestRecordItem.RecordUpdateVersion + "@" + DateTime.Now.Ticks; } ClearQuestRecordItem recordContentItem = new ClearQuestRecordItem(aRecord, contentMigrationAction.MigrationItemVersion); recordContentItem.CQSession = m_userSession; recordContentItem.Version = contentMigrationAction.MigrationItemVersion; ChangeGroup contentChangeGroup = recordContentItem.CreateChangeGroup( m_changeGroupService, m_migrationContext, isNewRecord && m_isLastRevisionAutoCorrectionEnabled); contentChangeGroup.Save(); if (isNewRecord && !containsNewHistory) { DeltaComputeProgressService.UpdateCache(recordDispName, ClearQuestRecordItem.NewRecordVersionValue); } } if (historyDelta.Count > 0) { var lastHistoryRecordItem = historyDelta[historyDelta.Keys.Last()].Last(); foreach (string histFieldName in historyDelta.Keys) { foreach (ClearQuestRecordItem recordHistItem in historyDelta[histFieldName]) { recordHistItem.CQSession = m_userSession; ChangeGroup changeGroup = recordHistItem.CreateChangeGroup( m_changeGroupService, m_migrationContext, (CQStringComparer.FieldName.Equals(recordHistItem.HistoryFieldName, lastHistoryRecordItem.HistoryFieldName) && recordHistItem.Version.Equals(lastHistoryRecordItem.Version, StringComparison.OrdinalIgnoreCase) && m_isLastRevisionAutoCorrectionEnabled)); changeGroup.Save(); } Debug.Assert(perHistoryFieldLastIndex.ContainsKey(histFieldName), "perHistoryFieldLastIndex.ContainsKey(histFieldName) returns false"); string deltaComputeProcessLookupId = CQDeltaComputationProgressLookupService.CreateHistoryItemId(recordEntDefName, recordDispName, histFieldName); DeltaComputeProgressService.UpdateCache(deltaComputeProcessLookupId, perHistoryFieldLastIndex[histFieldName]); } } else { TraceManager.TraceVerbose("ClearQuest Adapter: No non-sync delta versions found for record {0}", recordDispName); } #endregion #region process attachment OAdAttachmentFields aAttachmentFields = CQWrapper.GetAttachmentFields(aRecord); for (int aAttachmentFieldsIndex = 0; aAttachmentFieldsIndex < CQWrapper.AttachmentsFieldsCount(aAttachmentFields); aAttachmentFieldsIndex++) { object ob = (object)aAttachmentFieldsIndex; OAdAttachmentField aAttachmentField = CQWrapper.AttachmentsFieldsItem(aAttachmentFields, ref ob); string fieldName = CQWrapper.GetAttachmentFieldName(aAttachmentField); ChangeGroup changeGroup = m_changeGroupService.CreateChangeGroupForDeltaTable( string.Format("{0}:{1}:{2}", recordDispName, "Attachments", fieldName)); // process all attachments OAdAttachments attachments = CQWrapper.GetAttachments(aAttachmentField); int attachmentsCount = CQWrapper.AttachmentsCount(attachments); if (attachmentsCount > 0) { TraceManager.TraceVerbose("ClearQuest Adapter: Generating actions for {0} attachments on record {1}", attachmentsCount, recordDispName); for (int attachmentIndex = 0; attachmentIndex < attachmentsCount; attachmentIndex++) { object obIndex = (object)attachmentIndex; OAdAttachment aAttachment = CQWrapper.AttachmentsItem(attachments, ref obIndex); ClearQuestAttachmentItem attachmentItem = new ClearQuestAttachmentItem(aRecord, aAttachmentField, aAttachment, UserSessionConnConfig); attachmentItem.CQSession = m_userSession; attachmentItem.CreateChangeAction(changeGroup, lastHistoryVersion); } } if (changeGroup.Actions.Count > 0) { changeGroup.Save(); } } #endregion }