/// <summary> /// Builds a list of strings where each string should be added as a comment to the GitHub issue. /// </summary> public static IEnumerable <string> GetFormattedComments(WorkItemDetails workItemDetails) { ArgValidate.IsNotNull(workItemDetails, nameof(workItemDetails)); var stringBuilder = new StringBuilder(); // Format and return all work item comments. foreach (WorkItemComment comment in workItemDetails.Comments.EmptyIfNull()) { string postedBy = comment.PostedBy; if (string.IsNullOrEmpty(postedBy)) { postedBy = CodePlexStrings.UnknownUser; } string commentHeading = string.Format(Resources.CommentPostedByPersonXOnDateY, postedBy, comment.PostedDate.ToString("d")); stringBuilder .AppendLine(commentHeading) .AppendLine(comment.Message) .AppendLine(); yield return(stringBuilder.ToString()); stringBuilder.Clear(); } // Format closing comment if any is specified. string closingComment = GetFormattedClosingComment(workItemDetails); if (!string.IsNullOrEmpty(closingComment)) { yield return(closingComment); } }
private async Task AddCommentsAsync(int issueNumber, WorkItemDetails workItemDetails) { foreach (string comment in TextUtilities.GetFormattedComments(workItemDetails)) { await issues.Comment.Create(owner : repoOwner, name : repo, number : issueNumber, newComment : comment); } }
private static string GetFormattedClosingComment(WorkItemDetails workItemDetails) { string closingComment = workItemDetails.WorkItem.ClosedComment; string closedBy = workItemDetails.WorkItem.ClosedBy; string reasonClosed = workItemDetails.WorkItem.ReasonClosed?.Name; // TODO: Produce a different comment if closedBy != null. if (!string.IsNullOrEmpty(closingComment) && !string.IsNullOrEmpty(closedBy) && !string.IsNullOrEmpty(reasonClosed)) { string closingCommentTitle = string.Format(Resources.IssueClosedByXWithComment, MarkdownFormatter.Italic(closedBy)); var stringBuilder = new StringBuilder() .AppendLine(MarkdownFormatter.Bold(closingCommentTitle)) .AppendLine(closingComment); if (!string.IsNullOrEmpty(reasonClosed) && !StringComparer.Equals(reasonClosed, CodePlexStrings.Unassigned)) { stringBuilder .AppendLine() .AppendLine(MarkdownFormatter.Bold(Resources.ReasonClosed)) .AppendLine(reasonClosed); } return(stringBuilder.ToString()); } return(string.Empty); }
/// <summary> /// Writes some of the work item information to the console. /// </summary> public Task WriteWorkItemAsync(WorkItemDetails workItemDetails) { ArgValidate.IsNotNull(workItemDetails, nameof(workItemDetails)); ArgValidate.IsNotNull(workItemDetails.WorkItem, nameof(workItemDetails.WorkItem)); string output = $"{workItemDetails.WorkItem.Id}{Environment.NewLine}{workItemDetails.WorkItem.Summary}{Environment.NewLine}{workItemDetails.WorkItem.Description}{Environment.NewLine}{Environment.NewLine}"; Console.WriteLine(output); return(Task.FromResult(false)); }
/// <summary> /// Searches for issue in GitHub and updates it. /// </summary> /// <remarks> /// This is only expected to happen if the initial write failed on a previous run. /// </remarks> public Task UpdateWorkItemAsync(WorkItemDetails workItemDetails) { ArgValidate.IsNotNull(workItemDetails, nameof(workItemDetails)); ArgValidate.IsNotNull(workItemDetails.WorkItem, nameof(workItemDetails.WorkItem)); return(InvokeAsync( async() => { Issue issue = await GetCorrespondingIssueAsync(workItemDetails.WorkItem.Id); await UpdateIssueAsync(issue, workItemDetails); return true; // This return statement is only here because we're expected to return Task<T>. })); }
/// <summary> /// Writes <paramref name="workItemDetails"/> as a new issue to GitHub. /// </summary> public Task WriteWorkItemAsync(WorkItemDetails workItemDetails) { ArgValidate.IsNotNull(workItemDetails, nameof(workItemDetails)); ArgValidate.IsNotNull(workItemDetails.WorkItem, nameof(workItemDetails.WorkItem)); return(InvokeAsync( async() => { Issue createdIssue = await CreateNewIssueAsync(workItemDetails.WorkItem, workItemDetails.FileAttachments); await SetIssueDetailsAsync(createdIssue, workItemDetails); return true; // This return statement is only here because we're expected to return Task<T>. })); }
private async Task UpdateIssueAsync(Issue issue, WorkItemDetails workItemDetails) { WorkItem workItem = workItemDetails.WorkItem; IssueUpdate issueUpdate = issue.ToUpdate(); issueUpdate.Title = workItem.Summary; issueUpdate.Body = TextUtilities.GetFormattedWorkItemBody(workItem, workItemDetails.FileAttachments); SetNewIssueLabels(issueUpdate.Labels, workItem); issue = await issues.Update(owner : repoOwner, name : repo, number : issue.Number, issueUpdate : issueUpdate); await DeleteAllCommentsAsync(issue.Number); await SetIssueDetailsAsync(issue, workItemDetails); }
/// <summary> /// Gets the details about an individual work item. /// </summary> public async Task <WorkItemDetails> GetWorkItemAsync(WorkItemSummary workItem) { ArgValidate.IsNotNull(workItem, nameof(workItem)); WorkItemDetails workItemToReturn = null; string detailedUrl = string.Format(IssuesDetailsUrlTemplate, project, workItem.Id); try { string workItemJson = await httpClient.DownloadStringAsync(detailedUrl); workItemToReturn = JsonConvert.DeserializeObject <WorkItemDetails>(workItemJson); } catch (JsonReaderException ex) { // If the object coming back from CodePlex cannot be parsed, something went wrong // on network so indicate to the calling method this could be retried. throw new HttpRequestFailedException(ex.Message, ex); } return(workItemToReturn); }
private async Task SetIssueDetailsAsync(Issue issue, WorkItemDetails workItemDetails) { await AddCommentsAsync(issue.Number, workItemDetails); await UpdateLabelsAndStateAsync(issue, closeIssue : workItemDetails.WorkItem.IsClosed()); }
/// <summary> /// Writes some of the work item information to the console /// </summary> /// <remarks> /// In this case because the writer is dumping the data to console, there is no way to update the work item so we just dump it to console. /// </remarks> public Task UpdateWorkItemAsync(WorkItemDetails value) => WriteWorkItemAsync(value);
/// <summary> /// Migrates all work items from <paramref name="source"/> to <paramref name="destination"/>. /// </summary> public static async Task MigrateAsync( IWorkItemSource source, IWorkItemDestination destination, MigrationSettings settings, ILogger logger) { ArgValidate.IsNotNull(source, nameof(source)); ArgValidate.IsNotNull(destination, nameof(destination)); ArgValidate.IsNotNull(logger, nameof(logger)); ArgValidate.IsNotNull(settings, nameof(settings)); logger.LogMessage(LogLevel.Info, Resources.BeginMigrationMessage); try { // Retrive all work items that have already been migrated. logger.LogMessage(LogLevel.Info, Resources.LookupMigratedWorkItemsMessage); IReadOnlyList <MigratedWorkItem> migratedWorkItems = await destination.GetMigratedWorkItemsAsync(); IDictionary <int, MigrationState> migratedWorkItemState = GetWorkItemDictionary(migratedWorkItems); // Update the skip list to include any work items the user indicated we should not retrieve. AddSkipItemsToMigrateState(settings.WorkItemsToSkip, migratedWorkItemState); // Get the list of potential work items to migrate. logger.LogMessage(LogLevel.Info, Resources.LookupWorkItemsToMigrate); IReadOnlyList <WorkItemSummary> notMigratedWorkItems = await source.GetWorkItemsAsync(id => GetMigrationState(migratedWorkItemState, id) != MigrationState.Migrated); // Get the actual list of work items to migrate taking into account the limit specified in migration settings. int countToMigrate = settings.MaxItemsToMigrate == -1 ? notMigratedWorkItems.Count : settings.MaxItemsToMigrate; logger.LogMessage(LogLevel.Info, Resources.StartingMigrationOfXWorkItems, countToMigrate); logger.LogMessage(LogLevel.Warning, Resources.ProgressOfMigrationWillBeSlow); IEnumerable <WorkItemSummary> workItemsToMigrate = notMigratedWorkItems.Take(countToMigrate); int migratedWorkItemCount = 0; // Download the details from CodePlex and push it to the destination. foreach (WorkItemSummary workItemSummary in workItemsToMigrate) { logger.LogMessage(LogLevel.Trace, Resources.LookupIndividualWorkItem, workItemSummary.Id); WorkItemDetails item = null; await RetryAsync( async() => item = await source.GetWorkItemAsync(workItemSummary), settings.MaxRetryCount, settings.RetryDelay); if (GetMigrationState(migratedWorkItemState, workItemSummary.Id) == MigrationState.PartiallyMigrated) { logger.LogMessage(LogLevel.Trace, Resources.UpdatingWorkItem, workItemSummary.Id); await RetryAsync(() => destination.UpdateWorkItemAsync(item), settings.MaxRetryCount, settings.RetryDelay); } else { logger.LogMessage(LogLevel.Trace, Resources.AddingWorkItem, workItemSummary.Id); await RetryAsync(() => destination.WriteWorkItemAsync(item), settings.MaxRetryCount, settings.RetryDelay); } logger.LogMessage(LogLevel.Info, Resources.SuccessfullyMigratedWorkItemIdXTitleY, workItemSummary.Id, ++migratedWorkItemCount, countToMigrate, workItemSummary.Title); } } catch (Exception ex) { logger.LogMessage(LogLevel.Error, Resources.LogExceptionMessage, ex.Message); throw; } logger.LogMessage(LogLevel.Info, Resources.MigrationCompletedSuccessfully); }