Skip to content

Commit c2bd88f

Browse files
MergeTickets and MergeTicketsAsync API additions (#482)
* Added MergeTickets functionality and tests - KLK * Cleaned up formatting preferences. * Style corrections. * Removed unnecessary using statement (not sure how it got there in the first place) * Added MergeTicketsAsync version and related Test. * Changed MergeTicketsAsync to use Async methods internally. Co-authored-by: Elizabeth Schneider <elizabeth@speedy-geek.com>
1 parent d6fa1c1 commit c2bd88f

File tree

2 files changed

+214
-0
lines changed

2 files changed

+214
-0
lines changed

src/ZendeskApi_v2/Requests/Tickets.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ public interface ITickets : ICore
151151

152152
GroupTicketResponse GetTicketsByExternalId(string externalId, int pageNumber = 0, int itemsPerPage = 0, TicketSideLoadOptionsEnum sideLoadOptions = TicketSideLoadOptionsEnum.None);
153153

154+
JobStatusResponse MergeTickets(long targetTicketId, IEnumerable<long> sourceTicketIds, string targetComment = "", string sourceComment = "", bool targetCommentPublic = false, bool sourceCommentPublic = false);
155+
154156
#endif
155157

156158
#if ASYNC
@@ -264,6 +266,8 @@ public interface ITickets : ICore
264266

265267
Task<JobStatusResponse> BulkImportTicketsAsync(IEnumerable<TicketImport> tickets);
266268

269+
Task<JobStatusResponse> MergeTicketsAsync(long targetTicketId, IEnumerable<long> sourceTicketIds, string targetComment = "", string sourceComment = "", bool targetCommentPublic = false, bool sourceCommentPublic = false);
270+
267271
#endif
268272
}
269273

@@ -470,6 +474,31 @@ public bool DeleteMultiple(IEnumerable<long> ids)
470474
return GenericDelete($"{_tickets}/destroy_many.json?ids={ids.ToCsv()}");
471475
}
472476

477+
/// <summary>
478+
/// Merges the source tickets in the "ids" list into the target ticket with comments as defined.
479+
/// </summary>
480+
/// <param name="targetTicketId">Id of ticket to be merged into.</param>
481+
/// <param name="sourceTicketIds">List of ids of source tickets to be merged from.</param>
482+
/// <param name="targetComment">Private comment to add to the target ticket (optional but recommended)</param>
483+
/// <param name="sourceComment">Private comment to add to the source ticket(s) (optional but recommended)</param>
484+
/// <param name="targetCommentPublic">Whether comment in target ticket is public or private (default = private)</param>
485+
/// <param name="sourceCommentPublic">Whether comment in source ticket is public or private (default = private)</param>
486+
/// <returns>JobStatusResponse</returns>
487+
public JobStatusResponse MergeTickets(long targetTicketId, IEnumerable<long> sourceTicketIds, string targetComment = "", string sourceComment = "", bool targetCommentPublic = false, bool sourceCommentPublic = false)
488+
{
489+
return GenericPost<JobStatusResponse>(
490+
$"{_tickets}/{targetTicketId}/merge.json",
491+
new
492+
{
493+
ids = sourceTicketIds,
494+
target_comment = targetComment,
495+
source_comment = sourceComment,
496+
target_comment_is_public = targetCommentPublic,
497+
source_comment_is_public = sourceCommentPublic
498+
});
499+
}
500+
501+
473502
public GroupUserResponse GetCollaborators(long id)
474503
{
475504
return GenericGet<GroupUserResponse>($"{_tickets}/{id}/collaborators.json");
@@ -980,6 +1009,31 @@ public async Task<bool> DeleteTicketFormAsync(long id)
9801009
return await GenericDeleteAsync($"{_ticket_forms}/{id}.json");
9811010
}
9821011

1012+
/// <summary>
1013+
/// Merges the source tickets in the "ids" list into the target ticket with comments as defined.
1014+
/// </summary>
1015+
/// <param name="targetTicketId">Id of ticket to be merged into.</param>
1016+
/// <param name="sourceTicketIds">List of ids of source tickets to be merged from.</param>
1017+
/// <param name="targetComment">Private comment to add to the target ticket (optional but recommended)</param>
1018+
/// <param name="sourceComment">Private comment to add to the source ticket(s) (optional but recommended)</param>
1019+
/// <param name="targetCommentPublic">Whether comment in target ticket is public or private (default = private)</param>
1020+
/// <param name="sourceCommentPublic">Whether comment in source ticket is public or private (default = private)</param>
1021+
/// <returns>JobStatusResponse</returns>
1022+
public async Task<JobStatusResponse> MergeTicketsAsync(long targetTicketId, IEnumerable<long> sourceTicketIds, string targetComment = "", string sourceComment = "", bool targetCommentPublic = false, bool sourceCommentPublic = false)
1023+
{
1024+
return await GenericPostAsync<JobStatusResponse>(
1025+
$"{_tickets}/{targetTicketId}/merge.json",
1026+
new
1027+
{
1028+
ids = sourceTicketIds,
1029+
target_comment = targetComment,
1030+
source_comment = sourceComment,
1031+
target_comment_is_public = targetCommentPublic,
1032+
source_comment_is_public = sourceCommentPublic,
1033+
});
1034+
}
1035+
1036+
9831037
#region TicketMetrics
9841038

9851039
public Task<GroupTicketMetricResponse> GetAllTicketMetricsAsync()

test/ZendeskApi_v2.Test/TicketTests.cs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,166 @@ public void CanImportTicketAsync()
11191119
api.Tickets.DeleteAsync(res.Id);
11201120
}
11211121

1122+
[Test]
1123+
public void CanMergeTickets()
1124+
{
1125+
var sourceDescription = new List<string> { "This is a source ticket 1", "This is a source ticket 2" };
1126+
var targetDescription = "This is a the target ticket";
1127+
1128+
var sourceTicket1 = new Ticket
1129+
{
1130+
Subject = "Source Ticket 1",
1131+
Comment = new Comment { Body = sourceDescription[0], Public = true, }
1132+
};
1133+
var sourceTicket2 = new Ticket
1134+
{
1135+
Subject = "Source Ticket 2",
1136+
Comment = new Comment { Body = sourceDescription[1], Public=true, }
1137+
};
1138+
var targetTicket = new Ticket
1139+
{
1140+
Subject = "Target Ticket",
1141+
Comment = new Comment { Body = targetDescription, Public = true, }
1142+
};
1143+
1144+
var mergeIds = new List<long>();
1145+
1146+
var tick = api.Tickets.CreateTicket(sourceTicket1);
1147+
mergeIds.Add(tick.Ticket.Id.Value);
1148+
tick = api.Tickets.CreateTicket(sourceTicket2);
1149+
mergeIds.Add(tick.Ticket.Id.Value);
1150+
tick = api.Tickets.CreateTicket(targetTicket);
1151+
var targetTicketId = tick.Ticket.Id.Value;
1152+
1153+
var targetMergeComment =
1154+
$"Merged with ticket(s) {string.Join(", ", mergeIds.Select(m => $"#{m}").ToArray())}";
1155+
var sourceMergeComment = $"Closing in favor of #{targetTicketId}";
1156+
1157+
var res = api.Tickets.MergeTickets(
1158+
targetTicketId,
1159+
mergeIds,
1160+
targetMergeComment,
1161+
sourceMergeComment,
1162+
true,
1163+
true);
1164+
1165+
Assert.That(res.JobStatus.Status, Is.EqualTo("queued"));
1166+
1167+
do
1168+
{
1169+
Thread.Sleep(5000);
1170+
var job = api.JobStatuses.GetJobStatus(res.JobStatus.Id);
1171+
Assert.That(job.JobStatus.Id, Is.EqualTo(res.JobStatus.Id));
1172+
1173+
if (job.JobStatus.Status == "completed") break;
1174+
} while (true);
1175+
1176+
var counter = 0;
1177+
foreach (var id in mergeIds)
1178+
{
1179+
var oldTicket = api.Tickets.GetTicket(id);
1180+
Assert.That(oldTicket.Ticket.Id.Value, Is.EqualTo(id));
1181+
Assert.That(oldTicket.Ticket.Status, Is.EqualTo("closed"));
1182+
1183+
var oldComments = api.Tickets.GetTicketComments(id);
1184+
Assert.That(oldComments.Comments.Count, Is.EqualTo(2));
1185+
Assert.That(oldComments.Comments[0].Body, Is.EqualTo(sourceDescription[counter]));
1186+
Assert.That(oldComments.Comments[1].Body, Is.EqualTo(sourceMergeComment));
1187+
1188+
api.Tickets.DeleteAsync(id);
1189+
counter++;
1190+
}
1191+
1192+
var ticket = api.Tickets.GetTicket(targetTicketId);
1193+
Assert.That(ticket.Ticket.Id.Value, Is.EqualTo(targetTicketId));
1194+
1195+
var comments = api.Tickets.GetTicketComments(targetTicketId);
1196+
Assert.That(comments.Comments.Count, Is.EqualTo(2));
1197+
Assert.That(comments.Comments[0].Body, Is.EqualTo(targetDescription));
1198+
Assert.That(comments.Comments[1].Body, Is.EqualTo(targetMergeComment));
1199+
1200+
api.Tickets.DeleteAsync(targetTicketId);
1201+
}
1202+
1203+
[Test]
1204+
public async Task CanMergeTicketsAsync()
1205+
{
1206+
var sourceDescription = new List<string> { "This is a source ticket 1", "This is a source ticket 2" };
1207+
var targetDescription = "This is a the target ticket";
1208+
1209+
var sourceTicket1 = new Ticket
1210+
{
1211+
Subject = "Source Ticket 1",
1212+
Comment = new Comment { Body = sourceDescription[0], Public = true, }
1213+
};
1214+
var sourceTicket2 = new Ticket
1215+
{
1216+
Subject = "Source Ticket 2",
1217+
Comment = new Comment { Body = sourceDescription[1], Public = true, }
1218+
};
1219+
var targetTicket = new Ticket
1220+
{
1221+
Subject = "Target Ticket",
1222+
Comment = new Comment { Body = targetDescription, Public = true, }
1223+
};
1224+
1225+
var mergeIds = new List<long>();
1226+
1227+
var tick = await api.Tickets.CreateTicketAsync(sourceTicket1);
1228+
mergeIds.Add(tick.Ticket.Id.Value);
1229+
tick = await api.Tickets.CreateTicketAsync(sourceTicket2);
1230+
mergeIds.Add(tick.Ticket.Id.Value);
1231+
tick = await api.Tickets.CreateTicketAsync(targetTicket);
1232+
var targetTicketId = tick.Ticket.Id.Value;
1233+
1234+
var targetMergeComment =
1235+
$"Merged with ticket(s) {string.Join(", ", mergeIds.Select(m => $"#{m}").ToArray())}";
1236+
var sourceMergeComment = $"Closing in favor of #{targetTicketId}";
1237+
1238+
var res = await api.Tickets.MergeTicketsAsync(
1239+
targetTicketId,
1240+
mergeIds,
1241+
targetMergeComment,
1242+
sourceMergeComment);
1243+
1244+
Assert.That(res.JobStatus.Status, Is.EqualTo("queued"));
1245+
1246+
do
1247+
{
1248+
await Task.Delay(5000);
1249+
var job = await api.JobStatuses.GetJobStatusAsync(res.JobStatus.Id);
1250+
Assert.That(job.JobStatus.Id, Is.EqualTo(res.JobStatus.Id));
1251+
1252+
if (job.JobStatus.Status == "completed") break;
1253+
} while (true);
1254+
1255+
var counter = 0;
1256+
foreach (var id in mergeIds)
1257+
{
1258+
var oldTicket = await api.Tickets.GetTicketAsync(id);
1259+
Assert.That(oldTicket.Ticket.Id.Value, Is.EqualTo(id));
1260+
Assert.That(oldTicket.Ticket.Status, Is.EqualTo("closed"));
1261+
1262+
var oldComments = await api.Tickets.GetTicketCommentsAsync(id);
1263+
Assert.That(oldComments.Comments.Count, Is.EqualTo(2));
1264+
Assert.That(oldComments.Comments[0].Body, Is.EqualTo(sourceDescription[counter]));
1265+
Assert.That(oldComments.Comments[1].Body, Is.EqualTo(sourceMergeComment));
1266+
1267+
await api.Tickets.DeleteAsync(id);
1268+
counter++;
1269+
}
1270+
1271+
var ticket = await api.Tickets.GetTicketAsync(targetTicketId);
1272+
Assert.That(ticket.Ticket.Id.Value, Is.EqualTo(targetTicketId));
1273+
1274+
var comments = await api.Tickets.GetTicketCommentsAsync(targetTicketId);
1275+
Assert.That(comments.Comments.Count, Is.EqualTo(2));
1276+
Assert.That(comments.Comments[0].Body, Is.EqualTo(targetDescription));
1277+
Assert.That(comments.Comments[1].Body, Is.EqualTo(targetMergeComment));
1278+
1279+
await api.Tickets.DeleteAsync(targetTicketId);
1280+
}
1281+
11221282
[Test]
11231283
public void CanBulkImportTicket()
11241284
{

0 commit comments

Comments
 (0)