Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/portal onboarded apps endpoint #744

Merged
merged 11 commits into from
Sep 13, 2024
6 changes: 6 additions & 0 deletions .changeset/pr-744-2069530352.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

---
"fusion-project-portal": patch
---
Endpoint for Apps activated on portal combined with available apps for portal.
Endpoint for Portal-app with reference to contextIds.
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using AutoMapper;
using Equinor.ProjectExecutionPortal.Application.Services.AppService;
using Equinor.ProjectExecutionPortal.Application.Services.ContextService;
using Equinor.ProjectExecutionPortal.Application.Services.PortalService;
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.GetPortalOnboardedApp;

public class GetPortalOnboardedAppQuery(Guid portalId, string appKey) : QueryBase<PortalOnboardedAppDto?>
{
public Guid PortalId { get; } = portalId;
public string AppKey { get; } = appKey;

public class Handler : IRequestHandler<GetPortalOnboardedAppQuery, PortalOnboardedAppDto?>
{
private readonly IReadWriteContext _readWriteContext;
private readonly IAppService _appService;
private readonly IPortalService _portalService;
private readonly IContextService _contextService;
private readonly IMapper _mapper;

public Handler(IReadWriteContext readWriteContext, IAppService appService, IPortalService portalService, IContextService contextService, IMapper mapper)
{
_readWriteContext = readWriteContext;
_appService = appService;
_portalService = portalService;
_contextService = contextService;
_mapper = mapper;
}

public async Task<PortalOnboardedAppDto?> Handle(GetPortalOnboardedAppQuery request, CancellationToken cancellationToken)
{
var portal = await _readWriteContext.Set<Portal>()
.AsNoTracking()
.Include(portal => portal.ContextTypes)
.Include(portal => portal.Apps)
.ThenInclude(portalApp => portalApp.OnboardedApp)
.ThenInclude(app => app.ContextTypes)
.FirstOrDefaultAsync(x => x.Id == request.PortalId, cancellationToken);

if (portal == null)
{
return null;
}

var portalApps = portal.Apps.Where(app => app.OnboardedApp.AppKey == request.AppKey).ToList();

if (portalApps.Any()){

var onboardedContexts = await _readWriteContext.Set<OnboardedContext>()
.AsNoTracking()
.ToListAsync(cancellationToken);

var portalContextIds = await _contextService.GetFusionContextIds(onboardedContexts.Where(context => portalApps.Any(app => app.OnboardedContextId == context.Id)).ToList(), cancellationToken);

var portalOnboardedAppDto = _mapper.Map<PortalApp, PortalOnboardedAppDto>(portalApps.First());

await _portalService.EnrichPortalAppWithContextIds(portalOnboardedAppDto, portalContextIds, cancellationToken);

await _portalService.SetAppAsActiveInPortal(portalOnboardedAppDto, cancellationToken);

await _appService.EnrichAppWithFusionAppData(portalOnboardedAppDto.OnboardedApp, cancellationToken);

return portalOnboardedAppDto;
}

var onboardedApp = await _readWriteContext.Set<OnboardedApp>()
.AsNoTracking()
.Include(onboardedApp => onboardedApp.ContextTypes)
.FirstOrDefaultAsync(x => x.AppKey == request.AppKey, cancellationToken);

if (onboardedApp != null)
{
var portalOnboardedAppNotActive = await _portalService.GetPortalOnboardedAppNotActive(onboardedApp, cancellationToken);

await _appService.EnrichAppWithFusionAppData(portalOnboardedAppNotActive.OnboardedApp, cancellationToken);

return portalOnboardedAppNotActive;
}

return null;
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using AutoMapper;
using Equinor.ProjectExecutionPortal.Application.Services.AppService;
using Equinor.ProjectExecutionPortal.Application.Services.PortalService;
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.GetPortalOnboardedApps;

public class GetPortalOnboardedAppsQuery(Guid portalId) : QueryBase<IList<PortalOnboardedAppDto?>>
{
public Guid PortalId { get; } = portalId;

public class Handler : IRequestHandler<GetPortalOnboardedAppsQuery, IList<PortalOnboardedAppDto?>>
{
private readonly IReadWriteContext _readWriteContext;
private readonly IAppService _appService;
private readonly IPortalService _portalService;
private readonly IMapper _mapper;

public Handler(IReadWriteContext readWriteContext, IAppService appService, IPortalService portalService, IMapper mapper)
{
_readWriteContext = readWriteContext;
_appService = appService;
_portalService = portalService;
_mapper = mapper;
}

public async Task<IList<PortalOnboardedAppDto?>> Handle(GetPortalOnboardedAppsQuery request, CancellationToken cancellationToken)
{
var portal = await _readWriteContext.Set<Portal>()
.AsNoTracking()
.Include(portal => portal.ContextTypes)
.Include(portal => portal.Apps)
.ThenInclude(portalApp => portalApp.OnboardedApp)
.ThenInclude(app => app.ContextTypes)
.FirstOrDefaultAsync(x => x.Id == request.PortalId, cancellationToken);

if (portal == null)
{
return new List<PortalOnboardedAppDto?>();
}

var onboardedApps = await _readWriteContext.Set<OnboardedApp>()
.AsNoTracking()
.Include(onboardedApp => onboardedApp.ContextTypes)
.ToListAsync(cancellationToken);

var portalOnboardedAppsDto = await _portalService.CombinePortalAppsWithOnboardedApps(portal, onboardedApps, cancellationToken);

await _appService.EnrichAppsWithAllFusionAppData(portalOnboardedAppsDto.Select(portalAppDto => portalAppDto.OnboardedApp).ToList(), cancellationToken);

return portalOnboardedAppsDto;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Equinor.ProjectExecutionPortal.Application.Infrastructure.Mappings;
using Equinor.ProjectExecutionPortal.Application.Queries.OnboardedApps;

namespace Equinor.ProjectExecutionPortal.Application.Queries.Portals
{
public class PortalOnboardedAppDto : IMapFrom<Domain.Entities.PortalApp>
{
public OnboardedAppDto? OnboardedApp { get; set; }
public List<Guid> ContextIds { get; set; } = [];
public bool IsActive { get; set; } = false;
public bool IsGlobal { get; set; }
public bool IsContextual { get; set; }

}
}
Jossilainen marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Equinor.ProjectExecutionPortal.Application.Cache;
using Equinor.ProjectExecutionPortal.Application.Queries.OnboardedApps;
using Equinor.ProjectExecutionPortal.Application.Queries.Portals;
using Equinor.ProjectExecutionPortal.FusionPortalApi.Apps;
using Equinor.ProjectExecutionPortal.FusionPortalApi.Apps.Models;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Equinor.ProjectExecutionPortal.Application.Queries.OnboardedApps;
using Equinor.ProjectExecutionPortal.Application.Queries.Portals;
using Equinor.ProjectExecutionPortal.FusionPortalApi.Apps.Models;

namespace Equinor.ProjectExecutionPortal.Application.Services.AppService
Expand All @@ -18,5 +19,7 @@ public interface IAppService
Task<IList<OnboardedAppDto>> EnrichAppsWithFusionAppData(IList<OnboardedAppDto> apps, CancellationToken cancellationToken);

Task<IList<OnboardedAppDto>> EnrichAppsWithAllFusionAppData(IList<OnboardedAppDto> apps, CancellationToken cancellationToken);


}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Equinor.ProjectExecutionPortal.Application.Queries.OnboardedContexts;
using Equinor.ProjectExecutionPortal.Domain.Entities;
using Fusion.Integration;

namespace Equinor.ProjectExecutionPortal.Application.Services.ContextService
Expand Down Expand Up @@ -48,5 +49,23 @@ public async Task<FusionContext> GetFusionContext(Guid contextId, CancellationTo

return context;
}

public async Task<IList<Guid>> GetFusionContextIds(IList<OnboardedContext> contexts, CancellationToken cancellationToken)
{
var contextIds = new List<Guid>();

foreach (var context in contexts)
{
var contextIdentifier = ContextIdentifier.FromExternalId(context.ExternalId);
var fusionContext = await _fusionContextResolver.ResolveContextAsync(contextIdentifier);

if (fusionContext != null)
{
contextIds.Add(fusionContext.Id);
}
}

return contextIds;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Equinor.ProjectExecutionPortal.Application.Queries.OnboardedContexts;
using Equinor.ProjectExecutionPortal.Domain.Entities;
using Fusion.Integration;

namespace Equinor.ProjectExecutionPortal.Application.Services.ContextService
Expand All @@ -8,5 +9,6 @@ public interface IContextService
Task<OnboardedContextDto> EnrichContextWithFusionContextData(OnboardedContextDto context, CancellationToken cancellationToken);
Task<IList<OnboardedContextDto>> EnrichContextsWithFusionContextData(IList <OnboardedContextDto> contexts, CancellationToken cancellationToken);
Task<FusionContext> GetFusionContext(Guid contextId, CancellationToken cancellationToken);
Task<IList<Guid>> GetFusionContextIds(IList<OnboardedContext> contexts, CancellationToken cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Equinor.ProjectExecutionPortal.Application.Queries.Portals;
using Equinor.ProjectExecutionPortal.Domain.Entities;

namespace Equinor.ProjectExecutionPortal.Application.Services.PortalService
{
public interface IPortalService
{
Task<IList<PortalOnboardedAppDto>> SetAppsAsActiveInPortal(IList<PortalOnboardedAppDto> apps, CancellationToken cancellationToken);
Task<PortalOnboardedAppDto> SetAppAsActiveInPortal(PortalOnboardedAppDto app, CancellationToken cancellationToken);
Task<IList<PortalOnboardedAppDto>> CombinePortalAppsWithOnboardedApps(Portal portal, IList<OnboardedApp> onboardedApps, CancellationToken cancellationToken);
Task<PortalOnboardedAppDto> GetPortalOnboardedAppNotActive(OnboardedApp onboardedApp, CancellationToken cancellationToken);
Task<PortalOnboardedAppDto> EnrichPortalAppWithContextIds(PortalOnboardedAppDto portalOnboardedAppDto, IList<Guid> contextIds, CancellationToken cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using AutoMapper;
using Equinor.ProjectExecutionPortal.Application.Queries.OnboardedApps;
using Equinor.ProjectExecutionPortal.Application.Queries.Portals;
using Equinor.ProjectExecutionPortal.Domain.Entities;

namespace Equinor.ProjectExecutionPortal.Application.Services.PortalService
{
public class PortalService : IPortalService
{
private readonly IMapper _mapper;

public PortalService(IMapper mapper)
{
_mapper = mapper;
}
public async Task<IList<PortalOnboardedAppDto>> CombinePortalAppsWithOnboardedApps(Portal portal, IList<OnboardedApp> onboardedApps, CancellationToken cancellationToken)
{

var portalAppsDto = _mapper.Map<List<PortalApp>, List<PortalOnboardedAppDto>>(GetDistinctPortalApps(portal.Apps.ToList()));

await SetAppsAsActiveInPortal(portalAppsDto, cancellationToken);

var onBoardedAppsNotActiveInPortal = GetOnBoardedAppsNotActiveInPortal(portal, onboardedApps);

portalAppsDto.AddRange(onBoardedAppsNotActiveInPortal);

return portalAppsDto.OrderBy(x => x.OnboardedApp?.AppKey).ToList();

}
public async Task<PortalOnboardedAppDto> EnrichPortalAppWithContextIds(PortalOnboardedAppDto portalOnboardedAppDto, IList<Guid> contextIds, CancellationToken cancellationToken)
{
portalOnboardedAppDto.ContextIds = contextIds.ToList();

await Task.CompletedTask;

return portalOnboardedAppDto;
}

public async Task<PortalOnboardedAppDto> GetPortalOnboardedAppNotActive(OnboardedApp onboardedApp, CancellationToken cancellationToken)
{
return new PortalOnboardedAppDto()
{
OnboardedApp = _mapper.Map<OnboardedApp, OnboardedAppDto>(onboardedApp),
IsActive = false
};
}

public async Task<IList<PortalOnboardedAppDto>> SetAppsAsActiveInPortal(IList<PortalOnboardedAppDto> apps, CancellationToken cancellationToken)
{
foreach (var app in apps)
{
app.IsActive = true;
}
await Task.CompletedTask;
return apps;
}

public async Task<PortalOnboardedAppDto> SetAppAsActiveInPortal(PortalOnboardedAppDto app, CancellationToken cancellationToken)
{
app.IsActive = true;

await Task.CompletedTask;
return app;
}

private List<PortalApp> GetDistinctPortalApps(List<PortalApp> portalApps)
{
var distinctPortalApps = portalApps.GroupBy(app => app.OnboardedApp.Id)
.Select(group => group.First())
.ToList();
return distinctPortalApps;
}

private List<PortalOnboardedAppDto> GetOnBoardedAppsNotActiveInPortal(Portal portal, IList<OnboardedApp> onboardedApps)
{
var onBoardedAppsNotActiveInPortal = IsContextualPortal(portal) ?
onboardedApps
.Where(onboardedApp =>
portal.Apps.All(portalAppDto => portalAppDto.OnboardedApp.Id != onboardedApp.Id) &&
(onboardedApp.ContextTypes.Count == 0 ||
onboardedApp.ContextTypes.Any(m => portal.ContextTypes.Any(n => n.ContextTypeKey == m.ContextTypeKey))))
.ToList() :
onboardedApps
.Where(onboardedApp => portal.Apps.All(portalAppDto => portalAppDto.OnboardedApp.Id != onboardedApp.Id))
.ToList();

return onBoardedAppsNotActiveInPortal.Select(onBoardedApp => new PortalOnboardedAppDto()
{
OnboardedApp = _mapper.Map<OnboardedApp, OnboardedAppDto>(onBoardedApp),
IsActive = false
}).ToList();

}
private bool IsContextualPortal(Portal portal)
{
return portal.ContextTypes.Count != 0;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using Equinor.ProjectExecutionPortal.Application.Queries.Portals.GetPortal;
using Equinor.ProjectExecutionPortal.Application.Queries.Portals.GetPortalApps;
using Equinor.ProjectExecutionPortal.Application.Queries.Portals.GetPortalConfiguration;
using Equinor.ProjectExecutionPortal.Application.Queries.Portals.GetPortalOnboardedApp;
using Equinor.ProjectExecutionPortal.Application.Queries.Portals.GetPortalOnboardedApps;
using Equinor.ProjectExecutionPortal.Application.Queries.Portals.GetPortals;
using Equinor.ProjectExecutionPortal.Domain.Common.Exceptions;
using Equinor.ProjectExecutionPortal.WebApi.Authorization;
Expand Down Expand Up @@ -191,6 +193,38 @@ public async Task<ActionResult<List<ApiPortalApp>>> PortalApps([FromRoute] Guid

}

[HttpGet("{portalId:guid}/onboarded-apps")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(void), StatusCodes.Status404NotFound)]
public async Task<ActionResult<List<ApiPortalOnboardedApp>>> PortalOnboardedApps([FromRoute] Guid portalId)
{
var portalOnboardedAppsDto = await Mediator.Send(new GetPortalOnboardedAppsQuery(portalId));

if (!portalOnboardedAppsDto.Any())
{
return FusionApiError.NotFound(portalId, "Could not find portal with id");
}

return Ok(portalOnboardedAppsDto.Select(x => new ApiPortalOnboardedApp(x)).ToList());

}

[HttpGet("{portalId:guid}/onboarded-apps/{appKey}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(void), StatusCodes.Status404NotFound)]
public async Task<ActionResult<ApiPortalOnboardedApp>> PortalOnboardedApp([FromRoute] Guid portalId, string appKey)
{
var portalOnboardedAppDto = await Mediator.Send(new GetPortalOnboardedAppQuery(portalId, appKey));

if (portalOnboardedAppDto == null)
{
return FusionApiError.NotFound(portalId, "Could not find portal with id or appkey is invalid");
}

return new ApiPortalOnboardedApp(portalOnboardedAppDto);

}

[HttpGet("{portalId:guid}/contexts/{contextId:guid}/apps")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(void), StatusCodes.Status404NotFound)]
Expand Down
Loading