/// <summary>
        /// Retrieve information about the default association between builds of a specific branch/repo
        /// and a channel.
        /// </summary>
        /// <returns></returns>
        public override async Task <int> ExecuteAsync()
        {
            try
            {
                IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

                IEnumerable <DefaultChannel> defaultChannels = (await remote.GetDefaultChannelsAsync()).Where(defaultChannel =>
                {
                    return((string.IsNullOrEmpty(_options.SourceRepository) ||
                            defaultChannel.Repository.Contains(_options.SourceRepository, StringComparison.OrdinalIgnoreCase)) &&
                           (string.IsNullOrEmpty(_options.Branch) ||
                            defaultChannel.Branch.Contains(_options.Branch, StringComparison.OrdinalIgnoreCase)) &&
                           (string.IsNullOrEmpty(_options.Channel) ||
                            defaultChannel.Channel.Name.Contains(_options.Channel, StringComparison.OrdinalIgnoreCase)));
                });

                if (defaultChannels.Count() == 0)
                {
                    Console.WriteLine("No matching channels were found.");
                }

                // Write out a simple list of each channel's name
                foreach (DefaultChannel defaultChannel in defaultChannels)
                {
                    Console.WriteLine(OutputHelpers.GetDefaultChannelDescriptionString(defaultChannel));
                }

                return(Constants.SuccessCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, "Error: Failed to retrieve default channel information.");
                return(Constants.ErrorCode);
            }
        }
        public async Task <IEnumerable <Subscription> > FilterSubscriptions(IRemote remote)
        {
            IEnumerable <DefaultChannel> defaultChannels = await remote.GetDefaultChannelsAsync();

            return((await remote.GetSubscriptionsAsync()).Where(subscription =>
            {
                return SubcriptionFilter(subscription, defaultChannels);
            }));
        }
        /// <summary>
        ///     Resolve channel based on the input options. If no channel could be resolved
        ///     based on the input options, returns null.
        /// </summary>
        /// <returns>Default channel or null</returns>
        protected async Task <DefaultChannel> ResolveSingleChannel()
        {
            IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

            IEnumerable <DefaultChannel> potentialDefaultChannels = await remote.GetDefaultChannelsAsync();

            // User should have supplied id or a combo of the channel name, repo, and branch.
            if (_options.Id != -1)
            {
                DefaultChannel defaultChannel = potentialDefaultChannels.SingleOrDefault(d => d.Id == _options.Id);
                if (defaultChannel == null)
                {
                    Console.WriteLine($"Could not find a default channel with id {_options.Id}");
                }
                return(defaultChannel);
            }
            else if (string.IsNullOrEmpty(_options.Repository) ||
                     string.IsNullOrEmpty(_options.Channel) ||
                     string.IsNullOrEmpty(_options.Branch))
            {
                Console.WriteLine("Please specify either the default channel id with --id or a combination of --channel, --branch and --repo");
                return(null);
            }

            // Otherwise, filter based on the other inputs. If more than one resolves, then print the possible
            // matches and return null
            var matchingChannels = potentialDefaultChannels.Where(d =>
            {
                return((string.IsNullOrEmpty(_options.Repository) || d.Repository.Contains(_options.Repository, StringComparison.OrdinalIgnoreCase)) &&
                       (string.IsNullOrEmpty(_options.Channel) || d.Channel.Name.Contains(_options.Channel, StringComparison.OrdinalIgnoreCase)) &&
                       (string.IsNullOrEmpty(_options.Branch) || d.Branch.Contains(GitHelpers.NormalizeBranchName(_options.Branch), StringComparison.OrdinalIgnoreCase)));
            });

            if (!matchingChannels.Any())
            {
                Console.WriteLine($"No channels found matching the specified criteria.");
                return(null);
            }
            else if (matchingChannels.Count() != 1)
            {
                Console.WriteLine($"More than one channel matching the specified criteria. Please change your options to be more specific.");
                foreach (DefaultChannel defaultChannel in matchingChannels)
                {
                    Console.WriteLine($"    {UxHelpers.GetDefaultChannelDescriptionString(defaultChannel)}");
                }
                return(null);
            }
            else
            {
                return(matchingChannels.Single());
            }
        }
        public async Task UpdateLongestBuildPathAsync(CancellationToken cancellationToken)
        {
            using (Logger.BeginScope($"Updating Longest Build Path table"))
            {
                List <Channel> channels = Context.Channels.Select(c => new Channel()
                {
                    Id = c.Id, Name = c.Name
                }).ToList();

                // Get the flow graph
                IRemote barOnlyRemote = await RemoteFactory.GetBarOnlyRemoteAsync(Logger);

                List <Microsoft.DotNet.Maestro.Client.Models.DefaultChannel> defaultChannels = (await barOnlyRemote.GetDefaultChannelsAsync()).ToList();
                List <Microsoft.DotNet.Maestro.Client.Models.Subscription>   subscriptions   = (await barOnlyRemote.GetSubscriptionsAsync()).ToList();

                IEnumerable <string> frequencies = new[] { "everyWeek", "twiceDaily", "everyDay", "everyBuild", "none", };

                Logger.LogInformation($"Will update '{channels.Count}' channels");

                foreach (var channel in channels)
                {
                    // Build, then prune out what we don't want to see if the user specified channels.
                    DependencyFlowGraph flowGraph = await DependencyFlowGraph.BuildAsync(defaultChannels, subscriptions, barOnlyRemote, 30);

                    flowGraph.PruneGraph(
                        node => DependencyFlowGraph.IsInterestingNode(channel.Name, node),
                        edge => DependencyFlowGraph.IsInterestingEdge(edge, false, frequencies));

                    if (flowGraph.Nodes.Count > 0)
                    {
                        flowGraph.MarkBackEdges();
                        flowGraph.CalculateLongestBuildPaths();
                        flowGraph.MarkLongestBuildPath();

                        // Get the nodes on the longest path and order them by path time so that the
                        // contributing repos are in the right order
                        List <DependencyFlowNode> longestBuildPathNodes = flowGraph.Nodes
                                                                          .Where(n => n.OnLongestBuildPath)
                                                                          .OrderByDescending(n => n.BestCasePathTime)
                                                                          .ToList();

                        LongestBuildPath lbp = new LongestBuildPath()
                        {
                            ChannelId                = channel.Id,
                            BestCaseTimeInMinutes    = longestBuildPathNodes.Max(n => n.BestCasePathTime),
                            WorstCaseTimeInMinutes   = longestBuildPathNodes.Max(n => n.WorstCasePathTime),
                            ContributingRepositories = String.Join(';', longestBuildPathNodes.Select(n => $"{n.Repository}@{n.Branch}").ToArray()),
                            ReportDate               = DateTimeOffset.UtcNow,
                        };

                        Logger.LogInformation($"Will update {channel.Name} to best case time {lbp.BestCaseTimeInMinutes} and worst case time {lbp.WorstCaseTimeInMinutes}");
                        await Context.LongestBuildPaths.AddAsync(lbp);
                    }
                }

                await Context.SaveChangesAsync();
            }
        }
        public override async Task <int> ExecuteAsync()
        {
            IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

            IEnumerable <Subscription> subscriptions = await remote.GetSubscriptionsAsync();

            IEnumerable <DefaultChannel> defaultChannels = await remote.GetDefaultChannelsAsync();

            IEnumerable <Channel> channels = await remote.GetChannelsAsync();

            HashSet <string> channelsToEvaluate = ComputeChannelsToEvaluate(channels);
            HashSet <string> reposToEvaluate    = ComputeRepositoriesToEvaluate(defaultChannels, subscriptions);

            // Print out what will be evaluated. If no channels or repos are in the initial sets, then
            // this is currently an error. Because different PKPIs apply to different input items differently,
            // this check may not be useful in the future.

            if (channelsToEvaluate.Any())
            {
                Console.WriteLine("Evaluating the following channels:");
                foreach (string channel in channelsToEvaluate)
                {
                    Console.WriteLine($"  {channel}");
                }
            }
            else
            {
                Console.WriteLine($"There were no channels found to evaluate based on inputs, exiting.");
                return(Constants.ErrorCode);
            }

            if (reposToEvaluate.Any())
            {
                Console.WriteLine("Evaluating the following repositories:");
                foreach (string repo in reposToEvaluate)
                {
                    Console.WriteLine($"  {repo}");
                }
            }
            else
            {
                Console.WriteLine($"There were no repositories found to evaluate based on inputs, exiting.");
                return(Constants.ErrorCode);
            }

            Console.WriteLine();

            // Compute metrics, then run in parallel.

            List <Func <Task <HealthMetricWithOutput> > > metricsToRun = ComputeMetricsToRun(channelsToEvaluate, reposToEvaluate,
                                                                                             subscriptions, defaultChannels, channels);

            // Run the metrics
            HealthMetricWithOutput[] results = await Task.WhenAll <HealthMetricWithOutput>(metricsToRun.Select(metric => metric()));

            // Walk through and print the results out
            bool passed = true;

            foreach (var healthResult in results)
            {
                if (healthResult.Metric.Result != HealthResult.Passed)
                {
                    passed = false;
                }

                Console.WriteLine($"{healthResult.Metric.MetricDescription} - ({healthResult.Metric.Result})");
                if (healthResult.Metric.Result != HealthResult.Passed)
                {
                    Console.WriteLine();
                    Console.WriteLine(healthResult.FormattedConsoleOutput);
                }
            }

            return(passed ? Constants.SuccessCode : Constants.ErrorCode);
        }