public async Task CanCreateUpdateRemoveAsync()
        {
            await ResetAsync();

            await _repository.RemoveAllAsync();

            await _client.RefreshAsync();

            Assert.Equal(0, await _repository.CountAsync());

            var stack = StackData.GenerateStack(projectId: TestConstants.ProjectId, organizationId: TestConstants.OrganizationId);

            Assert.Null(stack.Id);

            await _repository.AddAsync(stack);

            Assert.NotNull(stack.Id);
            await _client.RefreshAsync();

            stack = await _repository.GetByIdAsync(stack.Id);

            Assert.NotNull(stack);

            stack.Description = "New Description";
            await _repository.SaveAsync(stack);

            await _repository.RemoveAsync(stack.Id);
        }
        public async Task WillMarkAutoSessionHeartbeatStackHidden()
        {
            var firstEventDate = SystemClock.OffsetNow.Subtract(TimeSpan.FromMinutes(5));
            var events         = new List <PersistentEvent> {
                GenerateEvent(firstEventDate.AddSeconds(10), "*****@*****.**", Event.KnownTypes.SessionHeartbeat)
            };

            var contexts = await _pipeline.RunAsync(events);

            Assert.False(contexts.Any(c => c.HasError));
            Assert.Equal(0, contexts.Count(c => c.IsCancelled));
            Assert.Equal(1, contexts.Count(c => c.IsProcessed));

            await _configuration.Client.RefreshAsync();

            var results = await _eventRepository.GetAllAsync(new SortingOptions().WithField(Fields.Date));

            Assert.Equal(2, results.Total);
            Assert.Equal(1, results.Documents.Count(e => e.IsSessionStart()));

            var sessionHeartbeat = results.Documents.Single(e => e.IsSessionHeartbeat());

            Assert.NotNull(sessionHeartbeat);
            Assert.True(sessionHeartbeat.IsHidden);

            var stack = await _stackRepository.GetByIdAsync(sessionHeartbeat.StackId);

            Assert.NotNull(stack);
            Assert.True(stack.IsHidden);

            stack = await _stackRepository.GetByIdAsync(results.Documents.First(e => !e.IsSessionHeartbeat()).StackId);

            Assert.NotNull(stack);
            Assert.False(stack.IsHidden);
        }
        public async Task SyncStackTagsAsync()
        {
            await ResetAsync();

            const string Tag1           = "Tag One";
            const string Tag2           = "Tag Two";
            const string Tag2_Lowercase = "tag two";

            PersistentEvent ev = EventData.GenerateEvent(projectId: TestConstants.ProjectId, organizationId: TestConstants.OrganizationId, generateTags: false, occurrenceDate: DateTime.Now);

            ev.Tags.Add(Tag1);

            var pipeline = IoC.GetInstance <EventPipeline>();
            await pipeline.RunAsync(ev);

            await _client.RefreshAsync();

            ev = await _eventRepository.GetByIdAsync(ev.Id);

            Assert.NotNull(ev);
            Assert.NotNull(ev.StackId);

            var stack = await _stackRepository.GetByIdAsync(ev.StackId, true);

            Assert.Equal(new TagSet {
                Tag1
            }, stack.Tags);

            ev = EventData.GenerateEvent(stackId: ev.StackId, projectId: TestConstants.ProjectId, organizationId: TestConstants.OrganizationId, generateTags: false, occurrenceDate: DateTime.Now);
            ev.Tags.Add(Tag2);

            await pipeline.RunAsync(ev);

            await _client.RefreshAsync();

            stack = await _stackRepository.GetByIdAsync(ev.StackId, true);

            Assert.Equal(new TagSet {
                Tag1, Tag2
            }, stack.Tags);

            ev = EventData.GenerateEvent(stackId: ev.StackId, projectId: TestConstants.ProjectId, organizationId: TestConstants.OrganizationId, generateTags: false, occurrenceDate: DateTime.Now);
            ev.Tags.Add(Tag2_Lowercase);

            await pipeline.RunAsync(ev);

            await _client.RefreshAsync();

            stack = await _stackRepository.GetByIdAsync(ev.StackId, true);

            Assert.Equal(new TagSet {
                Tag1, Tag2
            }, stack.Tags);
        }
Example #4
0
        public async Task IncrementUsage_OnlyChangeCache()
        {
            var stack = await _stackRepository.AddAsync(StackData.GenerateStack(id: TestConstants.StackId, projectId: TestConstants.ProjectId, organizationId: TestConstants.OrganizationId), o => o.ImmediateConsistency());

            // Assert stack state in elasticsearch before increment usage
            Assert.Equal(0, stack.TotalOccurrences);
            Assert.True(stack.FirstOccurrence <= SystemClock.UtcNow);
            Assert.True(stack.LastOccurrence <= SystemClock.UtcNow);

            // Assert state in cache before increment usage
            Assert.Equal(DateTime.MinValue, await _cache.GetUnixTimeMillisecondsAsync(_stackService.GetStackOccurrenceMinDateCacheKey(stack.Id)));
            Assert.Equal(DateTime.MinValue, await _cache.GetUnixTimeMillisecondsAsync(_stackService.GetStackOccurrenceMaxDateCacheKey(stack.Id)));
            Assert.Equal(0, await _cache.GetAsync <long>(_stackService.GetStackOccurrenceCountCacheKey(stack.Id), 0));
            var occurrenceSet = await _cache.GetListAsync <(string OrganizationId, string ProjectId, string StackId)>(_stackService.GetStackOccurrenceSetCacheKey());

            Assert.True(occurrenceSet.IsNull || !occurrenceSet.HasValue || occurrenceSet.Value.Count == 0);

            var firstUtcNow = SystemClock.UtcNow.Floor(TimeSpan.FromMilliseconds(1));

            await RefreshDataAsync();

            await _stackService.IncrementStackUsageAsync(TestConstants.OrganizationId, TestConstants.ProjectId, stack.Id, firstUtcNow, firstUtcNow, 1);

            // Assert stack state has no change after increment usage
            stack = await _stackRepository.GetByIdAsync(TestConstants.StackId);

            Assert.Equal(0, stack.TotalOccurrences);
            Assert.True(stack.FirstOccurrence <= SystemClock.UtcNow);
            Assert.True(stack.LastOccurrence <= SystemClock.UtcNow);

            // Assert state in cache has been changed after increment usage
            Assert.Equal(firstUtcNow, await _cache.GetUnixTimeMillisecondsAsync(_stackService.GetStackOccurrenceMinDateCacheKey(stack.Id)));
            Assert.Equal(firstUtcNow, await _cache.GetUnixTimeMillisecondsAsync(_stackService.GetStackOccurrenceMaxDateCacheKey(stack.Id)));
            Assert.Equal(1, await _cache.GetAsync <long>(_stackService.GetStackOccurrenceCountCacheKey(stack.Id), 0));
            occurrenceSet = await _cache.GetListAsync <(string OrganizationId, string ProjectId, string StackId)>(_stackService.GetStackOccurrenceSetCacheKey());

            Assert.Single(occurrenceSet.Value);

            var secondUtcNow = SystemClock.UtcNow.Floor(TimeSpan.FromMilliseconds(1));

            await RefreshDataAsync();

            await _stackService.IncrementStackUsageAsync(TestConstants.OrganizationId, TestConstants.ProjectId, stack.Id, secondUtcNow, secondUtcNow, 2);

            // Assert state in cache has been changed after increment usage again
            Assert.Equal(firstUtcNow, await _cache.GetUnixTimeMillisecondsAsync(_stackService.GetStackOccurrenceMinDateCacheKey(stack.Id)));
            Assert.Equal(secondUtcNow, await _cache.GetUnixTimeMillisecondsAsync(_stackService.GetStackOccurrenceMaxDateCacheKey(stack.Id)));
            Assert.Equal(3, await _cache.GetAsync <long>(_stackService.GetStackOccurrenceCountCacheKey(stack.Id), 0));
            occurrenceSet = await _cache.GetListAsync <(string OrganizationId, string ProjectId, string StackId)>(_stackService.GetStackOccurrenceSetCacheKey());

            Assert.Single(occurrenceSet.Value);
        }
Example #5
0
        public async Task WillSetStackDuplicateSignature()
        {
            var stack = StackData.GenerateStack();

            stack.DuplicateSignature = null;
            stack = await _repository.AddAsync(stack, o => o.ImmediateConsistency());

            Assert.NotEmpty(stack.ProjectId);
            Assert.NotEmpty(stack.SignatureHash);
            Assert.Null(stack.DuplicateSignature);

            var migration = GetService <SetStackDuplicateSignature>();
            var context   = new MigrationContext(GetService <ILock>(), _logger, CancellationToken.None);
            await migration.RunAsync(context);

            string expectedDuplicateSignature = $"{stack.ProjectId}:{stack.SignatureHash}";
            var    actualStack = await _repository.GetByIdAsync(stack.Id);

            Assert.NotEmpty(actualStack.ProjectId);
            Assert.NotEmpty(actualStack.SignatureHash);
            Assert.Equal($"{actualStack.ProjectId}:{actualStack.SignatureHash}", actualStack.DuplicateSignature);

            var results = await _repository.FindAsync(q => q.ElasticFilter(Query <Stack> .Term(s => s.DuplicateSignature, expectedDuplicateSignature)));

            Assert.Single(results.Documents);
        }
    public async Task CanMarkAsRegressedAsync()
    {
        var stack = await _repository.AddAsync(StackData.GenerateStack(projectId: TestConstants.ProjectId, organizationId: TestConstants.OrganizationId, dateFixed: SystemClock.UtcNow.SubtractMonths(1)), o => o.ImmediateConsistency());

        Assert.NotNull(stack);
        Assert.False(stack.Status == Core.Models.StackStatus.Regressed);
        Assert.NotNull(stack.DateFixed);

        await _repository.MarkAsRegressedAsync(stack.Id);

        stack = await _repository.GetByIdAsync(stack.Id);

        Assert.NotNull(stack);
        Assert.True(stack.Status == Core.Models.StackStatus.Regressed);
        Assert.NotNull(stack.DateFixed);
    }
Example #7
0
        public async Task CanSearchByNonPremiumFields()
        {
            var ev = await SubmitErrorEventAsync();

            Assert.NotNull(ev.StackId);

            var stack = await _stackRepository.GetByIdAsync(ev.StackId);

            Assert.NotNull(stack);
            Assert.False(stack.IsFixed());

            var result = await SendRequestAsAsync <IReadOnlyCollection <Stack> >(r => r
                                                                                 .AsGlobalAdminUser()
                                                                                 .AppendPath("stacks")
                                                                                 .QueryString("f", "status:fixed")
                                                                                 .StatusCodeShouldBeOk());

            Assert.NotNull(result);
            Assert.Equal(1, result.Count);
        }
    public async Task CanCleanupSoftDeletedOrganization()
    {
        var organization = OrganizationData.GenerateSampleOrganization(_billingManager, _plans);

        organization.IsDeleted = true;
        await _organizationRepository.AddAsync(organization, o => o.ImmediateConsistency());

        var project = await _projectRepository.AddAsync(ProjectData.GenerateSampleProject(), o => o.ImmediateConsistency());

        var stack = await _stackRepository.AddAsync(StackData.GenerateSampleStack(), o => o.ImmediateConsistency());

        var persistentEvent = await _eventRepository.AddAsync(EventData.GenerateEvent(organization.Id, project.Id, stack.Id), o => o.ImmediateConsistency());

        await _job.RunAsync();

        Assert.Null(await _organizationRepository.GetByIdAsync(organization.Id, o => o.IncludeSoftDeletes()));
        Assert.Null(await _projectRepository.GetByIdAsync(project.Id, o => o.IncludeSoftDeletes()));
        Assert.Null(await _stackRepository.GetByIdAsync(stack.Id, o => o.IncludeSoftDeletes()));
        Assert.Null(await _eventRepository.GetByIdAsync(persistentEvent.Id, o => o.IncludeSoftDeletes()));
    }
    public async Task WillMergeDuplicatedStacks()
    {
        var utcNow        = SystemClock.UtcNow;
        var originalStack = StackData.GenerateStack();

        originalStack.Id = ObjectId.GenerateNewId().ToString();
        originalStack.TotalOccurrences = 100;
        var duplicateStack = originalStack.DeepClone();

        duplicateStack.Id               = ObjectId.GenerateNewId().ToString();
        duplicateStack.Status           = StackStatus.Fixed;
        duplicateStack.TotalOccurrences = 10;
        duplicateStack.LastOccurrence   = originalStack.LastOccurrence.AddMinutes(1);
        duplicateStack.SnoozeUntilUtc   = originalStack.SnoozeUntilUtc = null;
        duplicateStack.DateFixed        = duplicateStack.LastOccurrence.AddMinutes(1);
        duplicateStack.Tags.Add("stack2");
        duplicateStack.References.Add("stack2");
        duplicateStack.OccurrencesAreCritical = true;

        originalStack = await _stackRepository.AddAsync(originalStack, o => o.ImmediateConsistency());

        duplicateStack = await _stackRepository.AddAsync(duplicateStack, o => o.ImmediateConsistency());

        await _eventRepository.AddAsync(EventData.GenerateEvents(count: 100, stackId: originalStack.Id), o => o.ImmediateConsistency());

        await _eventRepository.AddAsync(EventData.GenerateEvents(count: 10, stackId: duplicateStack.Id), o => o.ImmediateConsistency());

        var results = await _stackRepository.FindAsync(q => q.ElasticFilter(Query <Stack> .Term(s => s.DuplicateSignature, originalStack.DuplicateSignature)));

        Assert.Equal(2, results.Total);

        var migration = GetService <FixDuplicateStacks>();
        var context   = new MigrationContext(GetService <ILock>(), _logger, CancellationToken.None);
        await migration.RunAsync(context);

        await RefreshDataAsync();

        results = await _stackRepository.FindAsync(q => q.ElasticFilter(Query <Stack> .Term(s => s.DuplicateSignature, originalStack.DuplicateSignature)));

        Assert.Single(results.Documents);

        var updatedOriginalStack = await _stackRepository.GetByIdAsync(originalStack.Id, o => o.IncludeSoftDeletes());

        Assert.False(updatedOriginalStack.IsDeleted);
        var updatedDuplicateStack = await _stackRepository.GetByIdAsync(duplicateStack.Id, o => o.IncludeSoftDeletes());

        Assert.True(updatedDuplicateStack.IsDeleted);

        Assert.Equal(originalStack.CreatedUtc, updatedOriginalStack.CreatedUtc);
        Assert.Equal(110, updatedOriginalStack.TotalOccurrences);
        Assert.Equal(StackStatus.Fixed, updatedOriginalStack.Status);
        Assert.Equal(duplicateStack.LastOccurrence, updatedOriginalStack.LastOccurrence);
        Assert.Null(updatedOriginalStack.SnoozeUntilUtc);
        Assert.Equal(duplicateStack.DateFixed, updatedOriginalStack.DateFixed);
        Assert.Equal(originalStack.Tags.Count + 1, updatedOriginalStack.Tags.Count);
        Assert.Contains("stack2", updatedOriginalStack.Tags);
        Assert.Equal(originalStack.References.Count + 1, updatedOriginalStack.References.Count);
        Assert.Contains("stack2", updatedOriginalStack.References);
        Assert.True(updatedOriginalStack.OccurrencesAreCritical);
    }
Example #10
0
        public async Task <IHttpActionResult> GetByStackAsync(string stackId, string filter = null, string time = null, string offset = null)
        {
            if (String.IsNullOrEmpty(stackId))
            {
                return(NotFound());
            }

            Stack stack = await _stackRepository.GetByIdAsync(stackId);

            if (stack == null || !CanAccessOrganization(stack.OrganizationId))
            {
                return(NotFound());
            }

            return(await GetInternalAsync(String.Concat("stack:", stackId), filter, time, offset));
        }
Example #11
0
        public async Task <IHttpActionResult> GetByStackAsync(string stackId, string filter = null, string sort = null, string time = null, string offset = null, string mode = null, int page = 1, int limit = 10)
        {
            if (String.IsNullOrEmpty(stackId))
            {
                return(NotFound());
            }

            var stack = await _stackRepository.GetByIdAsync(stackId, true);

            if (stack == null || !CanAccessOrganization(stack.OrganizationId))
            {
                return(NotFound());
            }

            return(await GetInternalAsync(String.Concat("stack:", stackId), filter, sort, time, offset, mode, page, limit));
        }
        protected async Task <IReadOnlyCollection <Organization> > GetSelectedOrganizationsAsync(IOrganizationRepository organizationRepository, IProjectRepository projectRepository, IStackRepository stackRepository, string filter = null)
        {
            var associatedOrganizationIds = GetAssociatedOrganizationIds();

            if (associatedOrganizationIds.Count == 0)
            {
                return(EmptyOrganizations);
            }

            if (!String.IsNullOrEmpty(filter))
            {
                var scope = GetFilterScopeVisitor.Run(filter);
                if (scope.IsScopable)
                {
                    Organization organization = null;
                    if (scope.OrganizationId != null)
                    {
                        organization = await organizationRepository.GetByIdAsync(scope.OrganizationId, o => o.Cache());
                    }
                    else if (scope.ProjectId != null)
                    {
                        var project = await projectRepository.GetByIdAsync(scope.ProjectId, o => o.Cache());

                        if (project != null)
                        {
                            organization = await organizationRepository.GetByIdAsync(project.OrganizationId, o => o.Cache());
                        }
                    }
                    else if (scope.StackId != null)
                    {
                        var stack = await stackRepository.GetByIdAsync(scope.StackId, o => o.Cache());

                        if (stack != null)
                        {
                            organization = await organizationRepository.GetByIdAsync(stack.OrganizationId, o => o.Cache());
                        }
                    }

                    if (organization != null)
                    {
                        if (associatedOrganizationIds.Contains(organization.Id) || Request.IsGlobalAdmin())
                        {
                            return new[] { organization }
                        }
        public override async Task <object> CreateFromEventAsync(WebHookDataContext ctx)
        {
            if (ctx.Event == null)
            {
                throw new ArgumentException("Event cannot be null.");
            }

            if (ctx.Project == null)
            {
                ctx.Project = await _projectRepository.GetByIdAsync(ctx.Event.ProjectId, o => o.Cache()).AnyContext();
            }

            if (ctx.Project == null)
            {
                throw new ArgumentException("Project not found.");
            }

            if (ctx.Organization == null)
            {
                ctx.Organization = await _organizationRepository.GetByIdAsync(ctx.Event.OrganizationId, o => o.Cache()).AnyContext();
            }

            if (ctx.Organization == null)
            {
                throw new ArgumentException("Organization not found.");
            }

            if (ctx.Stack == null)
            {
                ctx.Stack = await _stackRepository.GetByIdAsync(ctx.Event.StackId).AnyContext();
            }

            if (ctx.Stack == null)
            {
                throw new ArgumentException("Stack not found.");
            }

            return(null);
        }
        public async Task CanMarkAsRegressedAsync()
        {
            await _repository.AddAsync(StackData.GenerateStack(id: TestConstants.StackId, projectId: TestConstants.ProjectId, organizationId: TestConstants.OrganizationId, dateFixed: SystemClock.UtcNow.SubtractMonths(1)));

            var stack = await _repository.GetByIdAsync(TestConstants.StackId);

            Assert.NotNull(stack);
            Assert.False(stack.IsRegressed);
            Assert.NotNull(stack.DateFixed);

            await RefreshDataAsync();

            await _repository.MarkAsRegressedAsync(TestConstants.StackId);

            stack = await _repository.GetByIdAsync(TestConstants.StackId);

            Assert.NotNull(stack);
            Assert.True(stack.IsRegressed);
            Assert.NotNull(stack.DateFixed);
        }
        public override async Task ProcessBatchAsync(ICollection <EventContext> contexts)
        {
            var stacks = new Dictionary <string, Tuple <bool, Stack> >();

            foreach (var ctx in contexts)
            {
                if (String.IsNullOrEmpty(ctx.Event.StackId))
                {
                    // only add default signature info if no other signature info has been added
                    if (ctx.StackSignatureData.Count == 0)
                    {
                        ctx.StackSignatureData.AddItemIfNotEmpty("Type", ctx.Event.Type);
                        ctx.StackSignatureData.AddItemIfNotEmpty("Source", ctx.Event.Source);
                    }

                    string signatureHash = ctx.StackSignatureData.Values.ToSHA1();
                    ctx.SignatureHash = signatureHash;

                    if (stacks.TryGetValue(signatureHash, out var value))
                    {
                        ctx.Stack = value.Item2;
                    }
                    else
                    {
                        ctx.Stack = await _stackRepository.GetStackBySignatureHashAsync(ctx.Event.ProjectId, signatureHash).AnyContext();

                        if (ctx.Stack != null)
                        {
                            stacks.Add(signatureHash, Tuple.Create(false, ctx.Stack));
                        }
                    }

                    if (ctx.Stack == null)
                    {
                        _logger.LogTrace("Creating new event stack.");
                        ctx.IsNew = true;

                        string title = _formattingPluginManager.GetStackTitle(ctx.Event);
                        var    stack = new Stack {
                            OrganizationId     = ctx.Event.OrganizationId,
                            ProjectId          = ctx.Event.ProjectId,
                            SignatureInfo      = new SettingsDictionary(ctx.StackSignatureData),
                            SignatureHash      = signatureHash,
                            DuplicateSignature = ctx.Event.ProjectId + ":" + signatureHash,
                            Title            = title?.Truncate(1000),
                            Tags             = ctx.Event.Tags ?? new TagSet(),
                            Type             = ctx.Event.Type,
                            TotalOccurrences = 1,
                            FirstOccurrence  = ctx.Event.Date.UtcDateTime,
                            LastOccurrence   = ctx.Event.Date.UtcDateTime
                        };

                        if (ctx.Event.Type == Event.KnownTypes.Session)
                        {
                            stack.Status = StackStatus.Ignored;
                        }

                        ctx.Stack = stack;
                        stacks.Add(signatureHash, Tuple.Create(true, ctx.Stack));
                    }
                }
                else
                {
                    ctx.Stack = await _stackRepository.GetByIdAsync(ctx.Event.StackId, o => o.Cache()).AnyContext();

                    if (ctx.Stack == null || ctx.Stack.ProjectId != ctx.Event.ProjectId)
                    {
                        ctx.SetError("Invalid StackId.");
                        continue;
                    }

                    ctx.SignatureHash = ctx.Stack.SignatureHash;

                    if (!stacks.ContainsKey(ctx.Stack.SignatureHash))
                    {
                        stacks.Add(ctx.Stack.SignatureHash, Tuple.Create(false, ctx.Stack));
                    }
                    else
                    {
                        stacks[ctx.Stack.SignatureHash] = Tuple.Create(false, ctx.Stack);
                    }

                    if (ctx.Stack.Status == StackStatus.Discarded)
                    {
                        ctx.IsDiscarded = true;
                        ctx.IsCancelled = true;
                    }
                }

                if (!ctx.IsNew && ctx.Event.Tags != null && ctx.Event.Tags.Count > 0)
                {
                    if (ctx.Stack.Tags == null)
                    {
                        ctx.Stack.Tags = new TagSet();
                    }

                    var newTags = ctx.Event.Tags.Where(t => !ctx.Stack.Tags.Contains(t)).ToList();
                    if (newTags.Count > 0 || ctx.Stack.Tags.Count > 50 || ctx.Stack.Tags.Any(t => t.Length > 100))
                    {
                        ctx.Stack.Tags.AddRange(newTags);
                        ctx.Stack.Tags.RemoveExcessTags();
                        // make sure the stack gets saved
                        if (!stacks.ContainsKey(ctx.Stack.SignatureHash))
                        {
                            stacks.Add(ctx.Stack.SignatureHash, Tuple.Create(true, ctx.Stack));
                        }
                        else
                        {
                            stacks[ctx.Stack.SignatureHash] = Tuple.Create(true, stacks[ctx.Stack.SignatureHash].Item2);
                        }
                    }
                }

                ctx.Event.IsFirstOccurrence = ctx.IsNew;
            }

            var stacksToAdd = stacks.Where(kvp => kvp.Value.Item1 && String.IsNullOrEmpty(kvp.Value.Item2.Id)).Select(kvp => kvp.Value.Item2).ToList();

            if (stacksToAdd.Count > 0)
            {
                await _stackRepository.AddAsync(stacksToAdd, o => o.Cache().Notifications(stacksToAdd.Count == 1)).AnyContext();

                if (stacksToAdd.Count > 1)
                {
                    await _publisher.PublishAsync(new EntityChanged {
                        ChangeType = ChangeType.Added,
                        Type       = StackTypeName,
                        Data       =
                        {
                            { ExtendedEntityChanged.KnownKeys.OrganizationId, contexts.First().Organization.Id },
                            { ExtendedEntityChanged.KnownKeys.ProjectId,      contexts.First().Project.Id      }
                        }
                    }).AnyContext();
                }
            }

            var stacksToSave = stacks.Where(kvp => kvp.Value.Item1 && !String.IsNullOrEmpty(kvp.Value.Item2.Id)).Select(kvp => kvp.Value.Item2).ToList();

            if (stacksToSave.Count > 0)
            {
                await _stackRepository.SaveAsync(stacksToSave, o => o.Cache().Notifications(false)).AnyContext(); // notification will get sent later in the update stats step
            }
            // Set stack ids after they have been saved and created
            contexts.ForEach(ctx => {
                ctx.Event.StackId = ctx.Stack?.Id;
            });
        }
        protected override async Task <JobResult> ProcessQueueEntryAsync(JobQueueEntryContext <EventNotificationWorkItem> context)
        {
            var eventModel = await _eventRepository.GetByIdAsync(context.QueueEntry.Value.EventId).AnyContext();

            if (eventModel == null)
            {
                return(JobResult.FailedWithMessage($"Could not load event: {context.QueueEntry.Value.EventId}"));
            }

            var  eventNotification = new EventNotification(context.QueueEntry.Value, eventModel);
            bool shouldLog         = eventNotification.Event.ProjectId != Settings.Current.InternalProjectId;
            int  emailsSent        = 0;

            Logger.Trace().Message("Process notification: project={0} event={1} stack={2}", eventNotification.Event.ProjectId, eventNotification.Event.Id, eventNotification.Event.StackId).WriteIf(shouldLog);

            var project = await _projectRepository.GetByIdAsync(eventNotification.Event.ProjectId, true).AnyContext();

            if (project == null)
            {
                return(JobResult.FailedWithMessage($"Could not load project: {eventNotification.Event.ProjectId}."));
            }
            Logger.Trace().Message($"Loaded project: name={project.Name}").WriteIf(shouldLog);

            var organization = await _organizationRepository.GetByIdAsync(project.OrganizationId, true).AnyContext();

            if (organization == null)
            {
                return(JobResult.FailedWithMessage($"Could not load organization: {project.OrganizationId}"));
            }

            Logger.Trace().Message($"Loaded organization: {organization.Name}").WriteIf(shouldLog);

            var stack = await _stackRepository.GetByIdAsync(eventNotification.Event.StackId).AnyContext();

            if (stack == null)
            {
                return(JobResult.FailedWithMessage($"Could not load stack: {eventNotification.Event.StackId}"));
            }

            if (!organization.HasPremiumFeatures)
            {
                Logger.Info().Message("Skipping \"{0}\" because organization \"{1}\" does not have premium features.", eventNotification.Event.Id, eventNotification.Event.OrganizationId).WriteIf(shouldLog);
                return(JobResult.Success);
            }

            if (stack.DisableNotifications || stack.IsHidden)
            {
                Logger.Info().Message("Skipping \"{0}\" because stack \"{1}\" notifications are disabled or stack is hidden.", eventNotification.Event.Id, eventNotification.Event.StackId).WriteIf(shouldLog);
                return(JobResult.Success);
            }

            if (context.CancellationToken.IsCancellationRequested)
            {
                return(JobResult.Cancelled);
            }

            Logger.Trace().Message("Loaded stack: title={0}", stack.Title).WriteIf(shouldLog);
            int totalOccurrences = stack.TotalOccurrences;

            // after the first 2 occurrences, don't send a notification for the same stack more then once every 30 minutes
            var lastTimeSentUtc = await _cacheClient.GetAsync <DateTime>(String.Concat("notify:stack-throttle:", eventNotification.Event.StackId), DateTime.MinValue).AnyContext();

            if (totalOccurrences > 2 &&
                !eventNotification.IsRegression &&
                lastTimeSentUtc != DateTime.MinValue &&
                lastTimeSentUtc > DateTime.UtcNow.AddMinutes(-30))
            {
                Logger.Info().Message("Skipping message because of stack throttling: last sent={0} occurrences={1}", lastTimeSentUtc, totalOccurrences).WriteIf(shouldLog);
                return(JobResult.Success);
            }

            // don't send more than 10 notifications for a given project every 30 minutes
            var    projectTimeWindow = TimeSpan.FromMinutes(30);
            string cacheKey          = String.Concat("notify:project-throttle:", eventNotification.Event.ProjectId, "-", DateTime.UtcNow.Floor(projectTimeWindow).Ticks);
            long   notificationCount = await _cacheClient.IncrementAsync(cacheKey, 1, projectTimeWindow).AnyContext();

            if (notificationCount > 10 && !eventNotification.IsRegression)
            {
                Logger.Info().Project(eventNotification.Event.ProjectId).Message("Skipping message because of project throttling: count={0}", notificationCount).WriteIf(shouldLog);
                return(JobResult.Success);
            }

            if (context.CancellationToken.IsCancellationRequested)
            {
                return(JobResult.Cancelled);
            }

            foreach (var kv in project.NotificationSettings)
            {
                var settings = kv.Value;
                Logger.Trace().Message("Processing notification: user={0}", kv.Key).WriteIf(shouldLog);

                var user = await _userRepository.GetByIdAsync(kv.Key).AnyContext();

                if (String.IsNullOrEmpty(user?.EmailAddress))
                {
                    Logger.Error().Message("Could not load user {0} or blank email address {1}.", kv.Key, user != null ? user.EmailAddress : "").Write();
                    continue;
                }

                if (!user.IsEmailAddressVerified)
                {
                    Logger.Info().Message("User {0} with email address {1} has not been verified.", kv.Key, user != null ? user.EmailAddress : "").WriteIf(shouldLog);
                    continue;
                }

                if (!user.EmailNotificationsEnabled)
                {
                    Logger.Info().Message("User {0} with email address {1} has email notifications disabled.", kv.Key, user != null ? user.EmailAddress : "").WriteIf(shouldLog);
                    continue;
                }

                if (!user.OrganizationIds.Contains(project.OrganizationId))
                {
                    Logger.Error().Message("Unauthorized user: project={0} user={1} organization={2} event={3}", project.Id, kv.Key, project.OrganizationId, eventNotification.Event.Id).Write();
                    continue;
                }

                Logger.Trace().Message("Loaded user: email={0}", user.EmailAddress).WriteIf(shouldLog);

                bool shouldReportNewError      = settings.ReportNewErrors && eventNotification.IsNew && eventNotification.Event.IsError();
                bool shouldReportCriticalError = settings.ReportCriticalErrors && eventNotification.IsCritical && eventNotification.Event.IsError();
                bool shouldReportRegression    = settings.ReportEventRegressions && eventNotification.IsRegression;
                bool shouldReportNewEvent      = settings.ReportNewEvents && eventNotification.IsNew;
                bool shouldReportCriticalEvent = settings.ReportCriticalEvents && eventNotification.IsCritical;
                bool shouldReport = shouldReportNewError || shouldReportCriticalError || shouldReportRegression || shouldReportNewEvent || shouldReportCriticalEvent;

                Logger.Trace().Message("Settings: newerror={0} criticalerror={1} regression={2} new={3} critical={4}",
                                       settings.ReportNewErrors, settings.ReportCriticalErrors,
                                       settings.ReportEventRegressions, settings.ReportNewEvents, settings.ReportCriticalEvents).WriteIf(shouldLog);
                Logger.Trace().Message("Should process: newerror={0} criticalerror={1} regression={2} new={3} critical={4}",
                                       shouldReportNewError, shouldReportCriticalError,
                                       shouldReportRegression, shouldReportNewEvent, shouldReportCriticalEvent).WriteIf(shouldLog);

                var request = eventNotification.Event.GetRequestInfo();
                // check for known bots if the user has elected to not report them
                if (shouldReport && !String.IsNullOrEmpty(request?.UserAgent))
                {
                    var botPatterns = project.Configuration.Settings.ContainsKey(SettingsDictionary.KnownKeys.UserAgentBotPatterns)
                        ? project.Configuration.Settings.GetStringCollection(SettingsDictionary.KnownKeys.UserAgentBotPatterns).ToList()
                        : new List <string>();

                    var info = await _parser.ParseAsync(request.UserAgent, eventNotification.Event.ProjectId).AnyContext();

                    if (info != null && info.Device.IsSpider || request.UserAgent.AnyWildcardMatches(botPatterns))
                    {
                        shouldReport = false;
                        Logger.Info().Message("Skipping because event is from a bot \"{0}\".", request.UserAgent).WriteIf(shouldLog);
                    }
                }

                if (!shouldReport)
                {
                    continue;
                }

                var model = new EventNotificationModel(eventNotification)
                {
                    ProjectName      = project.Name,
                    TotalOccurrences = totalOccurrences
                };

                // don't send notifications in non-production mode to email addresses that are not on the outbound email list.
                if (Settings.Current.WebsiteMode != WebsiteMode.Production &&
                    !Settings.Current.AllowedOutboundAddresses.Contains(v => user.EmailAddress.ToLowerInvariant().Contains(v)))
                {
                    Logger.Info().Message("Skipping because email is not on the outbound list and not in production mode.").WriteIf(shouldLog);
                    continue;
                }

                Logger.Trace().Message("Sending email to {0}...", user.EmailAddress).Write();
                await _mailer.SendNoticeAsync(user.EmailAddress, model).AnyContext();

                emailsSent++;
                Logger.Trace().Message("Done sending email.").WriteIf(shouldLog);
            }

            // if we sent any emails, mark the last time a notification for this stack was sent.
            if (emailsSent > 0)
            {
                await _cacheClient.SetAsync(String.Concat("notify:stack-throttle:", eventNotification.Event.StackId), DateTime.UtcNow, DateTime.UtcNow.AddMinutes(15)).AnyContext();

                Logger.Info().Message("Notifications sent: event={0} stack={1} count={2}", eventNotification.Event.Id, eventNotification.Event.StackId, emailsSent).WriteIf(shouldLog);
            }

            return(JobResult.Success);
        }
Example #17
0
        public async Task CanMarkFixed(string version)
        {
            var ev = await SubmitErrorEventAsync();

            Assert.NotNull(ev.StackId);
            Assert.False(ev.IsFixed);

            var stack = await _stackRepository.GetByIdAsync(ev.StackId);

            Assert.NotNull(stack);
            Assert.False(stack.IsFixed());

            await _workItemQueue.DeleteQueueAsync();

            var workIds = await SendRequestAsAsync <WorkInProgressResult>(r => r
                                                                          .Post()
                                                                          .AsGlobalAdminUser()
                                                                          .AppendPath($"stacks/{stack.Id}/mark-fixed")
                                                                          .QueryStringIf(() => !String.IsNullOrEmpty(version), "version", version)
                                                                          .StatusCodeShouldBeAccepted());

            Assert.Single(workIds.Workers);

            var stats = await _workItemQueue.GetQueueStatsAsync();

            Assert.Equal(1, stats.Enqueued);
            Assert.Equal(0, stats.Completed);

            var job = GetService <WorkItemJob>();
            await job.RunAsync();

            await RefreshDataAsync();

            stats = await _workItemQueue.GetQueueStatsAsync();

            Assert.Equal(1, stats.Completed);

            stack = await _stackRepository.GetByIdAsync(ev.StackId);

            Assert.NotNull(stack);
            Assert.True(stack.IsFixed());

            ev = await _eventRepository.GetByIdAsync(ev.Id);

            Assert.NotNull(ev);
            Assert.True(ev.IsFixed);

            workIds = await SendRequestAsAsync <WorkInProgressResult>(r => r
                                                                      .Delete()
                                                                      .AsGlobalAdminUser()
                                                                      .AppendPath($"stacks/{stack.Id}/mark-fixed")
                                                                      .QueryStringIf(() => !String.IsNullOrEmpty(version), "version", version)
                                                                      .StatusCodeShouldBeAccepted());

            Assert.Single(workIds.Workers);

            stack = await _stackRepository.GetByIdAsync(ev.StackId);

            Assert.NotNull(stack);
            Assert.False(stack.IsFixed());

            await job.RunAsync();

            ev = await _eventRepository.GetByIdAsync(ev.Id);

            Assert.NotNull(ev);
            Assert.False(ev.IsFixed);
        }
    public async Task CanRunJobWithDiscardedEventUsage()
    {
        Log.MinimumLevel = LogLevel.Debug;

        var organization = await _organizationRepository.GetByIdAsync(TestConstants.OrganizationId);

        var usage = await _usageService.GetUsageAsync(organization);

        Assert.Equal(0, usage.MonthlyTotal);

        usage = await _usageService.GetUsageAsync(organization);

        Assert.Equal(0, usage.MonthlyTotal);
        Assert.Equal(0, usage.MonthlyBlocked);

        var ev = GenerateEvent(type: Event.KnownTypes.Log, source: "test", userIdentity: "test1");

        Assert.NotNull(await EnqueueEventPostAsync(ev));

        var result = await _job.RunAsync();

        Assert.True(result.IsSuccess);

        await RefreshDataAsync();

        var events = await _eventRepository.GetAllAsync();

        Assert.Equal(2, events.Total);
        var logEvent = events.Documents.Single(e => String.Equals(e.Type, Event.KnownTypes.Log));

        Assert.NotNull(logEvent);
        var sessionEvent = events.Documents.Single(e => String.Equals(e.Type, Event.KnownTypes.Session));

        Assert.NotNull(sessionEvent);

        usage = await _usageService.GetUsageAsync(organization);

        Assert.Equal(1, usage.MonthlyTotal);
        Assert.Equal(0, usage.MonthlyBlocked);

        // Mark the stack as discarded
        var logStack = await _stackRepository.GetByIdAsync(logEvent.StackId);

        logStack.Status = StackStatus.Discarded;
        await _stackRepository.SaveAsync(logStack, o => o.ImmediateConsistency());

        var sessionStack = await _stackRepository.GetByIdAsync(sessionEvent.StackId);

        sessionStack.Status = StackStatus.Discarded;
        await _stackRepository.SaveAsync(sessionStack, o => o.ImmediateConsistency());

        // Verify job processed discarded events.
        Assert.NotNull(await EnqueueEventPostAsync(new List <PersistentEvent> {
            GenerateEvent(type: Event.KnownTypes.Session, sessionId: "abcdefghi"),
            GenerateEvent(type: Event.KnownTypes.Log, source: "test", sessionId: "abcdefghi"),
            GenerateEvent(type: Event.KnownTypes.Log, source: "test", userIdentity: "test3")
        }));

        result = await _job.RunAsync();

        Assert.True(result.IsSuccess);

        await RefreshDataAsync();

        events = await _eventRepository.GetAllAsync();

        Assert.Equal(3, events.Total);

        usage = await _usageService.GetUsageAsync(organization);

        Assert.Equal(1, usage.MonthlyTotal);
        Assert.Equal(0, usage.MonthlyBlocked);
    }
Example #19
0
        public override async Task ProcessBatchAsync(ICollection <EventContext> contexts)
        {
            var stacks = new Dictionary <string, Tuple <bool, Stack> >();

            foreach (var ctx in contexts)
            {
                if (String.IsNullOrEmpty(ctx.Event.StackId))
                {
                    // only add default signature info if no other signature info has been added
                    if (ctx.StackSignatureData.Count == 0)
                    {
                        ctx.StackSignatureData.AddItemIfNotEmpty("Type", ctx.Event.Type);
                        ctx.StackSignatureData.AddItemIfNotEmpty("Source", ctx.Event.Source);
                    }

                    string signatureHash = ctx.StackSignatureData.Values.ToSHA1();
                    ctx.SignatureHash = signatureHash;

                    Tuple <bool, Stack> value;
                    if (stacks.TryGetValue(signatureHash, out value))
                    {
                        ctx.Stack = value.Item2;
                    }
                    else
                    {
                        ctx.Stack = await _stackRepository.GetStackBySignatureHashAsync(ctx.Event.ProjectId, signatureHash).AnyContext();

                        if (ctx.Stack != null)
                        {
                            stacks.Add(signatureHash, Tuple.Create(false, ctx.Stack));
                        }
                    }

                    if (ctx.Stack == null)
                    {
                        Logger.Trace().Message("Creating new event stack.").Write();
                        ctx.IsNew = true;

                        string title = _formattingPluginManager.GetStackTitle(ctx.Event);
                        var    stack = new Stack {
                            OrganizationId   = ctx.Event.OrganizationId,
                            ProjectId        = ctx.Event.ProjectId,
                            SignatureInfo    = new SettingsDictionary(ctx.StackSignatureData),
                            SignatureHash    = signatureHash,
                            Title            = title?.Truncate(1000),
                            Tags             = ctx.Event.Tags ?? new TagSet(),
                            Type             = ctx.Event.Type,
                            TotalOccurrences = 1,
                            FirstOccurrence  = ctx.Event.Date.UtcDateTime,
                            LastOccurrence   = ctx.Event.Date.UtcDateTime
                        };

                        ctx.Stack = stack;
                        stacks.Add(signatureHash, Tuple.Create(true, ctx.Stack));
                    }
                }
                else
                {
                    ctx.Stack = await _stackRepository.GetByIdAsync(ctx.Event.StackId, true).AnyContext();

                    if (ctx.Stack == null || ctx.Stack.ProjectId != ctx.Event.ProjectId)
                    {
                        ctx.SetError("Invalid StackId.");
                        continue;
                    }

                    ctx.SignatureHash = ctx.Stack.SignatureHash;

                    if (!stacks.ContainsKey(ctx.Stack.SignatureHash))
                    {
                        stacks.Add(ctx.Stack.SignatureHash, Tuple.Create(false, ctx.Stack));
                    }
                    else
                    {
                        stacks[ctx.Stack.SignatureHash] = Tuple.Create(false, ctx.Stack);
                    }
                }

                if (!ctx.IsNew && ctx.Event.Tags != null && ctx.Event.Tags.Count > 0)
                {
                    if (ctx.Stack.Tags == null)
                    {
                        ctx.Stack.Tags = new TagSet();
                    }

                    List <string> newTags = ctx.Event.Tags.Where(t => !ctx.Stack.Tags.Contains(t)).ToList();
                    if (newTags.Count > 0)
                    {
                        ctx.Stack.Tags.AddRange(newTags);
                        // make sure the stack gets saved
                        if (!stacks.ContainsKey(ctx.Stack.SignatureHash))
                        {
                            stacks.Add(ctx.Stack.SignatureHash, Tuple.Create(true, ctx.Stack));
                        }
                        else
                        {
                            stacks[ctx.Stack.SignatureHash] = Tuple.Create(true, stacks[ctx.Stack.SignatureHash].Item2);
                        }
                    }
                }

                ctx.Event.IsFirstOccurrence = ctx.IsNew;

                // sync the fixed and hidden flags to the error occurrence
                ctx.Event.IsFixed  = ctx.Stack.DateFixed.HasValue;
                ctx.Event.IsHidden = ctx.Stack.IsHidden;
            }

            var stacksToAdd = stacks.Where(kvp => kvp.Value.Item1 && String.IsNullOrEmpty(kvp.Value.Item2.Id)).Select(kvp => kvp.Value.Item2).ToList();

            if (stacksToAdd.Count > 0)
            {
                await _stackRepository.AddAsync(stacksToAdd, true, sendNotification : stacksToAdd.Count == 1).AnyContext();

                if (stacksToAdd.Count > 1)
                {
                    await _publisher.PublishAsync(new ExtendedEntityChanged { ChangeType = ChangeType.Added, Type = typeof(Stack).Name, OrganizationId = contexts.First().Organization.Id, ProjectId = contexts.First().Project.Id }).AnyContext();
                }
            }

            var stacksToSave = stacks.Where(kvp => kvp.Value.Item1 && !String.IsNullOrEmpty(kvp.Value.Item2.Id)).Select(kvp => kvp.Value.Item2).ToList();

            if (stacksToSave.Count > 0)
            {
                await _stackRepository.SaveAsync(stacksToSave, true, sendNotification : false).AnyContext(); // notification will get sent later in the update stats step
            }
            // Set stack ids after they have been saved and created
            contexts.ForEach(ctx => {
                ctx.Event.StackId = ctx.Stack?.Id;
            });
        }