-
Notifications
You must be signed in to change notification settings - Fork 18
Add status classes and tests #170
Changes from 11 commits
15fd822
4e12b54
72f5ffd
1ffe907
9b15827
c20ff3d
88a025f
b5be64a
4c6bf3e
7fbd34e
bc7b870
c17481d
7eaf4e3
46d9624
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// 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; | ||
|
||
namespace NuGet.Services.Status | ||
{ | ||
/// <summary> | ||
/// Default implementation of <see cref="IComponent"/>. | ||
/// </summary> | ||
public abstract class Component : ReadOnlyComponent, IComponent | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you have examples somewhere of what the the hierarchy of these components is? What LeafComponents, PrimarySecondaryComponents, TreeComponents, etc. Could there be more specific name? NuGetServiceComponent? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To clarify: A consumer of status (gallery, status page) is going to only work with
I'll provide some examples for each
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest adding comments w/ your examples to the code. |
||
{ | ||
public new abstract ComponentStatus Status { get; set; } | ||
public new IEnumerable<IComponent> SubComponents { get; } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there another way to achieve this without the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately no, but I think from a consumer side this makes these classes a lot easier to use because I'm replacing the base properties in an intuitive way. I tried doing this without replacing any properties and it made the code substantially uglier. I replace I replace |
||
|
||
public Component( | ||
string name, | ||
string description) | ||
: base(name, description) | ||
{ | ||
SubComponents = Enumerable.Empty<IComponent>(); | ||
} | ||
|
||
public Component( | ||
string name, | ||
string description, | ||
IEnumerable<IComponent> subComponents) | ||
: base(name, description, subComponents) | ||
{ | ||
SubComponents = subComponents?.Select(s => new ComponentWrapper(s, this)).ToList() | ||
?? throw new ArgumentNullException(nameof(subComponents)); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// 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. | ||
|
||
namespace NuGet.Services.Status | ||
{ | ||
/// <summary> | ||
/// Describes whether or not a <see cref="IReadOnlyComponent"/> is performing as expected. | ||
/// </summary> | ||
public enum ComponentStatus | ||
{ | ||
/// <summary> | ||
/// The <see cref="IReadOnlyComponent"/> is performing as expected. | ||
/// </summary> | ||
Up, | ||
|
||
/// <summary> | ||
/// Some portion of the <see cref="IReadOnlyComponent"/> is not performing as expected. | ||
/// </summary> | ||
Degraded, | ||
|
||
/// <summary> | ||
/// The <see cref="IReadOnlyComponent"/> is completely unfunctional. | ||
/// </summary> | ||
Down | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// 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; | ||
|
||
namespace NuGet.Services.Status | ||
{ | ||
public static class ComponentUtility | ||
{ | ||
/// <summary> | ||
/// Concatenates a set of <see cref="IReadOnlyComponent.Name"/>s into a <see cref="IReadOnlyComponent.Path"/>. | ||
/// </summary> | ||
public static string GetPath(params string[] componentNames) | ||
{ | ||
componentNames = componentNames ?? throw new ArgumentNullException(nameof(componentNames)); | ||
|
||
return string.Join(Constants.ComponentPathDivider.ToString(), componentNames); | ||
} | ||
|
||
/// <summary> | ||
/// Gets the subcomponent of <paramref name="component"/> with <see cref="IReadOnlyComponent.Path"/> <paramref name="path"/>. | ||
/// If none exists, returns <c>null</c>. | ||
/// </summary> | ||
public static TComponent GetByPath<TComponent>(this TComponent component, string path) | ||
where TComponent : class, IReadOnlyComponent, IComponentRoot<TComponent> | ||
{ | ||
if (path == null) | ||
{ | ||
return null; | ||
} | ||
|
||
var componentNames = path.Split(Constants.ComponentPathDivider); | ||
return component.GetByNames(componentNames); | ||
} | ||
|
||
/// <summary> | ||
/// Gets the subcomponent of <paramref name="component"/> by iterating its subcomponents in the order of <paramref name="componentNames"/>. | ||
/// If none exists, returns <c>null</c>. | ||
/// </summary> | ||
public static TComponent GetByNames<TComponent>(this TComponent component, params string[] componentNames) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Extension methods let the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good catch |
||
where TComponent : class, IReadOnlyComponent, IComponentRoot<TComponent> | ||
{ | ||
if (component == null) | ||
{ | ||
return null; | ||
} | ||
|
||
if (componentNames == null) | ||
{ | ||
return null; | ||
} | ||
|
||
TComponent currentComponent = null; | ||
IComponentRoot<TComponent> currentRoot = new GetByNamesHelper<TComponent>(component); | ||
foreach (var componentName in componentNames) | ||
{ | ||
currentComponent = currentRoot.SubComponents.FirstOrDefault(c => c.Name == componentName); | ||
|
||
if (currentComponent == null) | ||
{ | ||
break; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If I run There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good catch! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was a bug. I added tests to make sure it doesn't break again. |
||
} | ||
|
||
currentRoot = currentComponent; | ||
} | ||
|
||
return currentComponent; | ||
} | ||
|
||
/// <remarks> | ||
/// Dummy implementation of <see cref="IComponentRoot{TComponent}"/> used by <see cref="GetByNames{TComponent}(TComponent, string[])"/>. | ||
/// </remarks> | ||
private class GetByNamesHelper<TComponent> : IComponentRoot<TComponent> | ||
where TComponent : IReadOnlyComponent | ||
{ | ||
public IEnumerable<TComponent> SubComponents { get; } | ||
|
||
public GetByNamesHelper(TComponent root) | ||
{ | ||
SubComponents = new[] { root }; | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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.Collections.Generic; | ||
using System.Linq; | ||
|
||
namespace NuGet.Services.Status | ||
{ | ||
/// <summary> | ||
/// Wrapper class for <see cref="IComponent"/> that sets <see cref="IReadOnlyComponent.Path"/> as expected. | ||
/// </summary> | ||
internal class ComponentWrapper : ReadOnlyComponentWrapper, IComponent | ||
{ | ||
private readonly IComponent _component; | ||
private readonly IComponent _parent; | ||
|
||
public new ComponentStatus Status { get { return _component.Status; } set { _component.Status = value; } } | ||
public new IEnumerable<IComponent> SubComponents { get; } | ||
|
||
public ComponentWrapper(IComponent component, IComponent parent) | ||
: base(component, parent) | ||
{ | ||
_component = component; | ||
_parent = parent; | ||
SubComponents = _component.SubComponents?.Select(s => new ComponentWrapper(s, this)).ToList() | ||
?? Enumerable.Empty<IComponent>(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
|
||
namespace NuGet.Services.Status | ||
{ | ||
public static class Constants | ||
{ | ||
/// <summary> | ||
/// Used to construct <see cref="IReadOnlyComponent.Path"/>. | ||
/// </summary> | ||
/// <example> | ||
/// Suppose C is a subcomponent of B which is a subcomponent of A. | ||
/// The path of C is <code>"A" + ComponentPathDivider + "B" + ComponentPathDivider + "C"</code>. | ||
/// </example> | ||
public static char ComponentPathDivider = '/'; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// 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 Newtonsoft.Json; | ||
|
||
namespace NuGet.Services.Status | ||
{ | ||
public class Event : IEvent | ||
{ | ||
public string AffectedComponentPath { get; } | ||
public ComponentStatus AffectedComponentStatus { get; } | ||
public DateTime StartTime { get; } | ||
public DateTime? EndTime { get; } | ||
public IEnumerable<IMessage> Messages { get; } | ||
|
||
public Event( | ||
string affectedComponentPath, | ||
ComponentStatus affectedComponentStatus, | ||
DateTime startTime, | ||
DateTime? endTime, | ||
IEnumerable<IMessage> messages) | ||
{ | ||
AffectedComponentPath = affectedComponentPath; | ||
AffectedComponentStatus = affectedComponentStatus; | ||
StartTime = startTime; | ||
EndTime = endTime; | ||
Messages = messages; | ||
} | ||
|
||
[JsonConstructor] | ||
public Event( | ||
string affectedComponentPath, | ||
ComponentStatus affectedComponentStatus, | ||
DateTime startTime, | ||
DateTime? endTime, | ||
IEnumerable<Message> messages) | ||
: this( | ||
affectedComponentPath, | ||
affectedComponentStatus, | ||
startTime, | ||
endTime, | ||
(IEnumerable<IMessage>)messages) | ||
{ | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// 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; | ||
|
||
namespace NuGet.Services.Status | ||
{ | ||
/// <summary> | ||
/// A writable <see cref="IReadOnlyComponent"/> that allows setting its status. | ||
/// </summary> | ||
public interface IComponent : IReadOnlyComponent, IComponentRoot<IComponent> | ||
{ | ||
/// <summary> | ||
/// The status of this part of the service. | ||
/// </summary> | ||
new ComponentStatus Status { get; set; } | ||
|
||
/// <summary> | ||
/// A list of writable subcomponents that make up this part of the service. | ||
/// </summary> | ||
new IEnumerable<IComponent> SubComponents { get; } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll remove this, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I forgot that this needs to exist so that |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// 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; | ||
|
||
namespace NuGet.Services.Status | ||
{ | ||
public interface IComponentRoot<out TComponent> | ||
where TComponent : IReadOnlyComponent | ||
{ | ||
IEnumerable<TComponent> SubComponents { get; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// 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; | ||
|
||
namespace NuGet.Services.Status | ||
{ | ||
/// <summary> | ||
/// Represents an event affecting a <see cref="IReadOnlyComponent"/>. | ||
/// </summary> | ||
public interface IEvent | ||
{ | ||
/// <summary> | ||
/// The <see cref="IReadOnlyComponent.Path"/> of the <see cref="IReadOnlyComponent"/> affected. | ||
/// </summary> | ||
string AffectedComponentPath { get; } | ||
|
||
/// <summary> | ||
/// The <see cref="IReadOnlyComponent.Status"/> of the <see cref="IReadOnlyComponent"/> affected. | ||
/// </summary> | ||
ComponentStatus AffectedComponentStatus { get; } | ||
|
||
/// <summary> | ||
/// When the event began. | ||
/// </summary> | ||
DateTime StartTime { get; } | ||
|
||
/// <summary> | ||
/// When the event ended, or <c>null</c> if the event is still active. | ||
/// </summary> | ||
DateTime? EndTime { get; } | ||
|
||
/// <summary> | ||
/// A set of <see cref="IMessage"/>s related to this event. | ||
/// </summary> | ||
IEnumerable<IMessage> Messages { get; } | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't you update
test.ps1
too?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!