Skip to content

Commit

Permalink
feature: API: migrate from Fusion Portal API to Fusion Apps API (#811)
Browse files Browse the repository at this point in the history
Co-authored-by: kjetilhau <[email protected]>
  • Loading branch information
kjetilhau and kjetilhau authored Oct 24, 2024
1 parent 2e486e0 commit 0a7119c
Show file tree
Hide file tree
Showing 83 changed files with 951 additions and 1,150 deletions.
9 changes: 9 additions & 0 deletions .changeset/pr-811-2107089113.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

---
"fusion-project-portal": major
---
- **BREAKING CHANGE**: Migrate from the deprecated Fusion Portal API (for apps) to the new and separate Fusion Apps API.
This results in breaking changes in the API contract. The reason for this is model changes in the new Fusion Apps API. And we prefer modelling 1-1 as best as possible instead of introducing remapping etc. on our end.
- **BREAKING CHANGE**: Removal of the Fusion Portal Proxy. We no longer provide endpoints for this on the API. All calls to get bundles and app information from Fusion shall go through the ClientBackend
- Fixed more tests
- Refactoring has been done in multiple locations
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ private static TimeSpan GetExpirationTime(CacheDuration duration, long expiratio
CacheDuration.Hours => TimeSpan.FromHours(expiration),
CacheDuration.Minutes => TimeSpan.FromMinutes(expiration),
CacheDuration.Seconds => TimeSpan.FromSeconds(expiration),
_ => throw new NotImplementedException($"Unknown {nameof(CacheDuration)}: {duration}"),
_ => throw new Exception($"Unknown {nameof(CacheDuration)}: {duration}"),
};

private class CacheClock : ISystemClock
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
using Equinor.ProjectExecutionPortal.FusionPortalApi.Apps;
using Equinor.ProjectExecutionPortal.FusionPortalApi.Apps.Models;
using Fusion.Integration.Apps.Abstractions.Abstractions;
using Fusion.Integration.Apps.Abstractions.Models;

namespace Equinor.ProjectExecutionPortal.Application.Cache;

public class FusionAppsCache : IFusionAppsCache
{
private readonly ICacheManager _cacheManager;
private readonly IFusionPortalApiService _fusionPortalApiService;
private readonly IAppsClient _fusionAppsClient;

public FusionAppsCache(ICacheManager cacheManager, IFusionPortalApiService fusionPortalApiService)
public FusionAppsCache(ICacheManager cacheManager, IAppsClient fusionAppsClient)
{
_cacheManager = cacheManager;
_fusionPortalApiService = fusionPortalApiService;
_fusionAppsClient = fusionAppsClient;
}

public async Task<List<FusionPortalAppInformation>> GetFusionApps()
public async Task<List<App>> GetFusionApps()
{
return await _cacheManager.GetOrCreateAsync("FUSION_APP",
async () =>
{
var fusionApps = await _fusionPortalApiService.TryGetFusionPortalApps();
var fusionApps = await _fusionAppsClient.GetAppsAsync();

return fusionApps.ToList();
},
CacheDuration.Minutes,
60);
}

public async Task<FusionPortalAppInformation?> GetFusionApp(string appKey)
public async Task<App?> GetFusionApp(string appKey)
{
return await _cacheManager.GetOrCreateAsync("FUSION_APP",
async () => await _fusionPortalApiService.TryGetFusionPortalApp(appKey),
async () => await _fusionAppsClient.GetAppAsync(appKey),
CacheDuration.Minutes,
60);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using Equinor.ProjectExecutionPortal.FusionPortalApi.Apps.Models;
using Fusion.Integration.Apps.Abstractions.Models;

namespace Equinor.ProjectExecutionPortal.Application.Cache;

public interface IFusionAppsCache
{
Task<List<FusionPortalAppInformation>> GetFusionApps();
Task<List<App>> GetFusionApps();

Task<FusionPortalAppInformation?> GetFusionApp(string appKey);
Task<App?> GetFusionApp(string appKey);
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public async Task<Guid> Handle(AddContextTypeCommand command, CancellationToken
if (!FusionContextType.IsValid(command.ContextTypeKey))
{
throw new NotFoundException($"ContextType: {command.ContextTypeKey} is not a valid Context type");
};
}

var contextType = new ContextType(command.ContextTypeKey);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Data;
using Equinor.ProjectExecutionPortal.Domain.Common.Exceptions;
using Equinor.ProjectExecutionPortal.Domain.Common.Exceptions;
using Equinor.ProjectExecutionPortal.Domain.Entities;
using Equinor.ProjectExecutionPortal.Infrastructure;
using MediatR;
Expand All @@ -15,7 +14,7 @@ public RemoveContextTypeCommand(string contextTypeKey)
}

public string ContextTypeKey { get; }

public class Handler : IRequestHandler<RemoveContextTypeCommand>
{
private readonly IReadWriteContext _readWriteContext;
Expand All @@ -27,19 +26,19 @@ public Handler(IReadWriteContext readWriteContext)

public async Task Handle(RemoveContextTypeCommand command, CancellationToken cancellationToken)
{
var entity = await _readWriteContext.Set<ContextType>()
var entity = await _readWriteContext.Set<ContextType>()
.Include(x => x.OnboardedApps)
.Include(x => x.Portals)
.FirstOrDefaultAsync(x => x.ContextTypeKey == command.ContextTypeKey, cancellationToken);

if (entity == null)
{
throw new NotFoundException(nameof(ContextType) ,command.ContextTypeKey);
throw new NotFoundException(nameof(ContextType), command.ContextTypeKey);
}

if (entity.Portals.Any())
{
throw new InvalidActionException("Cannot remove context type. Context type is used by portals");
throw new InvalidActionException("Cannot remove context type. Context type is used by portals");
}

if (entity.OnboardedApps.Any())
Expand All @@ -50,7 +49,6 @@ public async Task Handle(RemoveContextTypeCommand command, CancellationToken can
_readWriteContext.Set<ContextType>().Remove(entity);

await _readWriteContext.SaveChangesAsync(cancellationToken);

}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using Equinor.ProjectExecutionPortal.Application.Services.AppService;
using Equinor.ProjectExecutionPortal.Application.Services.ContextTypeService;
using Equinor.ProjectExecutionPortal.Application.Services.ContextTypeService;
using Equinor.ProjectExecutionPortal.Application.Services.FusionAppsService;
using Equinor.ProjectExecutionPortal.Domain.Common.Exceptions;
using Equinor.ProjectExecutionPortal.Domain.Entities;
using Equinor.ProjectExecutionPortal.Infrastructure;
Expand All @@ -22,19 +22,19 @@ public OnboardAppCommand(string appKey, IList<string>? contextTypes)
public class Handler : IRequestHandler<OnboardAppCommand, Guid>
{
private readonly IReadWriteContext _readWriteContext;
private readonly IAppService _appService;
private readonly IFusionAppsService _fusionAppsService;
private readonly IContextTypeService _contextTypeService;

public Handler(IReadWriteContext readWriteContext, IAppService appService, IContextTypeService contextTypeService)
public Handler(IReadWriteContext readWriteContext, IFusionAppsService fusionAppsService, IContextTypeService contextTypeService)
{
_readWriteContext = readWriteContext;
_appService = appService;
_fusionAppsService = fusionAppsService;
_contextTypeService = contextTypeService;
}

public async Task<Guid> Handle(OnboardAppCommand command, CancellationToken cancellationToken)
{
if (!await _appService.FusionAppExist(command.AppKey, cancellationToken))
if (!await _fusionAppsService.FusionAppExist(command.AppKey, cancellationToken))
{
throw new NotFoundException($"Could not locate app '{command.AppKey}' in Fusion.");
}
Expand All @@ -49,10 +49,10 @@ public async Task<Guid> Handle(OnboardAppCommand command, CancellationToken canc
}

var onboardedApp = new OnboardedApp(command.AppKey);

try
{
onboardedApp.AddContextTypes(await _contextTypeService.GetContextTypesByContextTypeKey(command.ContextTypes, cancellationToken));
onboardedApp.AddContextTypes(await _contextTypeService.GetAllowedContextTypesByKeys(command.ContextTypes, cancellationToken));
}
catch (InvalidActionException ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public async Task<Guid> Handle(UpdateOnboardedAppCommand command, CancellationTo

try
{
entity.AddContextTypes(await _contextTypeService.GetContextTypesByContextTypeKey(command.ContextTypes, cancellationToken));
entity.AddContextTypes(await _contextTypeService.GetAllowedContextTypesByKeys(command.ContextTypes, cancellationToken));
}
catch (InvalidActionException ex)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Equinor.ProjectExecutionPortal.Application.Services.ContextTypeService;
using Equinor.ProjectExecutionPortal.Domain.Common.Exceptions;
using Equinor.ProjectExecutionPortal.Domain.Common.Exceptions;
using Equinor.ProjectExecutionPortal.Domain.Entities;
using Equinor.ProjectExecutionPortal.Infrastructure;
using MediatR;
Expand All @@ -22,12 +21,10 @@ public AddContextTypeToPortalCommand(Guid portalId, string type)
public class Handler : IRequestHandler<AddContextTypeToPortalCommand, Unit>
{
private readonly IReadWriteContext _readWriteContext;
private readonly IContextTypeService _contextTypeService;

public Handler(IReadWriteContext readWriteContext, IContextTypeService contextTypeService)
public Handler(IReadWriteContext readWriteContext)
{
_readWriteContext = readWriteContext;
_contextTypeService = contextTypeService;
}

public async Task<Unit> Handle(AddContextTypeToPortalCommand command, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public async Task<Guid> Handle(CreatePortalCommand command, CancellationToken ca

var portal = new Portal(slug, command.Name, command.ShortName, command.SubText, command.Description, command.Icon);

portal.AddContextTypes(await _contextTypeService.GetContextTypesByContextTypeKey(command.ContextTypes, cancellationToken));
portal.AddContextTypes(await _contextTypeService.GetAllowedContextTypesByKeys(command.ContextTypes, cancellationToken));

await _readWriteContext.Set<Portal>().AddAsync(portal, cancellationToken);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public async Task<Guid> Handle(UpdatePortalCommand command, CancellationToken ca

entity.Update(slug, command.Name, command.ShortName, command.SubText, command.Description, command.Icon);

entity.AddContextTypes(await _contextTypeService.GetContextTypesByContextTypeKey(command.ContextTypes, cancellationToken));
entity.AddContextTypes(await _contextTypeService.GetAllowedContextTypesByKeys(command.ContextTypes, cancellationToken));

await _readWriteContext.SaveChangesAsync(cancellationToken);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@

<ItemGroup>
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Fusion.Integration" Version="8.0.4" />
<PackageReference Include="Fusion.Integration.Abstractions" Version="8.0.7" />
<PackageReference Include="Fusion.Integration.Apps.Abstractions" Version="8.0.0" />
<PackageReference Include="MediatR" Version="12.2.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Equinor.ProjectExecutionPortal.Domain\Equinor.ProjectExecutionPortal.Domain.csproj" />
<ProjectReference Include="..\Equinor.ProjectExecutionPortal.FusionPortalApi\Equinor.ProjectExecutionPortal.FusionPortalApi.csproj" />
<ProjectReference Include="..\Equinor.ProjectExecutionPortal.Infrastructure\Equinor.ProjectExecutionPortal.Infrastructure.csproj" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public Handler(IReadWriteContext context, IMapper mapper, IAppService appService

var onboardedApp = _mapper.Map<OnboardedApp, OnboardedAppDto>(entity);

await _appService.EnrichAppWithFusionAppData(onboardedApp, cancellationToken);
await _appService.EnrichWithFusionAppData(onboardedApp, cancellationToken);

return onboardedApp;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public async Task<IList<OnboardedAppDto>> Handle(GetOnboardedAppsQuery request,

var onboardedApps = _mapper.Map<List<Domain.Entities.OnboardedApp>, List<OnboardedAppDto>>(entity);

await _appService.EnrichAppsWithFusionAppData(onboardedApps, cancellationToken);
await _appService.EnrichWithFusionAppData(onboardedApps, cancellationToken);

return onboardedApps;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,22 @@
using Equinor.ProjectExecutionPortal.Application.Infrastructure.Mappings;
using Equinor.ProjectExecutionPortal.Application.Queries.ContextTypes;
using Equinor.ProjectExecutionPortal.FusionPortalApi.Apps.Models;
using Fusion.Integration.Apps.Abstractions.Models;

namespace Equinor.ProjectExecutionPortal.Application.Queries.OnboardedApps;

public enum FusionPortalAppInformationAmount
{
Minimal,
All
}

public class OnboardedAppDto : IMapFrom<Domain.Entities.OnboardedApp>
{
public Guid Id { get; set; }
public string AppKey { get; set; }
public FusionPortalAppInformation? AppInformation { get; set; }
public IList<ContextTypeDto> ContextTypes { get; set; }
public string? AppKey { get; set; }
public string? DisplayName { get; set; }
public string? Description { get; set; }
public App? AppInformation { get; set; }
public IList<ContextTypeDto> ContextTypes { get; set; } = new List<ContextTypeDto>();

public void SupplyWithFusionData(FusionPortalAppInformation appInformation, FusionPortalAppInformationAmount amount)
public void SupplyWithFusionData(App app)
{
switch (amount)
{
case FusionPortalAppInformationAmount.All:
AppInformation = appInformation;
break;

case FusionPortalAppInformationAmount.Minimal:
var appInfo = new FusionPortalAppInformation
{
Key = appInformation.Key,
Name = appInformation.Name,
Description = appInformation.Description
};

AppInformation = appInfo;

break;

default:
throw new ArgumentOutOfRangeException(nameof(amount), amount, null);
}
DisplayName = app.DisplayName;
Description = app.Description;
AppInformation = app;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ public GetOnboardedContextByExternalIdContextTypeQuery(string externalId, string
ContextType = contextType;
}

public string ExternalId { get; } = null!;
public string ContextType { get; } = null!;
public string ExternalId { get; }
public string ContextType { get; }

public class Handler : IRequestHandler<GetOnboardedContextByExternalIdContextTypeQuery, OnboardedContextDto?>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using Equinor.ProjectExecutionPortal.Application.Services.ContextService;
using Equinor.ProjectExecutionPortal.Domain.Common.Exceptions;
using Equinor.ProjectExecutionPortal.Domain.Entities;
using Equinor.ProjectExecutionPortal.Domain.Infrastructure;
using Equinor.ProjectExecutionPortal.Infrastructure;
using MediatR;
using Microsoft.EntityFrameworkCore;

namespace Equinor.ProjectExecutionPortal.Application.Queries.Portals.GetPortalAppKeys;

public class GetContextualAndGlobalAppKeysByPortalAndContextQuery : QueryBase<IList<string>>
{
public GetContextualAndGlobalAppKeysByPortalAndContextQuery(Guid portalId, Guid contextId)
{
PortalId = portalId;
ContextId = contextId;
}

public Guid PortalId { get; }
public Guid ContextId { get; }

public class Handler : IRequestHandler<GetContextualAndGlobalAppKeysByPortalAndContextQuery, IList<string>>
{
private readonly IReadWriteContext _readWriteContext;

private readonly IContextService _contextService;

public Handler(IReadWriteContext readWriteContext, IContextService contextService)
{
_readWriteContext = readWriteContext;

_contextService = contextService;
}

public async Task<IList<string>> Handle(GetContextualAndGlobalAppKeysByPortalAndContextQuery request, CancellationToken cancellationToken)
{
var fusionContext = await _contextService.GetFusionContext(request.ContextId, cancellationToken);

if (fusionContext == null)
{
throw new NotFoundException($"Invalid context-id: {request.ContextId}");
}

var portalWithContextualAndGlobalApps = await _readWriteContext.Set<Portal>()
.AsNoTracking()
.Include(portal => portal.Apps
.Where(app => app.OnboardedContext == null || (app.OnboardedContext.ExternalId == fusionContext.ExternalId && app.OnboardedContext.Type == fusionContext.Type.Name)))
.ThenInclude(app => app.OnboardedApp)
.ThenInclude(app => app.ContextTypes)
.FirstOrDefaultAsync(x => x.Id == request.PortalId, cancellationToken);

if (portalWithContextualAndGlobalApps == null)
{
throw new NotFoundException(nameof(Portal), request.PortalId);
}

var contextualAndGlobalPortalAppKeys = portalWithContextualAndGlobalApps.Apps
.Where(apps => apps.OnboardedApp.ContextTypes.Count == 0 || apps.OnboardedApp.ContextTypes.Any(m => m.ContextTypeKey == fusionContext.Type.Name))
.Select(app => app.OnboardedApp.AppKey)
.ToList();

return contextualAndGlobalPortalAppKeys;
}
}
}
Loading

0 comments on commit 0a7119c

Please sign in to comment.