コード例 #1
0
        /// <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);
            }
        }
コード例 #2
0
        /// <summary>
        /// Builds a string that is a hyper link in Markdown.
        /// </summary>
        public static string HyperLink(string anchorText, Uri url)
        {
            ArgValidate.IsNotNullNotEmpty(anchorText, nameof(anchorText));
            ArgValidate.IsNotNull(url, nameof(url));

            return($"[{anchorText}]({url})");
        }
コード例 #3
0
        /// <summary>
        /// Gets the list of work items that should be migrated.
        /// </summary>
        public async Task <IReadOnlyList <WorkItemSummary> > GetWorkItemsAsync(Func <int, bool> includePredicate)
        {
            ArgValidate.IsNotNull(includePredicate, nameof(includePredicate));

            string issuesListUrl = string.Format(IssuesListUrlTemplate, project, includeClosedWorkItems);

            var workItemSummaries = new List <WorkItemSummary>();

            // Get the first page of work items.
            PagedWorkItemList workItemList = await DownloadWorkItemSummaryPage(issuesListUrl);

            int totalWorkItems = workItemList.TotalItems;
            int itemsRetrieved = workItemList.WorkItemSummaries.Count;

            workItemSummaries.AddRange(workItemList.WorkItemSummaries);

            // Continue getting work item pages until we have all of them.
            while (itemsRetrieved < totalWorkItems)
            {
                string pagedUrl = string.Format(IssuesListUrlWithPagingTemplate, project, itemsRetrieved, includeClosedWorkItems);
                workItemList = await DownloadWorkItemSummaryPage(pagedUrl);

                itemsRetrieved += workItemList.WorkItemSummaries.Count;

                workItemSummaries.AddRange(workItemList.WorkItemSummaries);
            }

            // Now that we have the full list, consult the predicate to trim the list and return that.
            return(workItemSummaries.Where(w => includePredicate(w.Id)).ToArray());
        }
コード例 #4
0
        public async Task <IResponse> Send(IRequest request, CancellationToken cancellationToken = default(CancellationToken))
        {
            ArgValidate.IsNotNull(request, nameof(request));

            lock (lockObject)
            {
                DateTimeOffset requestTimeStamp = DateTimeOffset.UtcNow;

                // If we hit the request rate limit then we check to see if the oldest/first request on record occurred
                // more than one time interval ago. If this is the case we process the request immediately. Otherwise,
                // we wait until one time interval since our oldest request on record elapses.
                if (requestTimeStamps.Count == maxRequestsPerTimeInterval)
                {
                    DateTimeOffset oldestRequestTimeStamp = requestTimeStamps.Peek();
                    TimeSpan       timeSinceOldestRequest = requestTimeStamp - oldestRequestTimeStamp;

                    if (timeSinceOldestRequest < timeInterval)
                    {
                        Task.Delay(oldestRequestTimeStamp + timeInterval - requestTimeStamp).Wait(cancellationToken);
                    }

                    requestTimeStamps.Dequeue();
                }

                // We need to add the time *right now* cause this is (almost) the time the current request is getting processed.
                requestTimeStamps.Enqueue(DateTimeOffset.UtcNow);
            }

            return(await httpClient.Send(request, cancellationToken));
        }
コード例 #5
0
        /// <summary>
        /// Checks that the string is not null, empty or whitespace. Throws either ArgumentNullExcpetion or ArgumentException.
        /// </summary>
        public static void IsNotNullNotEmptyNotWhiteSpace(string s, string argName)
        {
            ArgValidate.IsNotNullNotEmpty(s, argName);

            if (string.IsNullOrWhiteSpace(s))
            {
                throw new ArgumentException(message: Resources.StringCannotBeAllWhiteSpace, paramName: argName);
            }
        }
コード例 #6
0
        /// <summary>
        /// Creates a <see cref="CodePlexWorkItemReader"/> object.
        /// </summary>
        public CodePlexWorkItemReader(string project, bool includeClosedWorkItems, IHttpClient httpClient)
        {
            ArgValidate.IsNotNullNotEmptyNotWhiteSpace(project, nameof(project));
            ArgValidate.IsNotNull(httpClient, nameof(httpClient));

            this.project = project;
            this.includeClosedWorkItems = includeClosedWorkItems;
            this.httpClient             = httpClient;
        }
コード例 #7
0
        /// <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>
        /// Creates a <see cref="GitHubRepoIssueReaderWriter"/> object. This object is used to interface with GitHub to manage issues that are being migrated.
        /// </summary>
        public GitHubRepoIssueReaderWriter(string repoOwner, string repo, IIssuesClient issues, ISearchClient search)
        {
            ArgValidate.IsNotNullNotEmptyNotWhiteSpace(repoOwner, nameof(repoOwner));
            ArgValidate.IsNotNullNotEmptyNotWhiteSpace(repo, nameof(repo));
            ArgValidate.IsNotNull(issues, nameof(issues));
            ArgValidate.IsNotNull(search, nameof(search));

            this.repoOwner = repoOwner;
            this.repo      = repo;
            this.issues    = issues;
            this.search    = search;
        }
コード例 #9
0
        /// <summary>
        /// Gets the response string from a uri and returns it as a string.
        /// </summary>
        /// <remarks>
        /// This will throw a <see cref="HttpRequestFailedException"/> on Web and HttpRequest exceptions.
        /// This is to allow for callers to catch these types of exceptions and potentially retry them.
        /// The prupose of this method/class is to allow tests to build a mock and avoid the actual web request.
        /// </remarks>
        public async Task <string> DownloadStringAsync(string uri)
        {
            ArgValidate.IsNotNullNotEmptyNotWhiteSpace(uri, nameof(uri));

            try
            {
                return(await httpClient.GetStringAsync(uri));
            }
            catch (Exception ex) when(ex is WebException || ex is HttpRequestException)
            {
                throw new HttpRequestFailedException($"Failed to get string from {uri}, error is {ex.Message}", ex);
            }
        }
        /// <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>.
            }));
        }
コード例 #12
0
        public Logger(string filePath)
        {
            ArgValidate.IsNotNullNotEmptyNotWhiteSpace(filePath, nameof(filePath));

            RollingFileAppender rollingFileAppender =
                (RollingFileAppender)LogManager
                .GetRepository()
                .GetAppenders()
                .SingleOrDefault(appender => appender is RollingFileAppender);

            if (rollingFileAppender != null)
            {
                rollingFileAppender.File = filePath;
                rollingFileAppender.ActivateOptions();
            }
        }
コード例 #13
0
        public RateLimitingHttpClientAdapter(Octokit.Internal.IHttpClient httpClient, TimeSpan timeInterval, int maxRequestsPerTimeInterval)
        {
            ArgValidate.IsNotNull(httpClient, nameof(httpClient));
            ArgValidate.IsInRange(maxRequestsPerTimeInterval, nameof(maxRequestsPerTimeInterval), min: 1, max: Int32.MaxValue);

            if (timeInterval <= TimeSpan.Zero)
            {
                throw new ArgumentException(message: Resources.TimeIntervalMustBeGreaterThanZero, paramName: nameof(timeInterval));
            }

            this.httpClient   = httpClient;
            this.timeInterval = timeInterval;
            this.maxRequestsPerTimeInterval = maxRequestsPerTimeInterval;
            requestTimeStamps = new Queue <DateTimeOffset>();
            lockObject        = new object();
        }
コード例 #14
0
        /// <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);
        }
コード例 #15
0
        /// <summary>
        /// Builds the string to write as the main body of a GitHub issue.
        /// </summary>
        public static string GetFormattedWorkItemBody(WorkItem workItem, IEnumerable <WorkItemFileAttachment> attachments)
        {
            ArgValidate.IsNotNull(workItem, nameof(workItem));

            var stringBuilder =
                new StringBuilder()
                .AppendLine(workItem.PlainDescription);

            string attachmentLinks = GetFormattedAttachmentHyperlinks(attachments);

            if (!string.IsNullOrEmpty(attachmentLinks))
            {
                stringBuilder
                .AppendLine()
                .Append(attachmentLinks);
            }

            return(stringBuilder
                   .AppendLine()
                   .Append(GetFormattedCodePlexWorkItemDetails(workItem))
                   .ToString());
        }
コード例 #16
0
        /// <summary>
        /// Returns the CodePlex work item ID from the body of a GitHub issue.
        /// </summary>
        public static int GetCodePlexWorkItemId(string issueBody)
        {
            ArgValidate.IsNotNull(issueBody, nameof(issueBody));

            Match match = CodePlexWorkItemIdRegex.Match(issueBody);

            if (match.Success)
            {
                string idString = match.Groups["ID"].Value;

                try
                {
                    return(Convert.ToInt32(idString));
                }
                catch (Exception ex) when(ex is FormatException || ex is OverflowException)
                {
                    throw new WorkItemIdentificationException(string.Format(Resources.InvalidCodePlexWorkItemId, idString), ex);
                }
            }

            throw new WorkItemIdentificationException(Resources.CodePlexWorkItemIdNotFoundInGitHubIssueBody);
        }
コード例 #17
0
        /// <summary>
        /// Builds a string that is an H4 in Markdown.
        /// </summary>
        public static string H4(string s)
        {
            ArgValidate.IsNotNullNotEmpty(s, nameof(s));

            return($"#### {s}");
        }
コード例 #18
0
        /// <summary>
        /// Builds a string that is italic in Markdown.
        /// </summary>
        public static string Italic(string s)
        {
            ArgValidate.IsNotNullNotEmpty(s, nameof(s));

            return($"_{s}_");
        }
コード例 #19
0
        /// <summary>
        /// Builds a string that is bold in Markdown.
        /// </summary>
        public static string Bold(string s)
        {
            ArgValidate.IsNotNullNotEmpty(s, nameof(s));

            return($"**{s}**");
        }
コード例 #20
0
        /// <summary>
        /// Checks whether the work item is closed.
        /// </summary>
        public static bool IsClosed(this WorkItem workItem)
        {
            ArgValidate.IsNotNull(workItem, nameof(workItem));

            return(string.Equals(workItem.Status?.Name, CodePlexStrings.Closed, StringComparison.OrdinalIgnoreCase));
        }
コード例 #21
0
        /// <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);
        }