From c75c1c51ba9ae26925d687c57bac34a17fbd8804 Mon Sep 17 00:00:00 2001 From: Scott Bommarito Date: Tue, 9 Oct 2018 12:09:17 -0700 Subject: [PATCH] Status - add export logic for two-layer aggregation (#566) --- .../Container/ContainerWrapper.cs | 29 ++++++ .../Container/IContainerWrapper.cs | 18 ++++ .../Export/ComponentExporter.cs | 91 +++++++++++++++++++ src/StatusAggregator/Export/EventExporter.cs | 55 +++++++++++ src/StatusAggregator/Export/EventsExporter.cs | 46 ++++++++++ .../Export/IComponentExporter.cs | 15 +++ src/StatusAggregator/Export/IEventExporter.cs | 17 ++++ .../Export/IEventsExporter.cs | 17 ++++ .../{ => Export}/IStatusExporter.cs | 9 +- .../Export/IStatusSerializer.cs | 18 ++++ src/StatusAggregator/Export/StatusExporter.cs | 41 +++++++++ .../Export/StatusSerializer.cs | 55 +++++++++++ .../Factory/AggregationStrategy.cs | 2 +- src/StatusAggregator/Job.cs | 37 ++++++-- src/StatusAggregator/StatusAggregator.cs | 13 +-- src/StatusAggregator/StatusAggregator.csproj | 15 ++- .../StatusContractResolver.cs | 73 --------------- src/StatusAggregator/StatusExporter.cs | 31 ------- 18 files changed, 458 insertions(+), 124 deletions(-) create mode 100644 src/StatusAggregator/Container/ContainerWrapper.cs create mode 100644 src/StatusAggregator/Container/IContainerWrapper.cs create mode 100644 src/StatusAggregator/Export/ComponentExporter.cs create mode 100644 src/StatusAggregator/Export/EventExporter.cs create mode 100644 src/StatusAggregator/Export/EventsExporter.cs create mode 100644 src/StatusAggregator/Export/IComponentExporter.cs create mode 100644 src/StatusAggregator/Export/IEventExporter.cs create mode 100644 src/StatusAggregator/Export/IEventsExporter.cs rename src/StatusAggregator/{ => Export}/IStatusExporter.cs (73%) create mode 100644 src/StatusAggregator/Export/IStatusSerializer.cs create mode 100644 src/StatusAggregator/Export/StatusExporter.cs create mode 100644 src/StatusAggregator/Export/StatusSerializer.cs delete mode 100644 src/StatusAggregator/StatusContractResolver.cs delete mode 100644 src/StatusAggregator/StatusExporter.cs diff --git a/src/StatusAggregator/Container/ContainerWrapper.cs b/src/StatusAggregator/Container/ContainerWrapper.cs new file mode 100644 index 000000000..332a20273 --- /dev/null +++ b/src/StatusAggregator/Container/ContainerWrapper.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.WindowsAzure.Storage.Blob; + +namespace StatusAggregator.Container +{ + public class ContainerWrapper : IContainerWrapper + { + private readonly CloudBlobContainer _container; + + public ContainerWrapper(CloudBlobContainer container) + { + _container = container; + } + + public Task CreateIfNotExistsAsync() + { + return _container.CreateIfNotExistsAsync(); + } + + public Task SaveBlobAsync(string name, string contents) + { + var blob = _container.GetBlockBlobReference(name); + return blob.UploadTextAsync(contents); + } + } +} \ No newline at end of file diff --git a/src/StatusAggregator/Container/IContainerWrapper.cs b/src/StatusAggregator/Container/IContainerWrapper.cs new file mode 100644 index 000000000..68f097cb7 --- /dev/null +++ b/src/StatusAggregator/Container/IContainerWrapper.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.WindowsAzure.Storage.Blob; + +namespace StatusAggregator.Container +{ + /// + /// Simple wrapper for that exists for unit-testing. + /// + public interface IContainerWrapper + { + Task CreateIfNotExistsAsync(); + + Task SaveBlobAsync(string name, string contents); + } +} \ No newline at end of file diff --git a/src/StatusAggregator/Export/ComponentExporter.cs b/src/StatusAggregator/Export/ComponentExporter.cs new file mode 100644 index 000000000..66c4da59d --- /dev/null +++ b/src/StatusAggregator/Export/ComponentExporter.cs @@ -0,0 +1,91 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.Extensions.Logging; +using NuGet.Jobs.Extensions; +using NuGet.Services.Status; +using NuGet.Services.Status.Table; +using StatusAggregator.Factory; +using StatusAggregator.Table; + +namespace StatusAggregator.Export +{ + public class ComponentExporter : IComponentExporter + { + private readonly ITableWrapper _table; + private readonly IComponentFactory _factory; + + private readonly ILogger _logger; + + public ComponentExporter( + ITableWrapper table, + IComponentFactory factory, + ILogger logger) + { + _table = table ?? throw new ArgumentNullException(nameof(table)); + _factory = factory ?? throw new ArgumentNullException(nameof(factory)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public IComponent Export() + { + using (_logger.Scope("Exporting active entities to component.")) + { + var rootComponent = _factory.Create(); + + // Apply the active entities to the component tree. + var activeEvents = _table + .GetActiveEntities() + .ToList() + .Where(e => + _table + .GetChildEntities(e) + .ToList() + .Any()) + .ToList(); + + _logger.LogInformation("Found {EventCount} active events with messages.", activeEvents.Count); + + var activeIncidentGroups = activeEvents + .SelectMany(e => + _table + .GetChildEntities(e) + .Where(i => i.IsActive) + .ToList()) + .ToList(); + + _logger.LogInformation("Found {GroupCount} active incident groups linked to active events with messages.", activeIncidentGroups.Count); + + var activeEntities = activeIncidentGroups + .Concat(activeEvents) + // Only apply entities with a non-Up status. + .Where(e => e.AffectedComponentStatus != (int)ComponentStatus.Up) + // If multiple events are affecting a single region, the event with the highest severity should affect the component. + .GroupBy(e => e.AffectedComponentPath) + .Select(g => g.OrderByDescending(e => e.AffectedComponentStatus).First()) + .ToList(); + + _logger.LogInformation("Active entities affect {PathCount} distinct subcomponents.", activeEntities.Count); + foreach (var activeEntity in activeEntities) + { + using (_logger.Scope("Applying active entity affecting {AffectedComponentPath} of severity {AffectedComponentStatus} at {StartTime} to root component", + activeEntity.AffectedComponentPath, activeEntity.AffectedComponentStatus, activeEntity.StartTime)) + { + var currentComponent = rootComponent.GetByPath(activeEntity.AffectedComponentPath); + + if (currentComponent == null) + { + throw new InvalidOperationException($"Couldn't find component with path {activeEntity.AffectedComponentPath} corresponding to active entities."); + } + + currentComponent.Status = (ComponentStatus)activeEntity.AffectedComponentStatus; + } + } + + return rootComponent; + } + } + } +} \ No newline at end of file diff --git a/src/StatusAggregator/Export/EventExporter.cs b/src/StatusAggregator/Export/EventExporter.cs new file mode 100644 index 000000000..eabf54942 --- /dev/null +++ b/src/StatusAggregator/Export/EventExporter.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Logging; +using NuGet.Jobs.Extensions; +using NuGet.Services.Status; +using NuGet.Services.Status.Table; +using StatusAggregator.Table; + +namespace StatusAggregator.Export +{ + public class EventExporter : IEventExporter + { + private readonly ITableWrapper _table; + private readonly ILogger _logger; + + public EventExporter( + ITableWrapper table, + ILogger logger) + { + _table = table ?? throw new ArgumentNullException(nameof(table)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public Event Export(EventEntity eventEntity) + { + using (_logger.Scope("Exporting event {EventRowKey}.", eventEntity.RowKey)) + { + var messages = _table.GetChildEntities(eventEntity) + .ToList() + // Don't show empty messages. + .Where(m => !string.IsNullOrEmpty(m.Contents)) + .ToList(); + + _logger.LogInformation("Event has {MessageCount} messages that are not empty.", messages.Count); + + if (!messages.Any()) + { + return null; + } + + return new Event( + eventEntity.AffectedComponentPath, + eventEntity.StartTime, + eventEntity.EndTime, + messages + .OrderBy(m => m.Time) + .Select(m => new Message(m.Time, m.Contents))); + } + } + } +} \ No newline at end of file diff --git a/src/StatusAggregator/Export/EventsExporter.cs b/src/StatusAggregator/Export/EventsExporter.cs new file mode 100644 index 000000000..c0bdda3d2 --- /dev/null +++ b/src/StatusAggregator/Export/EventsExporter.cs @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Logging; +using NuGet.Services.Status; +using NuGet.Services.Status.Table; +using StatusAggregator.Table; + +namespace StatusAggregator.Export +{ + public class EventsExporter : IEventsExporter + { + private readonly TimeSpan _eventVisibilityPeriod; + + private readonly ITableWrapper _table; + private readonly IEventExporter _exporter; + + private readonly ILogger _logger; + + public EventsExporter( + ITableWrapper table, + IEventExporter exporter, + StatusAggregatorConfiguration configuration, + ILogger logger) + { + _table = table ?? throw new ArgumentNullException(nameof(table)); + _exporter = exporter ?? throw new ArgumentNullException(nameof(exporter)); + _eventVisibilityPeriod = TimeSpan.FromDays(configuration?.EventVisibilityPeriodDays ?? throw new ArgumentNullException(nameof(configuration))); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public IEnumerable Export(DateTime cursor) + { + return _table + .CreateQuery() + .Where(e => e.IsActive || (e.EndTime >= cursor - _eventVisibilityPeriod)) + .ToList() + .Select(_exporter.Export) + .Where(e => e != null) + .ToList(); + } + } +} \ No newline at end of file diff --git a/src/StatusAggregator/Export/IComponentExporter.cs b/src/StatusAggregator/Export/IComponentExporter.cs new file mode 100644 index 000000000..3aad5befd --- /dev/null +++ b/src/StatusAggregator/Export/IComponentExporter.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using NuGet.Services.Status; + +namespace StatusAggregator.Export +{ + public interface IComponentExporter + { + /// + /// Exports the status of the current active entities to an . + /// + IComponent Export(); + } +} \ No newline at end of file diff --git a/src/StatusAggregator/Export/IEventExporter.cs b/src/StatusAggregator/Export/IEventExporter.cs new file mode 100644 index 000000000..a92761e19 --- /dev/null +++ b/src/StatusAggregator/Export/IEventExporter.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using NuGet.Services.Status; +using NuGet.Services.Status.Table; + +namespace StatusAggregator.Export +{ + public interface IEventExporter + { + /// + /// Exports as a . If it should not be exported, returns null. + /// + Event Export(EventEntity eventEntity); + } +} \ No newline at end of file diff --git a/src/StatusAggregator/Export/IEventsExporter.cs b/src/StatusAggregator/Export/IEventsExporter.cs new file mode 100644 index 000000000..e2261210d --- /dev/null +++ b/src/StatusAggregator/Export/IEventsExporter.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using NuGet.Services.Status; + +namespace StatusAggregator.Export +{ + public interface IEventsExporter + { + /// + /// Exports recent events. + /// + IEnumerable Export(DateTime cursor); + } +} \ No newline at end of file diff --git a/src/StatusAggregator/IStatusExporter.cs b/src/StatusAggregator/Export/IStatusExporter.cs similarity index 73% rename from src/StatusAggregator/IStatusExporter.cs rename to src/StatusAggregator/Export/IStatusExporter.cs index 0ed21b316..e60d81f66 100644 --- a/src/StatusAggregator/IStatusExporter.cs +++ b/src/StatusAggregator/Export/IStatusExporter.cs @@ -1,16 +1,17 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using NuGet.Services.Status; +using System; using System.Threading.Tasks; -namespace StatusAggregator +namespace StatusAggregator.Export { public interface IStatusExporter { /// /// Builds a and exports it to public storage so that it can be consumed by other services. /// - Task Export(); + Task Export(DateTime cursor); } -} +} \ No newline at end of file diff --git a/src/StatusAggregator/Export/IStatusSerializer.cs b/src/StatusAggregator/Export/IStatusSerializer.cs new file mode 100644 index 000000000..fea2d31c2 --- /dev/null +++ b/src/StatusAggregator/Export/IStatusSerializer.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using NuGet.Services.Status; + +namespace StatusAggregator.Export +{ + public interface IStatusSerializer + { + /// + /// Serializes and and saves to storage with a time of . + /// + Task Serialize(DateTime cursor, IComponent rootComponent, IEnumerable recentEvents); + } +} \ No newline at end of file diff --git a/src/StatusAggregator/Export/StatusExporter.cs b/src/StatusAggregator/Export/StatusExporter.cs new file mode 100644 index 000000000..467bf861d --- /dev/null +++ b/src/StatusAggregator/Export/StatusExporter.cs @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using NuGet.Jobs.Extensions; + +namespace StatusAggregator.Export +{ + public class StatusExporter : IStatusExporter + { + private readonly IComponentExporter _componentExporter; + private readonly IEventsExporter _eventExporter; + private readonly IStatusSerializer _serializer; + + private readonly ILogger _logger; + + public StatusExporter( + IComponentExporter componentExporter, + IEventsExporter eventExporter, + IStatusSerializer serializer, + ILogger logger) + { + _componentExporter = componentExporter ?? throw new ArgumentNullException(nameof(componentExporter)); + _eventExporter = eventExporter ?? throw new ArgumentNullException(nameof(eventExporter)); + _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public Task Export(DateTime cursor) + { + using (_logger.Scope("Exporting service status.")) + { + var rootComponent = _componentExporter.Export(); + var recentEvents = _eventExporter.Export(cursor); + return _serializer.Serialize(cursor, rootComponent, recentEvents); + } + } + } +} \ No newline at end of file diff --git a/src/StatusAggregator/Export/StatusSerializer.cs b/src/StatusAggregator/Export/StatusSerializer.cs new file mode 100644 index 000000000..d10340db5 --- /dev/null +++ b/src/StatusAggregator/Export/StatusSerializer.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using NuGet.Jobs.Extensions; +using NuGet.Services.Status; +using StatusAggregator.Container; + +namespace StatusAggregator.Export +{ + public class StatusSerializer : IStatusSerializer + { + public const string StatusBlobName = "status.json"; + + private readonly IContainerWrapper _container; + + private readonly ILogger _logger; + + public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings() + { + Converters = new List() { new StringEnumConverter() }, + DateTimeZoneHandling = DateTimeZoneHandling.Utc, + NullValueHandling = NullValueHandling.Ignore + }; + + public StatusSerializer( + IContainerWrapper container, + ILogger logger) + { + _container = container ?? throw new ArgumentNullException(nameof(container)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task Serialize(DateTime cursor, IComponent rootComponent, IEnumerable recentEvents) + { + ServiceStatus status; + string statusJson; + using (_logger.Scope("Serializing service status.")) + { + status = new ServiceStatus(cursor, rootComponent, recentEvents); + statusJson = JsonConvert.SerializeObject(status, Settings); + } + + using (_logger.Scope("Saving service status to blob storage.")) + { + await _container.SaveBlobAsync(StatusBlobName, statusJson); + } + } + } +} \ No newline at end of file diff --git a/src/StatusAggregator/Factory/AggregationStrategy.cs b/src/StatusAggregator/Factory/AggregationStrategy.cs index be302f27d..9615669b1 100644 --- a/src/StatusAggregator/Factory/AggregationStrategy.cs +++ b/src/StatusAggregator/Factory/AggregationStrategy.cs @@ -47,7 +47,7 @@ public async Task CanBeAggregatedByAsync(ParsedIncident input, TAggregatio // To guarantee that the aggregation reflects the latest information and is actually active, we must update it. await _aggregationUpdater.UpdateAsync(aggregationEntity, input.StartTime); - if (aggregationEntity.IsActive || !input.IsActive) + if (!aggregationEntity.IsActive || input.IsActive) { _logger.LogInformation("Cannot link entity to aggregation because it has been deactivated and the incident has not been."); return false; diff --git a/src/StatusAggregator/Job.cs b/src/StatusAggregator/Job.cs index 43aa73086..b8cedf87c 100644 --- a/src/StatusAggregator/Job.cs +++ b/src/StatusAggregator/Job.cs @@ -12,13 +12,14 @@ using Autofac.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection; using Microsoft.WindowsAzure.Storage; -using Microsoft.WindowsAzure.Storage.Blob; using Newtonsoft.Json.Linq; using NuGet.Jobs; using NuGet.Services.Incidents; using NuGet.Services.Status.Table; using NuGet.Services.Status.Table.Manual; using StatusAggregator.Collector; +using StatusAggregator.Container; +using StatusAggregator.Export; using StatusAggregator.Factory; using StatusAggregator.Manual; using StatusAggregator.Messages; @@ -45,6 +46,7 @@ public override void Init(IServiceContainer serviceContainer, IDictionary(); serviceCollection.AddTransient(); - serviceCollection.AddTransient(); serviceCollection.AddTransient(); } @@ -145,8 +146,8 @@ private static void AddStorage(ContainerBuilder containerBuilder) var storageAccount = ctx.ResolveNamed(name); return GetCloudBlobContainer(ctx, storageAccount); }) - .As() - .Named(name); + .As() + .Named(name); // We need to listen to manual status change updates from each storage. containerBuilder @@ -171,11 +172,12 @@ private static ITableWrapper GetTableWrapper(IComponentContext ctx, CloudStorage return new TableWrapper(storageAccount, configuration.TableName); } - private static CloudBlobContainer GetCloudBlobContainer(IComponentContext ctx, CloudStorageAccount storageAccount) + private static IContainerWrapper GetCloudBlobContainer(IComponentContext ctx, CloudStorageAccount storageAccount) { var blobClient = storageAccount.CreateCloudBlobClient(); var configuration = ctx.Resolve(); - return blobClient.GetContainerReference(configuration.ContainerName); + var container = blobClient.GetContainerReference(configuration.ContainerName); + return new ContainerWrapper(container); } private static void AddFactoriesAndUpdaters(ContainerBuilder containerBuilder) @@ -259,6 +261,29 @@ private static void AddEntityCollector(ContainerBuilder containerBuilder) }); } + private static void AddExporters(ContainerBuilder containerBuilder) + { + containerBuilder + .RegisterType() + .As(); + + containerBuilder + .RegisterType() + .As(); + + containerBuilder + .RegisterType() + .As(); + + containerBuilder + .RegisterType() + .As(); + + containerBuilder + .RegisterType() + .As(); + } + private const int _defaultEventStartMessageDelayMinutes = 15; private const int _defaultEventEndDelayMinutes = 15; private const int _defaultEventVisibilityPeriod = 10; diff --git a/src/StatusAggregator/StatusAggregator.cs b/src/StatusAggregator/StatusAggregator.cs index 16dd6d1b7..35457f757 100644 --- a/src/StatusAggregator/StatusAggregator.cs +++ b/src/StatusAggregator/StatusAggregator.cs @@ -1,26 +1,27 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.WindowsAzure.Storage.Blob; -using StatusAggregator.Table; -using StatusAggregator.Update; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using StatusAggregator.Container; +using StatusAggregator.Export; +using StatusAggregator.Table; +using StatusAggregator.Update; namespace StatusAggregator { public class StatusAggregator { - private readonly IEnumerable _containers; + private readonly IEnumerable _containers; private readonly IEnumerable _tables; private readonly IStatusUpdater _statusUpdater; private readonly IStatusExporter _statusExporter; public StatusAggregator( - IEnumerable containers, + IEnumerable containers, IEnumerable tables, IStatusUpdater statusUpdater, IStatusExporter statusExporter) @@ -39,7 +40,7 @@ public async Task Run(DateTime cursor) // Update and export the status. await _statusUpdater.Update(cursor); - await _statusExporter.Export(); + await _statusExporter.Export(cursor); } } } diff --git a/src/StatusAggregator/StatusAggregator.csproj b/src/StatusAggregator/StatusAggregator.csproj index 303c523fc..bedb433f4 100644 --- a/src/StatusAggregator/StatusAggregator.csproj +++ b/src/StatusAggregator/StatusAggregator.csproj @@ -46,6 +46,18 @@ + + + + + + + + + + + + @@ -61,7 +73,6 @@ - @@ -77,8 +88,6 @@ - - diff --git a/src/StatusAggregator/StatusContractResolver.cs b/src/StatusAggregator/StatusContractResolver.cs deleted file mode 100644 index cb9419116..000000000 --- a/src/StatusAggregator/StatusContractResolver.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections; -using System.Reflection; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; - -namespace StatusAggregator -{ - /// - /// Implementation of used by such that empty fields and arrays are not serialized. - /// - public class StatusContractResolver : DefaultContractResolver - { - protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) - { - JsonProperty property = base.CreateProperty(member, memberSerialization); - - var propertyType = property.PropertyType; - - if (propertyType == typeof(string)) - { - // Do not serialize strings if they are null or empty. - property.ShouldSerialize = instance => !string.IsNullOrEmpty((string)instance); - } - - if (typeof(IEnumerable).IsAssignableFrom(propertyType)) - { - SetShouldSerializeForIEnumerable(property, member); - } - - return property; - } - - private void SetShouldSerializeForIEnumerable(JsonProperty property, MemberInfo member) - { - Func getValue; - - // Create a function to get the value of the member using its type. - switch (member.MemberType) - { - case MemberTypes.Field: - getValue = instance => ((FieldInfo)member).GetValue(instance); - break; - case MemberTypes.Property: - getValue = instance => ((PropertyInfo)member).GetValue(instance); - break; - default: - return; - } - - // Do not serialize an IEnumerable if it is null or empty - property.ShouldSerialize = instance => - { - var value = (IEnumerable)getValue(instance); - - if (value == null) - { - return false; - } - - foreach (var obj in value) - { - return true; - } - - return false; - }; - } - } -} diff --git a/src/StatusAggregator/StatusExporter.cs b/src/StatusAggregator/StatusExporter.cs deleted file mode 100644 index 72ad54e62..000000000 --- a/src/StatusAggregator/StatusExporter.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using NuGet.Jobs.Extensions; -using NuGet.Services.Status; - -namespace StatusAggregator -{ - public class StatusExporter : IStatusExporter - { - private readonly ILogger _logger; - - public StatusExporter(ILogger logger) - { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task Export() - { - using (_logger.Scope("Exporting service status.")) - { - _logger.LogInformation("TODO"); - - return null; - } - } - } -}