Skip to content

Commit e5658be

Browse files
Merge pull request #1111 from solidify/feature/vs402625-bump-changeddate
Feature/vs402625 bump changeddate
2 parents 9ebfd12 + 72126a3 commit e5658be

File tree

9 files changed

+63
-36
lines changed

9 files changed

+63
-36
lines changed

docs/config.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ The migration configuration file is defined in a json file with the properties d
3838
|**suppress-notifications**|False|boolean|Set to True to suppress all notifications in Azure DevOps about created and updated Work Items. Default = False.|
3939
|**include-development-links**|False|boolean|Set to True to migrated commit links from Jira to Azure DevOps. You will also need to fill out the **repository-map** property. Default = False.|
4040
|**sleep-time-between-revision-import-milliseconds**|False|integer|How many milliseconds to sleep between each revision import. Use this if throttling is an issue for ADO Services. Default = 0 (no sleep).|
41-
|**buffer-revisions-succeeding-attachment-imports-milliseconds**|False|integer|How many milliseconds to buffer each subsequent revision if there is a negative revision timestamp offset. Increase this if you get problems with VS402625 error messages. Default = 5 (ms).|
41+
|**changeddate-bump-ms**|False|integer|How many milliseconds to buffer each subsequent revision if there is a negative revision timestamp offset. Increase this if you get a lot of VS402625 warning messages during the import. Default = 2 (ms).|
4242
|**process-template**|False|string|Process template in the target DevOps project. Supported values: Scrum, Agile or CMMI. Default = "Scrum".|
4343
|**link-map**|True|json|List of **links** to map between Jira and Azure DevOps/TFS work item link types.|
4444
|**type-map**|True|json|List of the work item **types** you want to migrate from Jira to Azure DevOps/TFS.|

docs/faq.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -430,28 +430,28 @@ curl -D-
430430
"http://johnie:8081/rest/api/2/search"
431431
```
432432

433-
## 19. I get the error message "VS402625: Dates must be increasing with each revision."
433+
## 19. I get the warning message "VS402625: Dates must be increasing with each revision."
434434

435-
This error message will show up if the tool attempts to import a subsequent revision with a changedDate that is less than the current changedDate of the current state of the Work Item.
435+
This warning message will show up if the tool attempts to import a subsequent revision with a changedDate that is less than the current changedDate of the current state of the Work Item.
436436

437437
This can have multiple causes:
438438

439439
- Two consecutive revisions have the same date stamp, or the difference is 1ms or less (i.e. the issue is in the Jira issue data itself).
440440
- ADO can sometimes add a few milliseconds to the work item changedDate when adding an attachment.
441+
- A link change in Jira has created a situation where the Link Import happens earlier in time than the timestamp of one of the revisions in the associated issues.
441442

442443
You may end up receiving an error message similar to this one:
443444

444445
```txt
445-
[E][18:32:06] VS402625: Dates must be increasing with each revision.
446-
[E][18:32:06] Work Item 15312 failed to save.
446+
[W][11:15:29] Received response while updating Work Item: VS402625: Dates must be increasing with each revision.. Bumped Changed Date by 2ms and trying again... New ChangedDate: 3/31/2016 3:21:38 PM, ms: 172
447447
```
448448

449-
The solution is to buffer the subsequent revision's changedDate by a few miliseconds. This is the purpose of the configuration parameter `buffer-revisions-succeeding-attachment-imports-milliseconds` (default: 5). Add this parameter to your `config.json` file and try increasing the value by 1 (5, 6, 7, and so on...) until the import succeeds without errors.
449+
The tool is attempting to buffer the subsequent revision's changedDate by a few miliseconds in order to get around the error response from the ADO Rest API. The exact number of miliseconds to buffer can be controlled with the config parameter `changeddate-bump-ms` (default: 2). If you experience a lot of this warning message and believe that your import is slowing down because of it, go ahead and add this parameter to your `config.json` file and try increasing the value by 1 (3, 4, 5, and so on...) until the import succeeds without too many warnings.
450450

451451
Example `config.json`:
452452

453453
```json
454-
"buffer-revisions-succeeding-attachment-imports-milliseconds": 6,
454+
"changeddate-bump-ms": 5,
455455
```
456456

457457
## 20. Sprint names are corrupted. ADO Iteration paths are named "[ synced = false ]"

src/WorkItemMigrator/Migration.Common/Config/ConfigJson.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ public class ConfigJson
8181
[JsonProperty(PropertyName = "include-jira-css-styles")]
8282
public bool IncludeJiraCssStyles { get; set; } = false;
8383

84-
[JsonProperty(PropertyName = "buffer-revisions-succeeding-attachment-imports-milliseconds")]
85-
public int BufferRevisionsSucceedingAttachmentImportsMilliseconds { get; set; } = 5;
84+
[JsonProperty(PropertyName = "changeddate-bump-ms")]
85+
public int ChangedDateBumpMS { get; set; } = 2;
8686

8787
[JsonProperty(PropertyName = "ignore-empty-revisions")]
8888
public bool IgnoreEmptyRevisions { get; set; } = false;

src/WorkItemMigrator/WorkItemImport/Agent.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public bool ImportRevision(WiRevision rev, WorkItem wi, Settings settings)
6767
if (rev.Index == 0)
6868
_witClientUtils.EnsureClassificationFields(rev);
6969

70-
_witClientUtils.EnsureDateFields(rev, wi, settings.BufferRevisionsSucceedingAttachmentImportsMilliseconds);
70+
_witClientUtils.EnsureDateFields(rev, wi);
7171
_witClientUtils.EnsureAuthorFields(rev);
7272
_witClientUtils.EnsureAssigneeField(rev, wi);
7373
_witClientUtils.EnsureFieldsOnStateChange(rev, wi);
@@ -228,7 +228,7 @@ internal static Agent Initialize(MigrationContext context, Settings settings)
228228

229229
var agent = new Agent(context, settings, restConnection);
230230

231-
var witClientWrapper = new WitClientWrapper(settings.Account, settings.Project, settings.Pat);
231+
var witClientWrapper = new WitClientWrapper(settings.Account, settings.Project, settings.Pat, settings.ChangedDateBumpMS);
232232
agent._witClientUtils = new WitClientUtils(witClientWrapper);
233233

234234
// check if projects exists, if not create it

src/WorkItemMigrator/WorkItemImport/ImportCommandLine.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ private bool ExecuteMigration(CommandOption token, CommandOption url, CommandOpt
9494
IncludeDevelopmentLinks = config.IncludeDevelopmentLinks,
9595
FieldMap = config.FieldMap,
9696
SuppressNotifications = config.SuppressNotifications,
97-
BufferRevisionsSucceedingAttachmentImportsMilliseconds = config.BufferRevisionsSucceedingAttachmentImportsMilliseconds
97+
ChangedDateBumpMS = config.ChangedDateBumpMS
9898
};
9999

100100
// initialize Azure DevOps/TFS connection. Creates/fetches project, fills area and iteration caches.

src/WorkItemMigrator/WorkItemImport/Settings.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ public Settings(string account, string project, string pat)
2222
public bool IncludeDevelopmentLinks { get; internal set; }
2323
public FieldMap FieldMap { get; internal set; }
2424
public bool SuppressNotifications { get; internal set; }
25-
public int BufferRevisionsSucceedingAttachmentImportsMilliseconds { get; set; }
25+
public int ChangedDateBumpMS { get; set; }
2626
}
2727
}

src/WorkItemMigrator/WorkItemImport/WitClient/WitClientUtils.cs

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ public void EnsureAssigneeField(WiRevision rev, WorkItem wi)
240240
rev.Fields.Add(new WiField() { ReferenceName = WiFieldReference.AssignedTo, Value = assignedTo });
241241
}
242242

243-
public void EnsureDateFields(WiRevision rev, WorkItem wi, int bufferMS)
243+
public void EnsureDateFields(WiRevision rev, WorkItem wi)
244244
{
245245
if (rev == null)
246246
{
@@ -263,17 +263,7 @@ public void EnsureDateFields(WiRevision rev, WorkItem wi, int bufferMS)
263263
}
264264
if (!rev.Fields.HasAnyByRefName(WiFieldReference.ChangedDate))
265265
{
266-
DateTime workItemChangedDate = (DateTime)(wi.Fields[WiFieldReference.ChangedDate]);
267-
if (workItemChangedDate.ToUniversalTime() == rev.Time.ToUniversalTime())
268-
{
269-
rev.Fields.Add(new WiField() { ReferenceName = WiFieldReference.ChangedDate, Value = rev.Time.AddMilliseconds(1).ToString("o") });
270-
}
271-
else
272-
{
273-
// ADO can add a few milliseconds to work item createdDate when adding an attachment, hence adding more here to the revision time
274-
rev.Fields.Add(new WiField() { ReferenceName = WiFieldReference.ChangedDate, Value = rev.Time.AddMilliseconds(bufferMS).ToString("o") });
275-
wi.Fields[WiFieldReference.ChangedDate] = rev.Time.AddMilliseconds(bufferMS);
276-
}
266+
rev.Fields.Add(new WiField() { ReferenceName = WiFieldReference.ChangedDate, Value = rev.Time.ToString("o") });
277267
}
278268

279269
}

src/WorkItemMigrator/WorkItemImport/WitClient/WitClientWrapper.cs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,17 @@ public class WitClientWrapper : IWitClientWrapper
2828
private VssConnection Connection { get; }
2929
private TeamProjectReference TeamProject { get; }
3030
private GitHttpClient GitClient { get; }
31+
private int ChangedDateBumpMS { get; }
3132

32-
public WitClientWrapper(string collectionUri, string project, string personalAccessToken)
33+
public WitClientWrapper(string collectionUri, string project, string personalAccessToken, int changedDateBumpMS)
3334
{
3435
var credentials = new VssBasicCredential("", personalAccessToken);
3536
Connection = new VssConnection(new Uri(collectionUri), credentials);
3637
WitClient = Connection.GetClient<WorkItemTrackingHttpClient>();
3738
ProjectClient = Connection.GetClient<ProjectHttpClient>();
3839
TeamProject = ProjectClient.GetProject(project).Result;
3940
GitClient = Connection.GetClient<GitHttpClient>();
41+
ChangedDateBumpMS = changedDateBumpMS;
4042
}
4143

4244
public WorkItem CreateWorkItem(string wiType, bool suppressNotifications, DateTime? createdDate = null, string createdBy = "")
@@ -104,13 +106,48 @@ public WorkItem GetWorkItem(int wiId)
104106

105107
public WorkItem UpdateWorkItem(JsonPatchDocument patchDocument, int workItemId, bool suppressNotifications)
106108
{
107-
return WitClient.UpdateWorkItemAsync(
108-
document: patchDocument,
109-
id: workItemId,
110-
suppressNotifications: suppressNotifications,
111-
bypassRules: true,
112-
expand: WorkItemExpand.All
113-
).Result;
109+
while (true)
110+
{
111+
try
112+
{
113+
var result = WitClient.UpdateWorkItemAsync(
114+
document: patchDocument,
115+
id: workItemId,
116+
suppressNotifications: suppressNotifications,
117+
bypassRules: true,
118+
expand: WorkItemExpand.All
119+
).Result;
120+
return result;
121+
}
122+
catch (AggregateException ex)
123+
{
124+
bool datesMustIncreaseError = false;
125+
foreach (Exception ex2 in ex.InnerExceptions)
126+
{
127+
// Handle 'VS402625' error responses, the supplied ChangedDate was older than the latest revision already in ADO.
128+
// We must bump the ChangedDate by a small factor and try again.
129+
if (ex2.Message.Contains("VS402625"))
130+
{
131+
foreach (var patchOp in patchDocument)
132+
{
133+
if (patchOp.Path == "/fields/System.ChangedDate")
134+
{
135+
patchOp.Value = ((DateTime)patchOp.Value).AddMilliseconds(ChangedDateBumpMS);
136+
Logger.Log(LogLevel.Warning, $"Received response while updating Work Item: {ex2.Message}." +
137+
$" Bumped ChangedDate by {ChangedDateBumpMS}ms and trying again... New ChangedDate: {patchOp.Value}, ms: " +
138+
((DateTime)patchOp.Value).Millisecond);
139+
break;
140+
}
141+
}
142+
datesMustIncreaseError = true;
143+
}
144+
}
145+
if (!datesMustIncreaseError)
146+
{
147+
throw;
148+
}
149+
}
150+
}
114151
}
115152

116153
public TeamProject GetProject(string projectId)

src/WorkItemMigrator/tests/Migration.Wi-Import.Tests/WitClient/WitClientUtilsTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ public void When_calling_ensure_date_fields_with_empty_args_Then_an_exception_is
280280
MockedWitClientWrapper witClientWrapper = new MockedWitClientWrapper();
281281
WitClientUtils wiUtils = new WitClientUtils(witClientWrapper);
282282
Assert.That(
283-
() => wiUtils.EnsureDateFields(null, null, 0),
283+
() => wiUtils.EnsureDateFields(null, null),
284284
Throws.InstanceOf<ArgumentException>());
285285
}
286286

@@ -303,7 +303,7 @@ public void When_calling_ensure_date_fields_with_first_revision_Then_dates_are_a
303303

304304
createdWI.Fields[WiFieldReference.ChangedDate] = now;
305305

306-
wiUtils.EnsureDateFields(rev, createdWI, 0);
306+
wiUtils.EnsureDateFields(rev, createdWI);
307307

308308
Assert.Multiple(() =>
309309
{
@@ -315,7 +315,7 @@ public void When_calling_ensure_date_fields_with_first_revision_Then_dates_are_a
315315
Assert.That(rev.Fields[1].ReferenceName, Is.EqualTo(WiFieldReference.ChangedDate));
316316
Assert.That(
317317
DateTime.Parse(rev.Fields[1].Value.ToString()),
318-
Is.EqualTo(DateTime.Parse(rev.Fields[0].Value.ToString()).AddMilliseconds(1)));
318+
Is.EqualTo(DateTime.Parse(rev.Fields[0].Value.ToString())));
319319
});
320320
}
321321

0 commit comments

Comments
 (0)