Skip to content

Commit

Permalink
feat: add kill switch
Browse files Browse the repository at this point in the history
  • Loading branch information
Marc-Antoine-Soucy committed Apr 22, 2024
1 parent 7bbf469 commit f097830
Show file tree
Hide file tree
Showing 22 changed files with 437 additions and 15 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)

Prefix your items with `(Template)` if the change is about the template and not the resulting application.

## 3.4.X
- Added a kill switch feature to the app.

## 3.3.X
- Added a forced update feature to the app.

Expand Down
6 changes: 5 additions & 1 deletion doc/Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,11 @@ This application uses [DynamicData](https://github.com/reactivemarbles/DynamicDa

### Forced Update

This application uses the [IUpdateRequiredService](/src/app/ApplicationTemplate.Business/ForcedUpdates/IUpdateRequiredService.cs) which exposes an event that allows you to know when you should redirect the user to a page that will lead him to the appstore where he can update the app.
This application uses the [IUpdateRequiredService](../src/app/ApplicationTemplate.Business/ForcedUpdates/IUpdateRequiredService.cs) which exposes an event that allows you to know when you should redirect the user to a page that will lead him to the appstore where he can update the app.

### Kill Switch

This application uses the [IKillSwitchService](../src/app/ApplicationTemplate.Business/KillSwitch/IKillSwitchService.cs) which exposes an event that allows you to know when you the Kill switch is activated and when it gets deactivated.

## Presentation

Expand Down
8 changes: 8 additions & 0 deletions doc/KillSwitch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Kill Switch

The kill switch feature is for when you want to temporarily lock the user out of the app.
This could be used for example when the server is down for some time, to avoid the user getting a ton of errors and getting reports from users.

To trigger the kill switch, we suscribe to the `KillSwitchActivationChanged` `event` from the `KillSwitchService` in the [CoreStartup](../src/app/ApplicationTemplate.Presentation/CoreStartup.cs#L223).

If the kill switch is activated, the user is brought to the `KillSwitchPage` where he can see a message that tells him the app is currently unavailable. If the kill switch is deactivated afterwards, the user is brought back to the initial navigation flow, which means he will be in the login page if he is not connected and to the home page if he is connected.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;

namespace ApplicationTemplate.DataAccess;

/// <summary>
/// Interface for the repository that gets when the kill switch is activated.
/// </summary>
public interface IKillSwitchRepository
{
/// <summary>
/// Observes and reports the activation or deactivation of the kill switch.
/// </summary>
/// <returns>
/// An <see cref="IObservable{Boolean}"/> that indicates whether the kill switch is activated or not.
/// </returns>
public IObservable<bool> ObserveKillSwitchActivation();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Reactive.Subjects;
using DynamicData;

namespace ApplicationTemplate.DataAccess;

/// <summary>
/// Mock implementation of the kill switch repository.
/// </summary>
public sealed class KillSwitchRepositoryMock : IKillSwitchRepository, IDisposable
{
private readonly Subject<bool> _killSwitchActivatedSubject = new();
private bool _killSwitchActivated = false;

/// <inheritdoc/>
public IObservable<bool> ObserveKillSwitchActivation() => _killSwitchActivatedSubject;

/// <summary>
/// Change the kill switch activation status.
/// </summary>
public void ChangeKillSwitchActivation()
{
_killSwitchActivated = !_killSwitchActivated;

_killSwitchActivatedSubject.OnNext(_killSwitchActivated);
}

/// <inheritdoc/>
public void Dispose()
{
_killSwitchActivatedSubject.Dispose();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;

namespace ApplicationTemplate.Business;

/// <summary>
/// Service that handles the kill switch.
/// </summary>
public interface IKillSwitchService
{
/// <summary>
/// Observes and reports the activation or deactivation of the kill switch.
/// </summary>
/// <returns>
/// An <see cref="IObservable{Boolean}"/> that indicates whether the kill switch is activated or not.
/// </returns>
public IObservable<bool> ObserveKillSwitchActivation();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Reactive.Linq;
using ApplicationTemplate.DataAccess;

namespace ApplicationTemplate.Business;

/// <summary>
/// Implementation of the IKillSwitchService.
/// </summary>
public class KillSwitchService : IKillSwitchService
{
private readonly IKillSwitchRepository _killSwitchRepository;

/// <summary>
/// Initializes a new instance of the <see cref="KillSwitchService"/> class.
/// </summary>
/// <param name="killSwitchRepository">The <see cref="IKillSwitchRepository"/>.</param>
public KillSwitchService(IKillSwitchRepository killSwitchRepository)
{
_killSwitchRepository = killSwitchRepository;
}

/// <inheritdoc/>
public IObservable<bool> ObserveKillSwitchActivation() => _killSwitchRepository.ObserveKillSwitchActivation();
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public static IServiceCollection AddApi(this IServiceCollection services, IConfi
.AddPosts(configuration)
.AddUserProfile()
.AddMinimumVersion()
.AddKillSwitch()
.AddDadJokes(configuration);

return services;
Expand All @@ -60,6 +61,12 @@ private static IServiceCollection AddMinimumVersion(this IServiceCollection serv
return services.AddSingleton<IMinimumVersionReposiory, MinimumVersionRepositoryMock>();
}

private static IServiceCollection AddKillSwitch(this IServiceCollection services)
{
// This one doesn't have an actual remote API yet. It's always a mock implementation.
return services.AddSingleton<IKillSwitchRepository, KillSwitchRepositoryMock>();
}

private static IServiceCollection AddAuthentication(this IServiceCollection services)
{
// This one doesn't have an actual remote API yet. It's always a mock implementation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public static IServiceCollection AddAppServices(this IServiceCollection services
.AddSingleton<IAuthenticationService, AuthenticationService>()
.AddSingleton<IUserProfileService, UserProfileService>()
.AddSingleton<IUpdateRequiredService, UpdateRequiredService>()
.AddSingleton<IKillSwitchService, KillSwitchService>()
.AddSingleton<DiagnosticsCountersService>();
}
}
53 changes: 53 additions & 0 deletions src/app/ApplicationTemplate.Presentation/CoreStartup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ protected override async Task StartServices(IServiceProvider services, bool isFi
services.GetRequiredService<DiagnosticsCountersService>().Start();
await ExecuteInitialNavigation(CancellationToken.None, services);
SuscribeToRequiredUpdate(services);
SuscribeToKillSwitch(services);
}
}

Expand Down Expand Up @@ -219,6 +220,58 @@ void ForceUpdate(object sender, EventArgs e)
}
}

private void SuscribeToKillSwitch(IServiceProvider serviceProvider)
{
var killSwitchService = serviceProvider.GetRequiredService<IKillSwitchService>();
var navigationController = serviceProvider.GetRequiredService<ISectionsNavigator>();

killSwitchService.ObserveKillSwitchActivation()
.SelectManyDisposePrevious(async (activated, ct) =>
{
Logger.LogTrace("Kill switch activation changed to {Activated}", activated);

if (activated)
{
await OnKillSwitchActivated(ct);
}
else
{
await OnKillSwitchDeactivated(ct);
}
})
.Subscribe()
.DisposeWith(Disposables);

async Task OnKillSwitchActivated(CancellationToken ct)
{

// Clear all navigation stacks and show the kill switch page.
// We clear the navigation stack to avoid weird animations once the kill switch is deactivated.
foreach (var modal in navigationController.State.Modals)
{
await navigationController.CloseModal(ct);
}

foreach (var stack in navigationController.State.Sections)
{
await ClearNavigationStack(ct, stack.Value);
}

await navigationController.NavigateAndClear(ct, () => new KillSwitchPageViewModel());

Logger.LogInformation("Navigated to kill switch page succesfully.");
}

async Task OnKillSwitchDeactivated(CancellationToken ct)
{
if (navigationController.GetActiveViewModel() is KillSwitchPageViewModel)
{
await ExecuteInitialNavigation(ct, serviceProvider);
Logger.LogInformation("Kill switch deactivated");
}
}
}

/// <summary>
/// Starts deactivating and reactivating ViewModels based on navigation.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,17 @@ public NavigationDebuggerViewModel()
{
this.GetService<IMinimumVersionReposiory>().CheckMinimumVersion();
});

/// <summary>
/// Gets a command that raises the kill switch event.
/// </summary>
public IDynamicCommand TriggerKillSwitch => this.GetCommand(() =>
{
var killSwitchRepository = this.GetService<IKillSwitchRepository>();

if (killSwitchRepository is KillSwitchRepositoryMock killSwitchRepositoryMock)
{
killSwitchRepositoryMock.ChangeKillSwitchActivation();
}
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace ApplicationTemplate.Presentation;

/// <summary>
/// ViewModel for the kill switch page.
/// </summary>
public sealed class KillSwitchPageViewModel : ViewModel
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Content\ForcedUpdate\ForcedUpdatePage.xaml.cs">
<DependentUpon>ForcedUpdatePage.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Content\KillSwitch\KillSwitchPage.xaml.cs">
<DependentUpon>KillSwitchPage.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Content\Menu.xaml.cs">
<DependentUpon>Menu.xaml</DependentUpon>
</Compile>
Expand Down Expand Up @@ -243,6 +246,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Content\KillSwitch\KillSwitchPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Content\Menu.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public static IServiceCollection AddNavigation(this IServiceCollection services)
{ typeof(SentEmailConfirmationPageViewModel), typeof(SentEmailConfirmationPage) },
{ typeof(ResetPasswordPageViewModel), typeof(ResetPasswordPage) },
{ typeof(ForcedUpdatePageViewModel), typeof(ForcedUpdatePage) },
{ typeof(KillSwitchPageViewModel), typeof(KillSwitchPage) },
};

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,20 +92,28 @@
Style="{StaticResource NavigationDebuggerTextBlockStyle}" />

<!-- Clear and Reinitialize buttons -->
<StackPanel Orientation="Horizontal"
<StackPanel Orientation="Vertical"
Spacing="8">

<Button Content="Clear all pages"
Style="{StaticResource NavigationDebuggerButtonStyle}"
Command="{Binding Clear}" />

<Button Content="Reinitialize navigation"
Style="{StaticResource NavigationDebuggerButtonStyle}"
Command="{Binding Reinitialize}" />
<StackPanel Orientation="Horizontal"
Spacing="8">
<Button Content="Clear all pages"
Style="{StaticResource NavigationDebuggerButtonStyle}"
Command="{Binding Clear}" />

<Button Content="Force update"
Style="{StaticResource NavigationDebuggerButtonStyle}"
Command="{Binding NavigateToForceUpdatePage}" />
<Button Content="Reinitialize navigation"
Style="{StaticResource NavigationDebuggerButtonStyle}"
Command="{Binding Reinitialize}" />
</StackPanel>

<StackPanel Orientation="Horizontal"
Spacing="8">
<Button Content="Force update"
Style="{StaticResource NavigationDebuggerButtonStyle}"
Command="{Binding NavigateToForceUpdatePage}" />
<Button Content="Toggle kill switch"
Style="{StaticResource NavigationDebuggerButtonStyle}"
Command="{Binding TriggerKillSwitch}" />
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Page x:Class="ApplicationTemplate.Views.Content.KillSwitchPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">

<Grid>

<TextBlock x:Uid="KillSwitchMessage"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="24"
Text="The app is not available at the moment, please come back later"
Margin="24" />

</Grid>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Microsoft.UI.Xaml.Controls;

namespace ApplicationTemplate.Views.Content
{
public sealed partial class KillSwitchPage : Page
{
public KillSwitchPage()
{
this.InitializeComponent();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -449,4 +449,7 @@ it's happening!</value>
<data name="ForcedUpdateContent.Text" xml:space="preserve">
<value>An update is required to continue using the application.</value>
</data>
<data name="KillSwitchMessage.Text" xml:space="preserve">
<value>The app is not available at the moment, please come back later.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -449,4 +449,7 @@ Je pense que nous savons tous pourquoi nous sommes ici."</value>
<data name="ForcedUpdateContent.Text" xml:space="preserve">
<value>Une mise à jour de l'application est nécessaire pour continuer de l'utiliser.</value>
</data>
<data name="KillSwitchMessage.Text" xml:space="preserve">
<value>L'application n'est pas disponible pour le moment, veuillez revenir plus tard.</value>
</data>
</root>
Loading

0 comments on commit f097830

Please sign in to comment.