/// <summary>
        /// Runs the smart signal.
        /// </summary>
        /// <param name="resources">The resources which the signal should run on.</param>
        /// <param name="analysisCadence">The analysis cadence.</param>
        /// <returns>A task that runs the smart signal.</returns>
        public async Task RunAsync(List <ResourceIdentifier> resources, TimeSpan analysisCadence)
        {
            this.cancellationTokenSource = new CancellationTokenSource();
            this.Results.Clear();
            var analysisRequest = new AnalysisRequest(resources, null, analysisCadence, this.analysisServicesFactory);

            try
            {
                // Run Signal
                this.IsSignalRunning = true;

                SmartSignalResult signalResults = await this.smartSignal.AnalyzeResourcesAsync(
                    analysisRequest,
                    this.Tracer,
                    this.cancellationTokenSource.Token);

                // Create signal result items
                List <SignalResultItem> signalResultItems = new List <SignalResultItem>();
                foreach (var resultItem in signalResults.ResultItems)
                {
                    // Create result item presentation
                    var resourceIds          = resources.Select(resource => resource.ResourceName).ToList();
                    var smartSignalsSettings = new SmartSignalSettings();
                    var smartSignalRequest   = new SmartSignalRequest(resourceIds, this.smartSignalManifes.Id, null, analysisCadence, smartSignalsSettings);
                    SmartSignalResultItemQueryRunInfo queryRunInfo = await this.queryRunInfoProvider.GetQueryRunInfoAsync(new List <ResourceIdentifier>() { resultItem.ResourceIdentifier }, this.cancellationTokenSource.Token);

                    SmartSignalResultItemPresentation resultItemPresentation = SmartSignalResultItemPresentation.CreateFromResultItem(
                        smartSignalRequest, this.smartSignalManifes.Name, resultItem, queryRunInfo);

                    // Create Azure resource identifier
                    ResourceIdentifier resourceIdentifier = ResourceIdentifier.CreateFromResourceId(resultItemPresentation.ResourceId);

                    signalResultItems.Add(new SignalResultItem(resultItemPresentation, resourceIdentifier));
                }

                this.Results = new ObservableCollection <SignalResultItem>(signalResultItems);
                this.tracer.TraceInformation($"Found {this.Results.Count} results");
            }
            catch (OperationCanceledException)
            {
                this.Tracer.TraceError("Signal run was canceled.");
            }
            catch (Exception e)
            {
                this.Tracer.ReportException(e);
            }
            finally
            {
                this.IsSignalRunning = false;
                this.cancellationTokenSource?.Dispose();
            }
        }
        /// <summary>
        /// Perform basic validations on the specified query run information.
        /// </summary>
        /// <param name="runInfo">The query run information</param>
        /// <param name="expectedType">The expected telemetry DB type</param>
        private void VerifyRunInfo(SmartSignalResultItemQueryRunInfo runInfo, TelemetryDbType expectedType)
        {
            // Verify the telemetry DB type
            if (runInfo.Type != expectedType)
            {
                throw new TelemetryDataClientCreationException($"Telemetry client creation failed - telemetry resource type is {runInfo.Type}");
            }

            // Verify that the resource IDs are not empty
            if (!runInfo.ResourceIds.Any())
            {
                throw new TelemetryDataClientCreationException("Telemetry client creation failed - no resources found");
            }
        }
        /// <summary>
        /// Creates an instance of <see cref="ITelemetryDataClient"/>, used for running queries against data in application insights.
        /// </summary>
        /// <param name="resources">The list of resources to analyze.</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <exception cref="TelemetryDataClientCreationException">An application insights telemetry data client could not be created for the specified resources.</exception>
        /// <returns>The telemetry data client, that can be used to run queries on application insights.</returns>
        public async Task <ITelemetryDataClient> CreateApplicationInsightsTelemetryDataClientAsync(IReadOnlyList <ResourceIdentifier> resources, CancellationToken cancellationToken)
        {
            // Get the query run info, and verify it
            SmartSignalResultItemQueryRunInfo runInfo = await this.queryRunInfoProvider.GetQueryRunInfoAsync(resources, cancellationToken);

            this.VerifyRunInfo(runInfo, TelemetryDbType.ApplicationInsights);

            // Get application Id (for the 1st application)
            ResourceIdentifier firstApplication   = ResourceIdentifier.CreateFromResourceId(runInfo.ResourceIds[0]);
            string             firstApplicationId = await this.azureResourceManagerClient.GetApplicationInsightsAppIdAsync(firstApplication, cancellationToken);

            // Create the client
            return(new ApplicationInsightsTelemetryDataClient(this.tracer, this.httpClientWrapper, this.credentialsFactory, firstApplicationId, resources.Select(resource => resource.ToResourceId()), this.queryTimeout));
        }
        /// <summary>
        /// Creates an instance of <see cref="ITelemetryDataClient"/>, used for running queries against data in log analytics workspaces.
        /// </summary>
        /// <param name="resources">The list of resources to analyze.</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <exception cref="TelemetryDataClientCreationException">A log analytics telemetry data client could not be created for the specified resources.</exception>
        /// <returns>The telemetry data client, that can be used to run queries on log analytics workspaces.</returns>
        public async Task <ITelemetryDataClient> CreateLogAnalyticsTelemetryDataClientAsync(IReadOnlyList <ResourceIdentifier> resources, CancellationToken cancellationToken)
        {
            // Get the query run info, and verify it
            SmartSignalResultItemQueryRunInfo runInfo = await this.queryRunInfoProvider.GetQueryRunInfoAsync(resources, cancellationToken);

            this.VerifyRunInfo(runInfo, TelemetryDbType.LogAnalytics);

            // Get workspace Id (for the 1st workspace)
            ResourceIdentifier firstWorkspace   = ResourceIdentifier.CreateFromResourceId(runInfo.ResourceIds[0]);
            string             firstWorkspaceId = await this.azureResourceManagerClient.GetLogAnalyticsWorkspaceIdAsync(firstWorkspace, cancellationToken);

            // Create the client
            return(new LogAnalyticsTelemetryDataClient(this.tracer, this.httpClientWrapper, this.credentialsFactory, firstWorkspaceId, runInfo.ResourceIds, this.queryTimeout));
        }
        public async Task WhenCreatingQueryRunInfoForApplicationInsightsResourcesThenTheCorrectInfoIsCreated()
        {
            var resources = new List <ResourceIdentifier>()
            {
                new ResourceIdentifier(ResourceType.ApplicationInsights, SubscriptionId, ResourceGroupName, ResourceName)
            };

            IQueryRunInfoProvider provider = new QueryRunInfoProvider(this.azureResourceManagerClientMock.Object);

            SmartSignalResultItemQueryRunInfo queryRunInfo = await provider.GetQueryRunInfoAsync(resources, default(CancellationToken));

            Assert.IsNotNull(queryRunInfo, "Query run information is null");
            Assert.AreEqual(TelemetryDbType.ApplicationInsights, queryRunInfo.Type, "Wrong telemetry DB type");
            CollectionAssert.AreEqual(new[] { resources.First().ToResourceId() }, queryRunInfo.ResourceIds.ToArray(), "Wrong resource IDs");
        }
        public async Task WhenCreatingQueryRunInfoForLogAnalyticsResourcesThenTheCorrectInfoIsCreated()
        {
            var resources = new List <ResourceIdentifier>()
            {
                new ResourceIdentifier(ResourceType.LogAnalytics, SubscriptionId, ResourceGroupName, WorkspaceNames[0]),
                new ResourceIdentifier(ResourceType.LogAnalytics, SubscriptionId, ResourceGroupName, WorkspaceNames[1])
            };

            string[] resourceIds = resources.Select(r => r.ToResourceId()).ToArray();
            IQueryRunInfoProvider             provider     = new QueryRunInfoProvider(this.azureResourceManagerClientMock.Object);
            SmartSignalResultItemQueryRunInfo queryRunInfo = await provider.GetQueryRunInfoAsync(resources, default(CancellationToken));

            Assert.IsNotNull(queryRunInfo, "Query run information is null");
            Assert.AreEqual(TelemetryDbType.LogAnalytics, queryRunInfo.Type, "Wrong telemetry DB type");
            CollectionAssert.AreEqual(resourceIds, queryRunInfo.ResourceIds.ToArray(), "Wrong resource IDs");
        }
        public async Task WhenCreatingQueryRunInfoForResourcesWithMultipleSubscriptionsThenAllWorkspacesAreReturned()
        {
            var resources = new List <ResourceIdentifier>()
            {
                new ResourceIdentifier(ResourceType.VirtualMachine, SubscriptionId + "1", ResourceGroupName + "1", ResourceName + "1"),
                new ResourceIdentifier(ResourceType.VirtualMachine, SubscriptionId + "2", ResourceGroupName + "2", ResourceName + "2")
            };

            var workspaces = new List <ResourceIdentifier>()
            {
                new ResourceIdentifier(ResourceType.LogAnalytics, SubscriptionId + "1", ResourceGroupName + "1", ResourceName + "1"),
                new ResourceIdentifier(ResourceType.LogAnalytics, SubscriptionId + "2", ResourceGroupName + "2", ResourceName + "2"),
                new ResourceIdentifier(ResourceType.LogAnalytics, SubscriptionId + "2", ResourceGroupName + "2", ResourceName + "3")
            };

            this.azureResourceManagerClientMock
            .Setup(x => x.GetAllResourcesInSubscriptionAsync(SubscriptionId + "1", It.IsAny <IEnumerable <ResourceType> >(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(new List <ResourceIdentifier>()
            {
                workspaces[0]
            });
            this.azureResourceManagerClientMock
            .Setup(x => x.GetAllResourcesInSubscriptionAsync(SubscriptionId + "2", It.IsAny <IEnumerable <ResourceType> >(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(new List <ResourceIdentifier>()
            {
                workspaces[1], workspaces[2]
            });
            this.azureResourceManagerClientMock
            .Setup(x => x.GetLogAnalyticsWorkspaceIdAsync(It.Is <ResourceIdentifier>(identifier => identifier.ResourceType == ResourceType.LogAnalytics), It.IsAny <CancellationToken>()))
            .ReturnsAsync("workspaceId");

            IQueryRunInfoProvider             provider     = new QueryRunInfoProvider(this.azureResourceManagerClientMock.Object);
            SmartSignalResultItemQueryRunInfo queryRunInfo = await provider.GetQueryRunInfoAsync(resources, default(CancellationToken));

            Assert.IsNotNull(queryRunInfo, "Query run information is null");
            this.azureResourceManagerClientMock.Verify(x => x.GetAllResourcesInSubscriptionAsync(SubscriptionId + "1", It.IsAny <IEnumerable <ResourceType> >(), It.IsAny <CancellationToken>()), Times.Once);
            this.azureResourceManagerClientMock.Verify(x => x.GetAllResourcesInSubscriptionAsync(SubscriptionId + "2", It.IsAny <IEnumerable <ResourceType> >(), It.IsAny <CancellationToken>()), Times.Once);
            CollectionAssert.AreEqual(workspaces.Select(w => w.ToResourceId()).ToArray(), queryRunInfo.ResourceIds.ToArray());
        }
        private SmartSignalResultItemPresentation CreatePresentation(SmartSignalResultItem resultItem, bool nullQueryRunInfo = false)
        {
            SmartSignalResultItemQueryRunInfo queryRunInfo = null;

            if (!nullQueryRunInfo)
            {
                queryRunInfo = new SmartSignalResultItemQueryRunInfo(
                    resultItem.ResourceIdentifier.ResourceType == ResourceType.ApplicationInsights ? TelemetryDbType.ApplicationInsights : TelemetryDbType.LogAnalytics,
                    new List <string>()
                {
                    "resourceId1", "resourceId2"
                });
            }

            DateTime lastExecutionTime = DateTime.Now.Date.AddDays(-1);
            string   resourceId        = "resourceId";
            var      request           = new SmartSignalRequest(new List <string>()
            {
                resourceId
            }, "signalId", lastExecutionTime, TimeSpan.FromDays(1), new SmartSignalSettings());

            return(SmartSignalResultItemPresentation.CreateFromResultItem(request, SignalName, resultItem, queryRunInfo));
        }
        /// <summary>
        /// Loads the signal, runs it, and returns the generated result presentations
        /// </summary>
        /// <param name="request">The signal request</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>A <see cref="Task{TResult}"/>, returning the list of Smart Signal result item presentations generated by the signal</returns>
        public async Task <List <SmartSignalResultItemPresentation> > RunAsync(SmartSignalRequest request, CancellationToken cancellationToken)
        {
            // Read the signal's package
            this.tracer.TraceInformation($"Loading signal package for signal ID {request.SignalId}");
            SmartSignalPackage signalPackage = await this.smartSignalRepository.ReadSignalPackageAsync(request.SignalId, cancellationToken);

            SmartSignalManifest signalManifest = signalPackage.Manifest;

            this.tracer.TraceInformation($"Read signal package, ID {signalManifest.Id}, Version {signalManifest.Version}");

            // Load the signal
            ISmartSignal signal = this.smartSignalLoader.LoadSignal(signalPackage);

            this.tracer.TraceInformation($"Signal instance loaded successfully, ID {signalManifest.Id}");

            // Get the resources on which to run the signal
            List <ResourceIdentifier> resources = await this.GetResourcesForSignal(request.ResourceIds, signalManifest, cancellationToken);

            // Run the signal
            this.tracer.TraceInformation($"Started running signal ID {signalManifest.Id}, Name {signalManifest.Name}");
            SmartSignalResult signalResult;

            try
            {
                var analysisRequest = new AnalysisRequest(resources, request.LastExecutionTime, request.Cadence, this.analysisServicesFactory);
                signalResult = await signal.AnalyzeResourcesAsync(analysisRequest, this.tracer, cancellationToken);

                this.tracer.TraceInformation($"Completed running signal ID {signalManifest.Id}, Name {signalManifest.Name}, returning {signalResult.ResultItems.Count} result items");
            }
            catch (Exception e)
            {
                this.tracer.TraceInformation($"Failed running signal ID {signalManifest.Id}, Name {signalManifest.Name}: {e.Message}");
                throw new SmartSignalCustomException(e.GetType().ToString(), e.Message, e.StackTrace);
            }

            // Verify that each result item belongs to one of the types declared in the signal manifest
            foreach (SmartSignalResultItem resultItem in signalResult.ResultItems)
            {
                if (!signalManifest.SupportedResourceTypes.Contains(resultItem.ResourceIdentifier.ResourceType))
                {
                    throw new UnidentifiedResultItemResourceTypeException(resultItem.ResourceIdentifier);
                }
            }

            // Trace the number of result items of each type
            foreach (var resultItemType in signalResult.ResultItems.GroupBy(x => x.GetType().Name))
            {
                this.tracer.TraceInformation($"Got {resultItemType.Count()} Smart Signal result items of type '{resultItemType.Key}'");
                this.tracer.ReportMetric("SignalResultItemType", resultItemType.Count(), new Dictionary <string, string>()
                {
                    { "ResultItemType", resultItemType.Key }
                });
            }

            // Create results
            List <SmartSignalResultItemPresentation> results = new List <SmartSignalResultItemPresentation>();

            foreach (var resultItem in signalResult.ResultItems)
            {
                SmartSignalResultItemQueryRunInfo queryRunInfo = await this.queryRunInfoProvider.GetQueryRunInfoAsync(new List <ResourceIdentifier>() { resultItem.ResourceIdentifier }, cancellationToken);

                results.Add(SmartSignalResultItemPresentation.CreateFromResultItem(request, signalManifest.Name, resultItem, queryRunInfo));
            }

            this.tracer.TraceInformation($"Returning {results.Count} results");
            return(results);
        }