Skip to content

Commit

Permalink
MergeTickets and MergeTicketsAsync API additions (#482)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
klkitchens and mozts2005 authored May 22, 2020
1 parent d6fa1c1 commit c2bd88f
Show file tree
Hide file tree
Showing 2 changed files with 214 additions and 0 deletions.
54 changes: 54 additions & 0 deletions src/ZendeskApi_v2/Requests/Tickets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ public interface ITickets : ICore

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

JobStatusResponse MergeTickets(long targetTicketId, IEnumerable<long> sourceTicketIds, string targetComment = "", string sourceComment = "", bool targetCommentPublic = false, bool sourceCommentPublic = false);

#endif

#if ASYNC
Expand Down Expand Up @@ -264,6 +266,8 @@ public interface ITickets : ICore

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

Task<JobStatusResponse> MergeTicketsAsync(long targetTicketId, IEnumerable<long> sourceTicketIds, string targetComment = "", string sourceComment = "", bool targetCommentPublic = false, bool sourceCommentPublic = false);

#endif
}

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

/// <summary>
/// Merges the source tickets in the "ids" list into the target ticket with comments as defined.
/// </summary>
/// <param name="targetTicketId">Id of ticket to be merged into.</param>
/// <param name="sourceTicketIds">List of ids of source tickets to be merged from.</param>
/// <param name="targetComment">Private comment to add to the target ticket (optional but recommended)</param>
/// <param name="sourceComment">Private comment to add to the source ticket(s) (optional but recommended)</param>
/// <param name="targetCommentPublic">Whether comment in target ticket is public or private (default = private)</param>
/// <param name="sourceCommentPublic">Whether comment in source ticket is public or private (default = private)</param>
/// <returns>JobStatusResponse</returns>
public JobStatusResponse MergeTickets(long targetTicketId, IEnumerable<long> sourceTicketIds, string targetComment = "", string sourceComment = "", bool targetCommentPublic = false, bool sourceCommentPublic = false)
{
return GenericPost<JobStatusResponse>(
$"{_tickets}/{targetTicketId}/merge.json",
new
{
ids = sourceTicketIds,
target_comment = targetComment,
source_comment = sourceComment,
target_comment_is_public = targetCommentPublic,
source_comment_is_public = sourceCommentPublic
});
}


public GroupUserResponse GetCollaborators(long id)
{
return GenericGet<GroupUserResponse>($"{_tickets}/{id}/collaborators.json");
Expand Down Expand Up @@ -980,6 +1009,31 @@ public async Task<bool> DeleteTicketFormAsync(long id)
return await GenericDeleteAsync($"{_ticket_forms}/{id}.json");
}

/// <summary>
/// Merges the source tickets in the "ids" list into the target ticket with comments as defined.
/// </summary>
/// <param name="targetTicketId">Id of ticket to be merged into.</param>
/// <param name="sourceTicketIds">List of ids of source tickets to be merged from.</param>
/// <param name="targetComment">Private comment to add to the target ticket (optional but recommended)</param>
/// <param name="sourceComment">Private comment to add to the source ticket(s) (optional but recommended)</param>
/// <param name="targetCommentPublic">Whether comment in target ticket is public or private (default = private)</param>
/// <param name="sourceCommentPublic">Whether comment in source ticket is public or private (default = private)</param>
/// <returns>JobStatusResponse</returns>
public async Task<JobStatusResponse> MergeTicketsAsync(long targetTicketId, IEnumerable<long> sourceTicketIds, string targetComment = "", string sourceComment = "", bool targetCommentPublic = false, bool sourceCommentPublic = false)
{
return await GenericPostAsync<JobStatusResponse>(
$"{_tickets}/{targetTicketId}/merge.json",
new
{
ids = sourceTicketIds,
target_comment = targetComment,
source_comment = sourceComment,
target_comment_is_public = targetCommentPublic,
source_comment_is_public = sourceCommentPublic,
});
}


#region TicketMetrics

public Task<GroupTicketMetricResponse> GetAllTicketMetricsAsync()
Expand Down
160 changes: 160 additions & 0 deletions test/ZendeskApi_v2.Test/TicketTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,166 @@ public void CanImportTicketAsync()
api.Tickets.DeleteAsync(res.Id);
}

[Test]
public void CanMergeTickets()
{
var sourceDescription = new List<string> { "This is a source ticket 1", "This is a source ticket 2" };
var targetDescription = "This is a the target ticket";

var sourceTicket1 = new Ticket
{
Subject = "Source Ticket 1",
Comment = new Comment { Body = sourceDescription[0], Public = true, }
};
var sourceTicket2 = new Ticket
{
Subject = "Source Ticket 2",
Comment = new Comment { Body = sourceDescription[1], Public=true, }
};
var targetTicket = new Ticket
{
Subject = "Target Ticket",
Comment = new Comment { Body = targetDescription, Public = true, }
};

var mergeIds = new List<long>();

var tick = api.Tickets.CreateTicket(sourceTicket1);
mergeIds.Add(tick.Ticket.Id.Value);
tick = api.Tickets.CreateTicket(sourceTicket2);
mergeIds.Add(tick.Ticket.Id.Value);
tick = api.Tickets.CreateTicket(targetTicket);
var targetTicketId = tick.Ticket.Id.Value;

var targetMergeComment =
$"Merged with ticket(s) {string.Join(", ", mergeIds.Select(m => $"#{m}").ToArray())}";
var sourceMergeComment = $"Closing in favor of #{targetTicketId}";

var res = api.Tickets.MergeTickets(
targetTicketId,
mergeIds,
targetMergeComment,
sourceMergeComment,
true,
true);

Assert.That(res.JobStatus.Status, Is.EqualTo("queued"));

do
{
Thread.Sleep(5000);
var job = api.JobStatuses.GetJobStatus(res.JobStatus.Id);
Assert.That(job.JobStatus.Id, Is.EqualTo(res.JobStatus.Id));

if (job.JobStatus.Status == "completed") break;
} while (true);

var counter = 0;
foreach (var id in mergeIds)
{
var oldTicket = api.Tickets.GetTicket(id);
Assert.That(oldTicket.Ticket.Id.Value, Is.EqualTo(id));
Assert.That(oldTicket.Ticket.Status, Is.EqualTo("closed"));

var oldComments = api.Tickets.GetTicketComments(id);
Assert.That(oldComments.Comments.Count, Is.EqualTo(2));
Assert.That(oldComments.Comments[0].Body, Is.EqualTo(sourceDescription[counter]));
Assert.That(oldComments.Comments[1].Body, Is.EqualTo(sourceMergeComment));

api.Tickets.DeleteAsync(id);
counter++;
}

var ticket = api.Tickets.GetTicket(targetTicketId);
Assert.That(ticket.Ticket.Id.Value, Is.EqualTo(targetTicketId));

var comments = api.Tickets.GetTicketComments(targetTicketId);
Assert.That(comments.Comments.Count, Is.EqualTo(2));
Assert.That(comments.Comments[0].Body, Is.EqualTo(targetDescription));
Assert.That(comments.Comments[1].Body, Is.EqualTo(targetMergeComment));

api.Tickets.DeleteAsync(targetTicketId);
}

[Test]
public async Task CanMergeTicketsAsync()
{
var sourceDescription = new List<string> { "This is a source ticket 1", "This is a source ticket 2" };
var targetDescription = "This is a the target ticket";

var sourceTicket1 = new Ticket
{
Subject = "Source Ticket 1",
Comment = new Comment { Body = sourceDescription[0], Public = true, }
};
var sourceTicket2 = new Ticket
{
Subject = "Source Ticket 2",
Comment = new Comment { Body = sourceDescription[1], Public = true, }
};
var targetTicket = new Ticket
{
Subject = "Target Ticket",
Comment = new Comment { Body = targetDescription, Public = true, }
};

var mergeIds = new List<long>();

var tick = await api.Tickets.CreateTicketAsync(sourceTicket1);
mergeIds.Add(tick.Ticket.Id.Value);
tick = await api.Tickets.CreateTicketAsync(sourceTicket2);
mergeIds.Add(tick.Ticket.Id.Value);
tick = await api.Tickets.CreateTicketAsync(targetTicket);
var targetTicketId = tick.Ticket.Id.Value;

var targetMergeComment =
$"Merged with ticket(s) {string.Join(", ", mergeIds.Select(m => $"#{m}").ToArray())}";
var sourceMergeComment = $"Closing in favor of #{targetTicketId}";

var res = await api.Tickets.MergeTicketsAsync(
targetTicketId,
mergeIds,
targetMergeComment,
sourceMergeComment);

Assert.That(res.JobStatus.Status, Is.EqualTo("queued"));

do
{
await Task.Delay(5000);
var job = await api.JobStatuses.GetJobStatusAsync(res.JobStatus.Id);
Assert.That(job.JobStatus.Id, Is.EqualTo(res.JobStatus.Id));

if (job.JobStatus.Status == "completed") break;
} while (true);

var counter = 0;
foreach (var id in mergeIds)
{
var oldTicket = await api.Tickets.GetTicketAsync(id);
Assert.That(oldTicket.Ticket.Id.Value, Is.EqualTo(id));
Assert.That(oldTicket.Ticket.Status, Is.EqualTo("closed"));

var oldComments = await api.Tickets.GetTicketCommentsAsync(id);
Assert.That(oldComments.Comments.Count, Is.EqualTo(2));
Assert.That(oldComments.Comments[0].Body, Is.EqualTo(sourceDescription[counter]));
Assert.That(oldComments.Comments[1].Body, Is.EqualTo(sourceMergeComment));

await api.Tickets.DeleteAsync(id);
counter++;
}

var ticket = await api.Tickets.GetTicketAsync(targetTicketId);
Assert.That(ticket.Ticket.Id.Value, Is.EqualTo(targetTicketId));

var comments = await api.Tickets.GetTicketCommentsAsync(targetTicketId);
Assert.That(comments.Comments.Count, Is.EqualTo(2));
Assert.That(comments.Comments[0].Body, Is.EqualTo(targetDescription));
Assert.That(comments.Comments[1].Body, Is.EqualTo(targetMergeComment));

await api.Tickets.DeleteAsync(targetTicketId);
}

[Test]
public void CanBulkImportTicket()
{
Expand Down

0 comments on commit c2bd88f

Please sign in to comment.