feat: initial commit
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Strata.Analytics.Api.Authorization
|
||||
{
|
||||
public class AuthorizationOptions
|
||||
{
|
||||
public Guid AuthorizedDbGuid { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Strata.Analytics.Api.Authorization
|
||||
{
|
||||
public class AuthorizedDbMinimumRoleHandler : AuthorizationHandler<AuthorizedDbMinimumRoleRequirement>
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IWebHostEnvironment _env;
|
||||
private Guid _authorizedDbGuid;
|
||||
|
||||
public AuthorizedDbMinimumRoleHandler(IHttpContextAccessor httpContextAccessor, IWebHostEnvironment env, IOptions<AuthorizationOptions> options)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_env = env;
|
||||
_authorizedDbGuid = options.Value.AuthorizedDbGuid;
|
||||
}
|
||||
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthorizedDbMinimumRoleRequirement requirement)
|
||||
{
|
||||
var user = _httpContextAccessor.HttpContext.User;
|
||||
var dbGuid = user.GetDatabaseGuid();
|
||||
if (!_env.IsProduction() || dbGuid == _authorizedDbGuid)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
context.Fail();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Strata.Analytics.Api.Authorization
|
||||
{
|
||||
public class AuthorizedDbMinimumRoleRequirement : IAuthorizationRequirement
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Strata.Analytics.Biz.Extracts.Services;
|
||||
using Strata.Analytics.Biz.Extracts.Models;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
|
||||
|
||||
namespace Strata.Analytics.Api.Authorization
|
||||
{
|
||||
public class CustomSQLStrataOnlyRoleHandler : AuthorizationHandler<ExtractCustomSqlStrataOnlyRoleRequirement>
|
||||
{
|
||||
private readonly IExtractService _extractService;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
public CustomSQLStrataOnlyRoleHandler(IExtractService extractService, IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_extractService = extractService;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ExtractCustomSqlStrataOnlyRoleRequirement requirement)
|
||||
{
|
||||
var extractIdObj = _httpContextAccessor.HttpContext!.GetRouteData()?.Values["extractId"];
|
||||
if (extractIdObj == null)
|
||||
{
|
||||
context.Fail();
|
||||
return;
|
||||
}
|
||||
|
||||
var extractId = int.Parse(extractIdObj.ToString()!);
|
||||
|
||||
var extract =
|
||||
await _extractService.GetByIdAsync(extractId, System.Threading.CancellationToken.None);
|
||||
|
||||
if (extract == null)
|
||||
{
|
||||
context.Fail();
|
||||
return;
|
||||
}
|
||||
|
||||
if (extract.ExtractType == ExtractType.CustomSql && !context.User.IsSdtEmployee())
|
||||
{
|
||||
context.Fail();
|
||||
return;
|
||||
}
|
||||
context.Succeed(requirement);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Threading.Tasks;
|
||||
using Strata.Analytics.Biz.DataSources.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Strata.Analytics.Api.Authorization
|
||||
{
|
||||
public class DataSourceMinimumRoleHandler : AuthorizationHandler<DataSourceMinimumRoleRequirement>
|
||||
{
|
||||
private readonly IDataSourceService _dataSourceService;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public DataSourceMinimumRoleHandler(IDataSourceService dataSourceService, IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_dataSourceService = dataSourceService;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, DataSourceMinimumRoleRequirement requirement)
|
||||
{
|
||||
var dataSourceId = await GetDataSourceId(context);
|
||||
if (!dataSourceId.HasValue)
|
||||
{
|
||||
context.Fail();
|
||||
return;
|
||||
}
|
||||
|
||||
var dataSourceRole = await _dataSourceService.GetCurrentUserRoleAsync(dataSourceId.Value, System.Threading.CancellationToken.None);
|
||||
|
||||
if (!dataSourceRole.HasValue || dataSourceRole.Value < requirement.MinimumRole)
|
||||
{
|
||||
context.Fail();
|
||||
return;
|
||||
}
|
||||
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
|
||||
private async Task<int?> GetDataSourceId(AuthorizationHandlerContext context)
|
||||
{
|
||||
// Check route for dataSourceId
|
||||
var dataSourceIdObj = _httpContextAccessor.HttpContext.GetRouteData()?.Values["dataSourceId"];
|
||||
if (dataSourceIdObj == null)
|
||||
{
|
||||
// Check body for dataSourceId
|
||||
var req = _httpContextAccessor.HttpContext.Request;
|
||||
|
||||
try
|
||||
{
|
||||
req.EnableBuffering();
|
||||
req.Body.Position = 0;
|
||||
var body = await req.ReadFromJsonAsync<Dictionary<string, dynamic>>(null);
|
||||
req.Body.Position = 0;
|
||||
if (req.Body.Length > 0)
|
||||
{
|
||||
foreach (var kvp in body)
|
||||
if (string.Equals(kvp.Key, "dataSourceId", StringComparison.InvariantCultureIgnoreCase))
|
||||
dataSourceIdObj = kvp.Value;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!int.TryParse(dataSourceIdObj.ToString(), out int dataSourceId))
|
||||
return null;
|
||||
|
||||
return dataSourceId;
|
||||
}
|
||||
|
||||
// Swallows throw when user does not have SdtEmployee claim and returns false
|
||||
private bool IsSdtEmployee(AuthorizationHandlerContext context)
|
||||
{
|
||||
var isSdtEmployee = false;
|
||||
try
|
||||
{
|
||||
isSdtEmployee = context.User.IsSdtEmployee();
|
||||
}
|
||||
catch { }
|
||||
|
||||
return isSdtEmployee;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Strata.Analytics.Biz.DataSources.Models;
|
||||
|
||||
namespace Strata.Analytics.Api.Authorization
|
||||
{
|
||||
public class DataSourceMinimumRoleRequirement : IAuthorizationRequirement
|
||||
{
|
||||
public DataSourceRole MinimumRole { get; set; }
|
||||
|
||||
public DataSourceMinimumRoleRequirement(DataSourceRole role)
|
||||
{
|
||||
MinimumRole = role;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
public class ExtractCustomSqlStrataOnlyRoleRequirement : IAuthorizationRequirement
|
||||
{
|
||||
|
||||
public ExtractCustomSqlStrataOnlyRoleRequirement()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Strata.Analytics.Biz.Extracts.Services;
|
||||
|
||||
namespace Strata.Analytics.Api.Authorization
|
||||
{
|
||||
public class ExtractMinimumRoleHandler : AuthorizationHandler<ExtractMinimumRoleRequirement>
|
||||
{
|
||||
private readonly IExtractService _extractService;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
public ExtractMinimumRoleHandler(IExtractService extractService, IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_extractService = extractService;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ExtractMinimumRoleRequirement requirement)
|
||||
{
|
||||
var extractIdObj = _httpContextAccessor.HttpContext!.GetRouteData()?.Values["extractId"];
|
||||
if (extractIdObj == null)
|
||||
{
|
||||
context.Fail();
|
||||
return;
|
||||
}
|
||||
|
||||
var extractId = int.Parse(extractIdObj.ToString()!);
|
||||
var extractRole = await _extractService.GetCurrentUserRoleAsync(extractId, System.Threading.CancellationToken.None);
|
||||
|
||||
var extract =
|
||||
await _extractService.GetByIdAsync(extractId, System.Threading.CancellationToken.None);
|
||||
if (extract == null)
|
||||
{
|
||||
context.Fail();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!extractRole.HasValue || extractRole.Value < requirement.MinimumRole)
|
||||
{
|
||||
context.Fail();
|
||||
return;
|
||||
}
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Strata.Analytics.Biz.Extracts.Models;
|
||||
|
||||
namespace Strata.Analytics.Api.Authorization
|
||||
{
|
||||
public class ExtractMinimumRoleRequirement : IAuthorizationRequirement
|
||||
{
|
||||
public ExtractRole MinimumRole { get; set; }
|
||||
|
||||
public ExtractMinimumRoleRequirement(ExtractRole role)
|
||||
{
|
||||
MinimumRole = role;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Strata.Analytics.Api.Authorization
|
||||
{
|
||||
public class Policies
|
||||
{
|
||||
public const string StrataOnly = "StrataOnly";
|
||||
public const string IsAdmin = "IsAdmin";
|
||||
public const string DataSourceViewer = "DataSourceViewer";
|
||||
public const string DataSourceEditor = "DataSourceEditor";
|
||||
public const string WorkbookViewer = "WorkbookViewer";
|
||||
public const string WorkbookEditor = "WorkbookEditor";
|
||||
public const string ExtractViewer = "ExtractViewer";
|
||||
public const string ExtractEditor = "ExtractEditor";
|
||||
public const string AuthorizedDbOnly = "AuthorizedDbOnly";
|
||||
public const string CustomSqlAccess = "CustomSqlAccess";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Strata.Analytics.Biz.Workbooks.Services;
|
||||
|
||||
namespace Strata.Analytics.Api.Authorization
|
||||
{
|
||||
public class WorkbookMinimumRoleHandler : AuthorizationHandler<WorkbookMinimumRoleRequirement>
|
||||
{
|
||||
private readonly IWorkbookService _workbookService;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public WorkbookMinimumRoleHandler(IWorkbookService workbookService, IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_workbookService = workbookService;
|
||||
_httpContextAccessor = httpContextAccessor;;
|
||||
}
|
||||
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, WorkbookMinimumRoleRequirement requirement)
|
||||
{
|
||||
var workbookIdObj = _httpContextAccessor.HttpContext.GetRouteData()?.Values["workbookId"];
|
||||
if (workbookIdObj == null)
|
||||
{
|
||||
context.Fail();
|
||||
return;
|
||||
}
|
||||
|
||||
var workbookId = int.Parse(workbookIdObj.ToString());
|
||||
var workbookRole = await _workbookService.GetCurrentUserRoleAsync(workbookId, System.Threading.CancellationToken.None);
|
||||
|
||||
if (!workbookRole.HasValue || workbookRole.Value < requirement.MinimumRole)
|
||||
{
|
||||
context.Fail();
|
||||
return;
|
||||
}
|
||||
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Strata.Analytics.Models.Workbooks;
|
||||
|
||||
namespace Strata.Analytics.Api.Authorization
|
||||
{
|
||||
public class WorkbookMinimumRoleRequirement : IAuthorizationRequirement
|
||||
{
|
||||
public WorkbookRole MinimumRole { get; set; }
|
||||
|
||||
public WorkbookMinimumRoleRequirement(WorkbookRole role)
|
||||
{
|
||||
MinimumRole = role;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Strata.Analytics.Biz.ContentMigration.Services;
|
||||
using Strata.Analytics.Biz.ContentMigration.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Strata.Analytics.Api.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Strata.Analytics.Biz.ContentMigration.Models.MigrationPlan;
|
||||
|
||||
namespace Strata.Analytics.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class ContentMigrationController : ControllerBase
|
||||
{
|
||||
private readonly IContentMigrationService _contentMigrationService;
|
||||
|
||||
public ContentMigrationController(IContentMigrationService contentMigrationService)
|
||||
{
|
||||
_contentMigrationService = contentMigrationService;
|
||||
}
|
||||
|
||||
[HttpGet("legacy-reports")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<IEnumerable<ReReportDisplay>> GetAdHocReports(CancellationToken cancellationToken)
|
||||
{
|
||||
return await _contentMigrationService.GetAdHocReports(cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("{reportGuid}/migrate")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<AdhocMigrationResult>> MigrateReport([FromRoute] Guid reportGuid, [FromQuery, BindRequired] int workbookId, [FromQuery, BindRequired] int dataSourceId, [FromBody] Plan plan, CancellationToken cancellationToken)
|
||||
{
|
||||
var migratedReport = await _contentMigrationService.MigrateReport(reportGuid, workbookId, dataSourceId, plan, cancellationToken);
|
||||
if (migratedReport == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
return Ok(migratedReport);
|
||||
}
|
||||
|
||||
[HttpPost("{reportGuid}/migrateExtract")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<ExtractMigrationResult>> MigrateReportToExtract([FromRoute] Guid reportGuid, [FromQuery, BindRequired] int dataSourceId, [FromBody] Plan plan, CancellationToken cancellationToken)
|
||||
{
|
||||
var migratedReport = await _contentMigrationService.MigrateReportToExtract(reportGuid, dataSourceId, plan, cancellationToken);
|
||||
if (migratedReport == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
return Ok(migratedReport);
|
||||
}
|
||||
|
||||
[HttpGet("migrationPlan")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Plan>> GetMigrationPlan([FromQuery, BindRequired] Guid reportGuid, [FromQuery, BindRequired] int dataSourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
var migrationPlan = await _contentMigrationService.GetMigrationPlan(reportGuid, dataSourceId, cancellationToken);
|
||||
if (migrationPlan == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
return Ok(migrationPlan);
|
||||
}
|
||||
|
||||
[HttpPut("dataMappings")]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<DataMapping>> UpdateDataMapping([FromBody] DataMapping dataMapping, CancellationToken cancellationToken)
|
||||
{
|
||||
var updatedDataMapping = await _contentMigrationService.UpdateDataMapping(dataMapping, cancellationToken);
|
||||
if (updatedDataMapping == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
return Ok(updatedDataMapping);
|
||||
}
|
||||
|
||||
[HttpGet("dataMappings")]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<DataMapping>>> GetDataMappings(CancellationToken cancellationToken)
|
||||
{
|
||||
var dataMappings = await _contentMigrationService.GetDataMappings(cancellationToken);
|
||||
return Ok(dataMappings);
|
||||
}
|
||||
|
||||
[HttpPost("dataMappings")]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public async Task<ActionResult> AddDataMappings([FromBody] IEnumerable<DataMapping> dataMappings, CancellationToken cancellationToken)
|
||||
{
|
||||
await _contentMigrationService.AddDataMappings(dataMappings, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("dataMappings/{dataMappingId}")]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> DeleteDataMapping([FromRoute] int dataMappingId, CancellationToken cancellationToken)
|
||||
{
|
||||
var mapping = await _contentMigrationService.GetDataMapping(dataMappingId, cancellationToken);
|
||||
if (mapping == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
await _contentMigrationService.DeleteDataMapping(mapping, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Strata.Analytics.Api.Authorization;
|
||||
using Strata.Analytics.Biz.DataQuery;
|
||||
using Strata.Analytics.Biz.DataSources.Models;
|
||||
using Strata.Analytics.Biz.DataSources.Services;
|
||||
using Strata.Analytics.Biz.Export.Models;
|
||||
using Strata.Analytics.Biz.Export.Services;
|
||||
using Strata.Analytics.Models.Utils;
|
||||
using Strata.DataSchema.Models.Query;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.Analytics.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class DataSourcesController : ControllerBase
|
||||
{
|
||||
private readonly IDataSourceService _dataSourceService;
|
||||
private readonly IDataQueryService _dataQueryService;
|
||||
private readonly IExportService _exportService;
|
||||
private readonly ILogger<DataSourcesController> _logger;
|
||||
|
||||
public DataSourcesController(IDataSourceService dataSourceService, IDataQueryService dataQueryService, IExportService exportService, ILogger<DataSourcesController> logger)
|
||||
{
|
||||
_dataSourceService = dataSourceService;
|
||||
_dataQueryService = dataQueryService;
|
||||
_exportService = exportService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IEnumerable<DataSource>> GetDataSourcesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
return await _dataSourceService.GetAllAsync(cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{dataSourceId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<DataSource>> GetById(int dataSourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
var dataSource = await _dataSourceService.GetByIdAsync(dataSourceId, cancellationToken);
|
||||
if (dataSource == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(dataSource);
|
||||
}
|
||||
|
||||
[HttpPut("{dataSourceId}")]
|
||||
[Authorize(Policy = Policies.DataSourceEditor)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<DataSource>> Update(int dataSourceId, DataSourceSaveInfo dataSourceSaveInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var isFound = await _dataSourceService.ContainsAsync(dataSourceId, cancellationToken);
|
||||
if (!isFound)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var updatedDataSource = await _dataSourceService.UpdateAsync(dataSourceId, dataSourceSaveInfo, cancellationToken);
|
||||
|
||||
return Ok(updatedDataSource);
|
||||
}
|
||||
|
||||
[HttpPut("")]
|
||||
[Authorize(Policy = Policies.DataSourceEditor)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<DataSource>> Save(DataSourceSaveInfo dataSourceSaveInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var isFound = await _dataSourceService.ContainsAsync(dataSourceSaveInfo.DataSourceId, cancellationToken);
|
||||
if (!isFound)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var updatedDataSource = await _dataSourceService.SaveAsync(dataSourceSaveInfo, cancellationToken);
|
||||
|
||||
return Ok(updatedDataSource);
|
||||
}
|
||||
|
||||
[HttpDelete("{dataSourceId}")]
|
||||
[Authorize(Policy = Policies.DataSourceEditor)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<IActionResult> Delete(int dataSourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
await _dataSourceService.DeleteAsync(dataSourceId, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult<DataSource>> Create(DataSourceSaveInfo dataSourceSaveInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _dataSourceService.CreateAsync(dataSourceSaveInfo, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("{dataSourceId}/tables/{dataTableId}")]
|
||||
[Authorize(Policy = Policies.DataSourceEditor)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<DataSourceTable>> AddDataTableLink([FromRoute] int dataSourceId, [FromRoute] int dataTableId, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _dataSourceService.AddDataTableAsync(dataSourceId, dataTableId, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpDelete("{dataSourceId}/tables/{dataTableId}")]
|
||||
[Authorize(Policy = Policies.DataSourceEditor)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult> DeleteDataTableLink([FromRoute] int dataSourceId, [FromRoute] int dataTableId, CancellationToken cancellationToken)
|
||||
{
|
||||
await _dataSourceService.DeleteDataTableAsync(dataSourceId, dataTableId, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("{dataSourceId}/role-assignments")]
|
||||
[Authorize(Policy = Policies.DataSourceViewer)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<DataSourceRoleAssignment>>> GetRoleAssignments([FromRoute] int dataSourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await _dataSourceService.GetRoleAssignmentsAsync(new List<int> { dataSourceId }, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("{dataSourceId}/role-assignments/batch")]
|
||||
[Authorize(Policy = Policies.DataSourceEditor)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<DataSourceRoleAssignment>>> SaveRoleAssignments([FromRoute] int dataSourceId, [FromBody] Batch<DataSourceRoleAssignmentSaveInfo, Guid> roleAssignments, CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await _dataSourceService.SaveRoleAssignmentsAsync(dataSourceId, roleAssignments, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("{dataSourceId}/datacolumn/values")]
|
||||
[Authorize(Policy = Policies.DataSourceViewer)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<dynamic>>> GetColumnValues([FromRoute] int dataSourceId, [FromBody] ColumnQueryConfig columnQueryConfig, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Ok(await _dataQueryService.GetColumnValues(columnQueryConfig, cancellationToken));
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
var dataColumnIds = new List<int> { columnQueryConfig.DataColumnId };
|
||||
if (columnQueryConfig.Filter != null)
|
||||
dataColumnIds.Add(columnQueryConfig.Filter.DataColumnId);
|
||||
_logger.LogWarning(
|
||||
"Attempted to {method} for data source {dataSourceId} with the following data column ids {dataColumnIds}",
|
||||
nameof(GetColumnValues),
|
||||
dataSourceId,
|
||||
dataColumnIds);
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("{dataSourceId}/query")]
|
||||
[Authorize(Policy = Policies.DataSourceViewer)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<dynamic>>> QueryData([FromRoute] int dataSourceId, [FromBody] QueryConfig query, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Ok(await _dataQueryService.QueryData(query, cancellationToken));
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Attempted to {method} for data source {dataSourceId} with the following data column ids {dataColumnIds}",
|
||||
nameof(QueryData),
|
||||
dataSourceId,
|
||||
query.GetAllColumnIds());
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("query")]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<SqlResponse>> GetSqlQuery(QueryConfig queryConfig, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Ok(await _dataQueryService.BuildSqlQuery(queryConfig, cancellationToken));
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Attempted to {method} with the following data column ids {dataColumnIds}",
|
||||
nameof(GetSqlQuery),
|
||||
queryConfig.GetAllColumnIds());
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("{dataSourceId}/download")]
|
||||
[Authorize(Policy = Policies.DataSourceViewer)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult> DownloadDataSheet([FromRoute] int dataSourceId, [FromBody] ExportDataSheetRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var stream = await _exportService.DownloadDataSheet(request, cancellationToken);
|
||||
return File(stream, "application/octet-stream");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using Strata.Analytics.Biz.Database;
|
||||
|
||||
namespace Strata.Analytics.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class DatabasesController : ControllerBase
|
||||
{
|
||||
private readonly IDatabaseService _databaseService;
|
||||
|
||||
public DatabasesController(IDatabaseService databaseService)
|
||||
{
|
||||
_databaseService = databaseService;
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
public async Task<IEnumerable<Database>> GetDatabases(CancellationToken cancellationToken)
|
||||
{
|
||||
return await _databaseService.GetDatabasesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Strata.Analytics.Biz.Encounters;
|
||||
using Strata.Analytics.Api.Models;
|
||||
|
||||
namespace Strata.Analytics.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class EncountersController : ControllerBase
|
||||
{
|
||||
private readonly IEncounterService _encounterService;
|
||||
|
||||
public EncountersController(IEncounterService encounterService)
|
||||
{
|
||||
_encounterService = encounterService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given an encounter record number, return the encounter id
|
||||
/// </summary>
|
||||
[HttpPost("encounter-id")]
|
||||
public async Task<int> GetEncounterId([FromBody] EncounterInfo encounterInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
// Needs to be POST to prevent PHI data in url string
|
||||
return await _encounterService.GetEncounterIdAsync(encounterInfo.EncounterRecordNumber, cancellationToken);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Strata.Analytics.Biz.Extracts.Models;
|
||||
using Strata.Analytics.Biz.Extracts.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Hangfire;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Strata.Analytics.Api.Authorization;
|
||||
using Strata.Analytics.Models.Utils;
|
||||
using System;
|
||||
using Strata.Analytics.Biz.Extracts.Jobs;
|
||||
using System.Security.Claims;
|
||||
using Hangfire.States;
|
||||
using Strata.CoreLib.Claims;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
|
||||
namespace Strata.Analytics.Api.Controllers
|
||||
{
|
||||
[Authorize(Policy = Policies.IsAdmin)]
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class ExtractsController : ControllerBase
|
||||
{
|
||||
private readonly IExtractService _extractService;
|
||||
private readonly IBackgroundJobClient _backgroundJobClient;
|
||||
private readonly IExtractExportHistoryService _historyService;
|
||||
private readonly IExtractScheduleService _extractScheduleService;
|
||||
private readonly ClaimsPrincipal _user;
|
||||
|
||||
public ExtractsController(IExtractService extractService, IBackgroundJobClient backgroundJobClient, IExtractExportHistoryService historyService, IExtractScheduleService extractScheduleService, IClaimsPrincipalAccessor claimsPrincipalAccessor)
|
||||
{
|
||||
_extractService = extractService;
|
||||
_backgroundJobClient = backgroundJobClient;
|
||||
_historyService = historyService;
|
||||
_extractScheduleService = extractScheduleService;
|
||||
_user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IEnumerable<Extract>> GetExtractsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return await _extractService.GetAllAsync(cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{extractId}")]
|
||||
[Authorize(Policy = Policies.ExtractViewer)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<Extract>> GetExtractById(int extractId, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
var extract = await _extractService.GetByIdAsync(extractId, cancellationToken);
|
||||
if (extract == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(extract);
|
||||
}
|
||||
|
||||
[HttpDelete("{extractId}")]
|
||||
[Authorize(Policy = Policies.CustomSqlAccess)]
|
||||
[Authorize(Policy = Policies.ExtractEditor)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<IActionResult> DeleteExtract(int extractId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _extractService.ContainsAsync(extractId, cancellationToken))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
await _extractScheduleService.DeleteByExtractIdAsync(extractId, cancellationToken);
|
||||
await _extractService.DeleteAsync(extractId, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<Extract>> CreateExtract(ExtractCreateInfo extractCreateInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _extractService.CreateAsync(extractCreateInfo, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPut("{extractId}")]
|
||||
[Authorize(Policy = Policies.CustomSqlAccess)]
|
||||
[Authorize(Policy = Policies.ExtractEditor)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<Extract>> UpdateExtract([FromRoute] int extractId, [FromBody] ExtractSaveInfo extractSaveInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _extractService.ContainsAsync(extractId, cancellationToken))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(await _extractService.UpdateAsync(extractId, extractSaveInfo, cancellationToken));
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("export/{extractId}")]
|
||||
[ProducesResponseType(StatusCodes.Status202Accepted)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
[ProducesDefaultResponseType]
|
||||
[Authorize(Policy = Policies.ExtractEditor)]
|
||||
public async Task<IActionResult> ExportExtract(int extractId, [FromBody] ExtractExportRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _extractService.ContainsAsync(request.ExtractId, cancellationToken))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
request.Timezone = _user.GetTimezone();
|
||||
|
||||
var jobId = _backgroundJobClient.Enqueue<IExtractExportJob>(job =>
|
||||
job.ExecuteExport(request.OutputFileName, _user.GetDatabaseGuid(), _user.GetStrataId(), _user.GetUserGuid(), request, null, cancellationToken));
|
||||
|
||||
return Accepted(new { jobId });
|
||||
}
|
||||
|
||||
[HttpDelete("{extractId}/history/{extractHistoryId}/cancel/")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
[ProducesDefaultResponseType]
|
||||
[Authorize(Policy = Policies.ExtractEditor)]
|
||||
public async Task<ActionResult<ExtractExportHistory>> CancelExportExtract([FromRoute] int extractId, [FromRoute] int extractHistoryId, CancellationToken cancellationToken)
|
||||
{
|
||||
var history = await _historyService.CancelExtractExport(extractHistoryId, cancellationToken);
|
||||
|
||||
if (history == null) return NotFound();
|
||||
|
||||
_backgroundJobClient.ChangeState(history.JobId, new DeletedState {Reason = "Job cancelled manually" });
|
||||
|
||||
return Ok(history);
|
||||
}
|
||||
|
||||
[HttpGet("history/{extractId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
[Authorize(Policy = Policies.ExtractViewer)]
|
||||
public async Task<ActionResult<List<ExtractExportHistory>>> GetHistoryById(int extractId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _extractService.ContainsAsync(extractId, cancellationToken))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(await _historyService.GetByExtractIdAsync(extractId, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("{extractId}/role-assignments")]
|
||||
[Authorize(Policy = Policies.ExtractViewer)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<ExtractRoleAssignment>>> GetRoleAssignments([FromRoute] int extractId, CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await _extractService.GetRoleAssignmentsAsync(extractId, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("{extractId}/role-assignments/batch")]
|
||||
[Authorize(Policy = Policies.ExtractEditor)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<ExtractRoleAssignment>>> SaveRoleAssignments(
|
||||
[FromRoute] int extractId, [FromBody] Batch<ExtractRoleAssignmentSaveInfo, Guid> roleAssignments, CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await _extractService.SaveRoleAssignmentsAsync(extractId, roleAssignments, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("{extractId}/schedule")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
[Authorize(Policy = Policies.ExtractEditor)]
|
||||
public async Task<ActionResult<ExtractSchedule>> CreateExtractSchedule([FromRoute] int extractId, [FromBody] ExtractScheduleSaveInfo saveInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _extractService.ContainsAsync(extractId, cancellationToken))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(await _extractScheduleService.CreateAsync(saveInfo, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("{extractId}/schedule/{extractScheduleId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
[Authorize(Policy = Policies.ExtractEditor)]
|
||||
public async Task<ActionResult<ExtractSchedule>> UpdateExtractSchedule([FromRoute] int extractId, [FromRoute] int extractScheduleId, [FromBody] ExtractScheduleSaveInfo saveInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _extractScheduleService.ContainsAsync(extractScheduleId, cancellationToken))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(await _extractScheduleService.UpdateAsync(extractScheduleId, saveInfo, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("{extractId}/schedule")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesDefaultResponseType]
|
||||
[Authorize(Policy = Policies.ExtractEditor)]
|
||||
public async Task<ActionResult<ExtractSchedule>> GetScheduleByExtractId([FromRoute] int extractId, CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await _extractScheduleService.GetByExtractIdAsync(extractId, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpDelete("{extractId}/schedule/{extractScheduleId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
[Authorize(Policy = Policies.ExtractEditor)]
|
||||
public async Task<ActionResult> DeleteExtractSchedule([FromRoute] int extractId, [FromRoute] int extractScheduleId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _extractScheduleService.ContainsAsync(extractScheduleId, cancellationToken))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
await _extractScheduleService.DeleteAsync(extractScheduleId, cancellationToken);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Threading.Tasks;
|
||||
using Strata.FeatureFlags.Client;
|
||||
using System.Security.Claims;
|
||||
using Strata.CoreLib.Claims;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
|
||||
namespace Strata.Analytics.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class FeatureController : ControllerBase
|
||||
{
|
||||
private readonly IFeatureFlagServiceClient _featureFlagService;
|
||||
private readonly ClaimsPrincipal _user;
|
||||
|
||||
public FeatureController(IFeatureFlagServiceClient featureFlagService, IClaimsPrincipalAccessor claimsPrincipalAccessor)
|
||||
{
|
||||
_featureFlagService = featureFlagService;
|
||||
_user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal();
|
||||
}
|
||||
|
||||
[HttpGet("dashboards-enabled")]
|
||||
public async Task<bool> IsDashboardsEnabled()
|
||||
{
|
||||
return await _featureFlagService.IsEnabledAsync("analytics-dashboards", _user.GetDatabaseGuid());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Strata.Analytics.Biz.Workbooks.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Strata.Analytics.Models.Workbooks;
|
||||
|
||||
namespace Strata.Analytics.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class SavedFiltersController : ControllerBase
|
||||
{
|
||||
private readonly ISavedFilterService _savedFilterService;
|
||||
|
||||
public SavedFiltersController(ISavedFilterService savedFiltersService)
|
||||
{
|
||||
_savedFilterService = savedFiltersService;
|
||||
}
|
||||
|
||||
[HttpGet("{savedFilterId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<SavedFilterInfo>> GetSavedFiltersAsync(int savedFilterId, CancellationToken cancellationToken)
|
||||
{
|
||||
var savedFilters = await _savedFilterService.GetSavedFiltersAsync(savedFilterId, cancellationToken);
|
||||
if (savedFilters == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(savedFilters);
|
||||
}
|
||||
|
||||
[HttpDelete("{savedFilterId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<IActionResult> Delete(int savedFilterId, CancellationToken cancellationToken)
|
||||
{
|
||||
await _savedFilterService.DeleteSavedFiltersAsync(savedFilterId, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<int>> Create(SavedFilterInfo savedFilters, CancellationToken cancellationToken)
|
||||
{
|
||||
var cache = await _savedFilterService.CreateSavedFiltersAsync(savedFilters, cancellationToken);
|
||||
|
||||
return cache.SavedFilterId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
using Hangfire;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Strata.Analytics.Api.Authorization;
|
||||
using Strata.Analytics.Api.Models;
|
||||
using Strata.Analytics.Biz.Database;
|
||||
using Strata.Analytics.Biz.DataQuery;
|
||||
using Strata.Analytics.Biz.DataSources.Models;
|
||||
using Strata.Analytics.Biz.Schema;
|
||||
using Strata.Analytics.Biz.Schema.Jobs;
|
||||
using Strata.Analytics.Biz.StandardDataSources.Jobs;
|
||||
using Strata.Analytics.Biz.StandardDataSources.Models;
|
||||
using Strata.Analytics.Biz.StandardDataSources.Services;
|
||||
using Strata.Analytics.Models;
|
||||
using Strata.Analytics.Models.Utils;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using Strata.DataSchema.Models.Query;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.Analytics.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class StandardDataSourcesController : ControllerBase
|
||||
{
|
||||
private readonly IStandardDataSourceService _standardDataSourceService;
|
||||
private readonly IDataQueryService _dataQueryService;
|
||||
private readonly IDatabaseService _databaseService;
|
||||
private readonly IStandardDataSourcePublishService _standardDataSourcePublishService;
|
||||
private readonly IBackgroundJobClient _backgroundJobClient;
|
||||
private readonly IPublishStandardDataSourcesJob _publishStandardDataSourcesJob;
|
||||
private readonly ILogger<StandardDataSourcesController> _logger;
|
||||
|
||||
public StandardDataSourcesController(
|
||||
IStandardDataSourceService standardDataSourceService,
|
||||
IDataQueryService dataQueryService,
|
||||
IDatabaseService databaseService,
|
||||
IStandardDataSourcePublishService standardDataSourcePublishService,
|
||||
IBackgroundJobClient backgroundJobClient,
|
||||
IPublishStandardDataSourcesJob publishStandardDataSourcesJob,
|
||||
ILogger<StandardDataSourcesController> logger)
|
||||
{
|
||||
_standardDataSourceService = standardDataSourceService;
|
||||
_dataQueryService = dataQueryService;
|
||||
_databaseService = databaseService;
|
||||
_standardDataSourcePublishService = standardDataSourcePublishService;
|
||||
_backgroundJobClient = backgroundJobClient;
|
||||
_publishStandardDataSourcesJob = publishStandardDataSourcesJob;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IEnumerable<StandardDataSource>> GetDataSourcesAsync(bool includeTableLinks = false, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _standardDataSourceService.GetAllAsync(includeTableLinks, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{standardDataSourceId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<StandardDataSource>> GetById(int standardDataSourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
var standardDataSource = await _standardDataSourceService.GetByIdAsync(standardDataSourceId, cancellationToken);
|
||||
if (standardDataSource == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(standardDataSource);
|
||||
}
|
||||
|
||||
[HttpPut("")]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
[Authorize(Policy = Policies.AuthorizedDbOnly)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<StandardDataSource>> Save(StandardDataSourceSaveInfo standardDataSourceSaveInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var isFound = await _standardDataSourceService.ContainsAsync(standardDataSourceSaveInfo.StandardDataSourceId, cancellationToken);
|
||||
if (!isFound)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var updatedDataSource = await _standardDataSourceService.SaveAsync(standardDataSourceSaveInfo, cancellationToken);
|
||||
|
||||
return Ok(updatedDataSource);
|
||||
}
|
||||
|
||||
[HttpDelete("{standardDataSourceId}")]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
[Authorize(Policy = Policies.AuthorizedDbOnly)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<IActionResult> Delete(int standardDataSourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
await _standardDataSourceService.DeleteAsync(standardDataSourceId, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
[Authorize(Policy = Policies.AuthorizedDbOnly)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult<StandardDataSource>> Create(StandardDataSourceSaveInfo standardDataSourceSaveInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _standardDataSourceService.CreateAsync(standardDataSourceSaveInfo, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{standardDataSourceId}/role-assignments")]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
[Authorize(Policy = Policies.AuthorizedDbOnly)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<StandardDataSourceRoleAssignment>>> GetRoleAssignments([FromRoute] int standardDataSourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await _standardDataSourceService.GetRoleAssignmentsAsync(new List<int> { standardDataSourceId }, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("{standardDataSourceId}/role-assignments/batch")]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
[Authorize(Policy = Policies.AuthorizedDbOnly)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<DataSourceRoleAssignment>>> SaveRoleAssignments([FromRoute] int standardDataSourceId, [FromBody] Batch<StandardDataSourceRoleAssignmentSaveInfo, Guid> roleAssignments, CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await _standardDataSourceService.SaveRoleAssignmentsAsync(standardDataSourceId, roleAssignments, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("{standardDataSourceId}/datacolumn/values")]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
[Authorize(Policy = Policies.AuthorizedDbOnly)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<dynamic>>> GetColumnValues([FromRoute] int standardDataSourceId, [FromBody] ColumnQueryConfig columnQueryConfig, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Ok(await _dataQueryService.GetColumnValues(columnQueryConfig, cancellationToken));
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
var dataColumnIds = new List<int> { columnQueryConfig.DataColumnId };
|
||||
if (columnQueryConfig.Filter != null)
|
||||
dataColumnIds.Add(columnQueryConfig.Filter.DataColumnId);
|
||||
_logger.LogWarning(
|
||||
"Attempted to {method} for standard data source {standardDataSourceId} with the following data column ids {dataColumnIds}",
|
||||
nameof(GetColumnValues),
|
||||
standardDataSourceId,
|
||||
dataColumnIds);
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("{standardDataSourceId}/query")]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
[Authorize(Policy = Policies.AuthorizedDbOnly)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<dynamic>>> QueryData([FromRoute] int standardDataSourceId, [FromBody] QueryConfig query, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Ok(await _dataQueryService.QueryData(query, cancellationToken));
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Attempted to {method} for standard data source {standardDataSourceId} with the following data column ids {dataColumnsIds}",
|
||||
nameof(QueryData),
|
||||
standardDataSourceId,
|
||||
query.GetAllColumnIds());
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("query")]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
[Authorize(Policy = Policies.AuthorizedDbOnly)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<SqlResponse>> GetSqlQuery(QueryConfig queryConfig, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Ok(await _dataQueryService.BuildSqlQuery(queryConfig, cancellationToken));
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Attempted to {method} with the following data column ids {dataColumnIds}",
|
||||
nameof(GetSqlQuery),
|
||||
queryConfig.GetAllColumnIds());
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("{standardDataSourceId}/include-in-publish")]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
[Authorize(Policy = Policies.AuthorizedDbOnly)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<StandardDataSource>> IncludeDataSourceInPublish([FromRoute] int standardDataSourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await _standardDataSourceService.SetIsIncludedInStandardModel(standardDataSourceId, true, cancellationToken));
|
||||
|
||||
}
|
||||
|
||||
[HttpPost("{standardDataSourceId}/exclude-from-publish")]
|
||||
[Authorize(Policy = Policies.StrataOnly)]
|
||||
[Authorize(Policy = Policies.AuthorizedDbOnly)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<StandardDataSource>> ExcludeDataSourceInPublish([FromRoute] int standardDataSourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await _standardDataSourceService.SetIsIncludedInStandardModel(standardDataSourceId, false, cancellationToken));
|
||||
|
||||
}
|
||||
|
||||
[HttpPost("publish")]
|
||||
[Authorize(Policy = Policies.AuthorizedDbOnly)]
|
||||
public async Task<string> PublishAsync([FromBody] PublishConfig publishConfig, CancellationToken cancellationToken)
|
||||
{
|
||||
if (publishConfig.StrataId == 0)
|
||||
{
|
||||
var databases = await _databaseService.GetDatabasesAsync(cancellationToken);
|
||||
return _backgroundJobClient.Enqueue<PublishStandardDataSourcesAllClientsJob>(job => job.ExecuteAsync(databases.ToList(), User.GetUsername(), DateTime.UtcNow, CancellationToken.None));
|
||||
}
|
||||
|
||||
if (Debugger.IsAttached && publishConfig.StrataId == User.GetStrataId())
|
||||
{
|
||||
await _publishStandardDataSourcesJob.ExecuteAsync(User.GetDatabaseGuid(), User.GetStrataId(), Guid.NewGuid().ToString(), User.GetUsername(), DateTime.UtcNow, cancellationToken);
|
||||
return "";
|
||||
}
|
||||
var database = await _databaseService.GetDatabaseByStrataId(publishConfig.StrataId, cancellationToken);
|
||||
var parentJobId = _backgroundJobClient.Enqueue<PublishStandardDataSourcesJob>(job => job.ExecuteAsync(database.DatabaseGuid, database.StrataId, Guid.NewGuid().ToString(), User.GetUsername(), DateTime.UtcNow, CancellationToken.None));
|
||||
|
||||
return parentJobId;
|
||||
}
|
||||
|
||||
[HttpGet("publish-history")]
|
||||
public async Task<ActionResult<IEnumerable<PublicationEvent>>> GetPublicationHistory(CancellationToken cancellationToken)
|
||||
{
|
||||
var publications = await _standardDataSourcePublishService.GetPublicationsAsync(cancellationToken);
|
||||
|
||||
return Ok(publications
|
||||
.GroupBy(x => x.RequestCorrelationId)
|
||||
.Select(x => new PublicationEvent
|
||||
{
|
||||
RequestDate = x.FirstOrDefault().RequestDate,
|
||||
RequestedBy = x.FirstOrDefault().RequestedBy,
|
||||
StandardDataSourceIds = x.Select(y => y.StandardDataSourceId).Distinct(),
|
||||
StrataIds = x.Select(y => y.ClientId).Distinct()
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Strata.Users.Client;
|
||||
using Strata.Users.Client.UserGroups.Models;
|
||||
using System.Linq;
|
||||
|
||||
namespace Strata.Analytics.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class UserGroupsController : ControllerBase
|
||||
{
|
||||
private readonly IUsersServiceClient _usersService;
|
||||
|
||||
public UserGroupsController(IUsersServiceClient usersService)
|
||||
{
|
||||
_usersService = usersService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IEnumerable<UserGroup>> GetUserGroups(CancellationToken cancellationToken)
|
||||
{
|
||||
var userGroups = await _usersService.GetAllUserGroupsAsync(cancellationToken);
|
||||
return userGroups.Where(ug => !ug.IsHidden);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Strata.Users.Client;
|
||||
using Strata.Users.Client.Users.Models;
|
||||
|
||||
namespace Strata.Analytics.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class UsersController : ControllerBase
|
||||
{
|
||||
private readonly IUsersServiceClient _usersService;
|
||||
|
||||
public UsersController(IUsersServiceClient usersService)
|
||||
{
|
||||
_usersService = usersService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IEnumerable<User>> GetUsers(CancellationToken cancellationToken)
|
||||
{
|
||||
return await _usersService.GetAllUsersAsync(cancellationToken);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Strata.Analytics.Biz.Workbooks.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Strata.Analytics.Api.Authorization;
|
||||
using Strata.Analytics.Models.Utils;
|
||||
using System;
|
||||
using Strata.Analytics.Models.Workbooks;
|
||||
|
||||
namespace Strata.Analytics.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{api-version:apiVersion}/[controller]")]
|
||||
public class WorkbooksController : ControllerBase
|
||||
{
|
||||
private readonly IWorkbookService _workbookService;
|
||||
|
||||
public WorkbooksController(IWorkbookService workbookService)
|
||||
{
|
||||
_workbookService = workbookService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IEnumerable<Workbook>> GetWorkbooksAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
return await _workbookService.GetAllAsync(cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{workbookId}")]
|
||||
[Authorize(Policy = Policies.WorkbookViewer)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<Workbook>> GetById(int workbookId, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
var workbook = await _workbookService.GetByIdAsync(workbookId, cancellationToken);
|
||||
if (workbook == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(workbook);
|
||||
}
|
||||
|
||||
// Delete should return 404 if id doesn't exist. Use Contains instead of loading entire object into memory to check for existence.
|
||||
[HttpDelete("{workbookId}")]
|
||||
[Authorize(Policy = Policies.WorkbookEditor)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<IActionResult> Delete(int workbookId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _workbookService.ContainsAsync(workbookId, cancellationToken))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
await _workbookService.DeleteAsync(workbookId, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<Workbook>> Create(WorkbookCreateInfo workbookCreateInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _workbookService.CreateAsync(workbookCreateInfo, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPut("{workbookId}")]
|
||||
[Authorize(Policy = Policies.WorkbookEditor)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<Workbook>> UpdateWorkbook([FromRoute] int workbookId, [FromBody] WorkbookSaveInfo workbookSaveInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _workbookService.ContainsAsync(workbookId, cancellationToken))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
return await _workbookService.UpdateAsync(workbookId, workbookSaveInfo, cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("{workbookId}/role-assignments")]
|
||||
[Authorize(Policy = "WorkbookViewer")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<WorkbookRoleAssignment>>> GetRoleAssignments([FromRoute] int workbookId, CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await _workbookService.GetRoleAssignmentsAsync(workbookId, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("{workbookId}/role-assignments/batch")]
|
||||
[Authorize(Policy = Policies.WorkbookEditor)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesDefaultResponseType]
|
||||
public async Task<ActionResult<IEnumerable<WorkbookRoleAssignment>>> SaveRoleAssignments([FromRoute] int workbookId, [FromBody] Batch<WorkbookRoleAssignmentSaveInfo, Guid> roleAssignments, CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await _workbookService.SaveRoleAssignmentsAsync(workbookId, roleAssignments, cancellationToken));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Strata.Analytics.Api.Models
|
||||
{
|
||||
public class EncounterInfo
|
||||
{
|
||||
public string EncounterRecordNumber { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Strata.Analytics.Models
|
||||
{
|
||||
public class PublicationEvent
|
||||
{
|
||||
public DateTime RequestDate { get; set; }
|
||||
public string RequestedBy { get; set; }
|
||||
public IEnumerable<int> StandardDataSourceIds { get; set; }
|
||||
public IEnumerable<int> StrataIds { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Strata.Analytics.Api.Models
|
||||
{
|
||||
public class PublishConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 0 means to publish to all clients
|
||||
/// </summary>
|
||||
public int StrataId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.CommandLineUtils;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serilog;
|
||||
using Strata.Configuration.Client.DependencyInjection;
|
||||
using Strata.Logging.DependencyInjection;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Strata.Analytics.Biz.DbContexts.Factories;
|
||||
|
||||
namespace Strata.Analytics.Api
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var commandLineApplication = new CommandLineApplication(false);
|
||||
var doMigrate = commandLineApplication.Option(
|
||||
"--ef-migrate",
|
||||
"Apply entity framework migrations and exit",
|
||||
CommandOptionType.NoValue);
|
||||
|
||||
commandLineApplication.HelpOption("-? | -h | --help");
|
||||
commandLineApplication.OnExecute(() =>
|
||||
{
|
||||
ExecuteApp(args, doMigrate);
|
||||
return 0;
|
||||
});
|
||||
commandLineApplication.Execute(args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "There was an unhandled exception that caused the program to crash");
|
||||
|
||||
//This is for serilog to ensure that all of the cached logs are flushed when the app crashes
|
||||
//This is useful so we can capture logs when the application crashes during startup:
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
}
|
||||
|
||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseStrataConfiguration()
|
||||
.UseStrataLogging()
|
||||
.UseStartup<Startup>();
|
||||
|
||||
public static void ExecuteApp(string[] args, CommandOption doMigrate)
|
||||
{
|
||||
var webHost = CreateWebHostBuilder(args).Build();
|
||||
|
||||
if (doMigrate.HasValue())
|
||||
{
|
||||
Log.Debug("Applying Entity Framework migrations");
|
||||
|
||||
using var scope = webHost.Services.CreateScope();
|
||||
using var context = new AnalyticsDbContextFactory().CreateDbContext(new string[] { });
|
||||
context.Database.Migrate();
|
||||
Log.Debug("All done, closing app");
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
// no flags provided, so just run the webhost
|
||||
webHost.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Strata.Analytics.Api": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"Hangfire__RunLocally": "true",
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"AWS_Profile": "sdt-analytics-service-role",
|
||||
"AWS_REGION": "us-east-1",
|
||||
"AWS_S3_US_EAST_1_REGIONAL_ENDPOINT": "regional"
|
||||
},
|
||||
"applicationUrl": "https://localhost:8443"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
using Hangfire;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Prometheus;
|
||||
using Strata.Analytics.Api.Authorization;
|
||||
using Strata.Analytics.Biz.DataSources.Models;
|
||||
using Strata.Analytics.Biz.Extracts.Models;
|
||||
using Strata.Analytics.Biz.Utils;
|
||||
using Strata.Analytics.Models.Workbooks;
|
||||
using Strata.ApiLib.Core;
|
||||
using Strata.ApiLib.Core.Transformers;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using Strata.Hangfire.AspNetCore.Filters.Dashboard;
|
||||
using Strata.Hangfire.TaskManagement.AspNetCore;
|
||||
using Strata.SwaggerExtensions;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net.Mime;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using AuthorizationOptions = Strata.Analytics.Api.Authorization.AuthorizationOptions;
|
||||
|
||||
namespace Strata.Analytics.Api
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class Startup
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddStrataPlatformServices(_configuration);
|
||||
services.AddControllers(options =>
|
||||
{
|
||||
//Prevent browsers from caching API responses (this addresses an IE11 issue):
|
||||
options.Filters.Add(
|
||||
new ResponseCacheAttribute()
|
||||
{
|
||||
Location = ResponseCacheLocation.None,
|
||||
NoStore = false,
|
||||
Duration = -1
|
||||
});
|
||||
options.ReturnHttpNotAcceptable = true;
|
||||
options.Filters.Add(new ProducesAttribute(MediaTypeNames.Application.Json));
|
||||
options.Filters.Add(new ConsumesAttribute(MediaTypeNames.Application.Json));
|
||||
options.Conventions.Add(new RouteTokenTransformerConvention(new SlugifyParameterTransformer()));
|
||||
})
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
|
||||
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
|
||||
|
||||
// Uncomment this line if you want enum to be serialized / deserialized as string value instead of int value
|
||||
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
|
||||
|
||||
// Date and Time Only serialization/deserialization support
|
||||
options.JsonSerializerOptions.Converters.Add(new DateOnlyJsonConverter());
|
||||
options.JsonSerializerOptions.Converters.Add(new TimeOnlyJsonConverter());
|
||||
});
|
||||
|
||||
// authorization
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy(Policies.DataSourceViewer,
|
||||
policy => policy.Requirements.Add(new DataSourceMinimumRoleRequirement(DataSourceRole.Viewer)));
|
||||
options.AddPolicy(Policies.DataSourceEditor,
|
||||
policy => policy.Requirements.Add(new DataSourceMinimumRoleRequirement(DataSourceRole.Editor)));
|
||||
options.AddPolicy(Policies.WorkbookViewer,
|
||||
policy => policy.Requirements.Add(new WorkbookMinimumRoleRequirement(WorkbookRole.Viewer)));
|
||||
options.AddPolicy(Policies.WorkbookEditor,
|
||||
policy => policy.Requirements.Add(new WorkbookMinimumRoleRequirement(WorkbookRole.Editor)));
|
||||
options.AddPolicy(Policies.ExtractViewer,
|
||||
policy => policy.Requirements.Add(new ExtractMinimumRoleRequirement(ExtractRole.Viewer)));
|
||||
options.AddPolicy(Policies.ExtractEditor,
|
||||
policy => policy.Requirements.Add(new ExtractMinimumRoleRequirement(ExtractRole.Editor)));
|
||||
options.AddPolicy(Policies.CustomSqlAccess,
|
||||
policy => policy.Requirements.Add(new ExtractCustomSqlStrataOnlyRoleRequirement()));
|
||||
options.AddPolicy(Policies.StrataOnly,
|
||||
policy => policy.RequireAssertion(context => context.User.IsSdtEmployee()));
|
||||
options.AddPolicy(Policies.IsAdmin,
|
||||
policy => policy.RequireAssertion(context => context.User.IsAdmin()));
|
||||
options.AddPolicy(Policies.AuthorizedDbOnly, policy => policy.Requirements.Add(new AuthorizedDbMinimumRoleRequirement()));
|
||||
});
|
||||
services
|
||||
.AddOptions<AuthorizationOptions>()
|
||||
.Configure<IConfiguration>((options, configuration) =>
|
||||
{
|
||||
configuration.Bind("authorization", options);
|
||||
})
|
||||
.ValidateDataAnnotations();
|
||||
|
||||
services.AddScoped<IAuthorizationHandler, AuthorizedDbMinimumRoleHandler>();
|
||||
services.AddScoped<IAuthorizationHandler, DataSourceMinimumRoleHandler>();
|
||||
services.AddScoped<IAuthorizationHandler, WorkbookMinimumRoleHandler>();
|
||||
services.AddScoped<IAuthorizationHandler, ExtractMinimumRoleHandler>();
|
||||
services.AddScoped<IAuthorizationHandler, CustomSQLStrataOnlyRoleHandler>();
|
||||
|
||||
// configure hangfire
|
||||
services.AddStrataHangfire();
|
||||
services.AddStrataHangfireTaskManagement();
|
||||
services.AddAnalyticsServices(_configuration);
|
||||
|
||||
services.AddRouting();
|
||||
services.AddSwagger(_configuration);
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
|
||||
{
|
||||
app.UseRouting();
|
||||
|
||||
app.UseStrataPlatformMiddleware();
|
||||
app.UseMetricServer();
|
||||
app.UseHttpMetrics();
|
||||
app.ConfigureSwagger(env, provider);
|
||||
app.UseStrataHangfireTaskManagement();
|
||||
|
||||
#if DEBUG
|
||||
app.UseHangfireDashboard("/hangfire", new DashboardOptions
|
||||
{
|
||||
Authorization = new[] { new AllowAnyoneDashboardAuthorizationFilter() }
|
||||
});
|
||||
#endif
|
||||
app.UseEndpoints(endpoints => endpoints.MapControllers().RequireAuthorization());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
||||
<IncludeOpenAPIAnalyzers>true</IncludeOpenAPIAnalyzers>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.22">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.1" />
|
||||
<PackageReference Include="Strata.ApiLib.Core" Version="5.3.0" />
|
||||
<PackageReference Include="Strata.Configuration.Client" Version="8.26.0" />
|
||||
<PackageReference Include="Strata.Hangfire.AspNetCore" Version="4.0.0" />
|
||||
<PackageReference Include="Strata.Hangfire.TaskManagement.AspNetCore" Version="0.6.0" />
|
||||
<PackageReference Include="Strata.Identity.Client" Version="3.1.0" />
|
||||
<PackageReference Include="Strata.Logging" Version="3.6.1" />
|
||||
<PackageReference Include="Strata.SwaggerExtensions" Version="2.3.0" />
|
||||
<PackageReference Include="prometheus-net.AspNetCore" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Strata.Analytics.Biz\Strata.Analytics.Biz.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
},
|
||||
"hangfire": {
|
||||
"redis": {
|
||||
"prefix": "analytics-jobs-service",
|
||||
"configOptions": "ddenredisc01.sdt.local:6379,ddenredisc02.sdt.local:6379,ddenredisc03.sdt.local:6379,ddenredisc04.sdt.local:6379,ddenredisc05.sdt.local:6379,ddenredisc06.sdt.local:6379"
|
||||
}
|
||||
},
|
||||
"endpoints": {
|
||||
"dataschema": "https://dataschema-api.dev.stratanetwork.net/",
|
||||
//"dataschema": "https://localhost:9443/",
|
||||
"systemsettings": "https://settings-api.dev.stratanetwork.net/"
|
||||
},
|
||||
"strataApi": {
|
||||
"clientSecret": "V_okTrkWYBs-PqMP.inbJShIvMGWcpdqCDq_ZjntVF.ydgRCoYaDVJYEWEIdsjJ_"
|
||||
},
|
||||
"Snowflake": {
|
||||
"Url": "https://stratadev.us-east-1.privatelink.snowflakecomputing.com",
|
||||
"Account": "stratadev"
|
||||
},
|
||||
"presignedUrlExpirationInMinutes": 5,
|
||||
"ServerSideEncryptionS3Key": "arn:aws:kms:us-east-1:809820578507:key/c8f62293-5726-4342-b614-9b23bf2043c9"
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Version": "0.0.0",
|
||||
"configuration": {
|
||||
"basicAuthUsername": "BkQsRDdmdjkFNhKVuTI.TpujQJXuKhGDcV.F_hjADMRTaEUWMJnviaesoOrhhfnz",
|
||||
"basicAuthPassword": "GWPIIWECDqh.hjDNdpxVEfeFe-bYFwPSx-oBGFD_od_SUQVk-QqTIkZY_NXMeqeo"
|
||||
},
|
||||
"StrataConfigServerBaseUrl": "https://configuration.dev.stratanetwork.net",
|
||||
"authorization": {
|
||||
"authorizedDbGuid": "45e3b554-86a5-4b43-875d-3da7eed909d8"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Strata.Hangfire.Configuration;
|
||||
using Strata.SqlTools.Configuration.Postgres;
|
||||
using Strata.Analytics.Biz.DbContexts;
|
||||
using System.Reflection;
|
||||
using Amazon;
|
||||
using Amazon.S3;
|
||||
using Hangfire;
|
||||
using Strata.Analytics.Biz.DataSources.Services;
|
||||
using Strata.Analytics.Biz.Extracts.Export;
|
||||
using Strata.Analytics.Biz.Workbooks.Services;
|
||||
using Strata.Id.Client;
|
||||
using Strata.Analytics.Biz.Extracts.Services;
|
||||
using Strata.Analytics.Biz.DbContexts.Snowflake;
|
||||
using Strata.CoreLib.Claims;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using Strata.SnowflakeLib.Configuration;
|
||||
using Strata.Analytics.Biz.Encounters;
|
||||
using Strata.Analytics.Biz.DataQuery;
|
||||
using Strata.Analytics.Biz.Extracts.Jobs;
|
||||
using Strata.Analytics.Biz.Hangfire;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Strata.Analytics.Biz.SystemSettings;
|
||||
using Strata.SqlTools.Configuration.Common.AsyncFactory;
|
||||
using Strata.Hangfire.TaskManagement.Configuration;
|
||||
using Strata.Analytics.Biz.Extracts;
|
||||
using Strata.Analytics.Biz.ContentMigration.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Strata.Analytics.Biz.DbContexts.Jazz;
|
||||
using Strata.Analytics.Biz.StandardDataSources.Services;
|
||||
using Strata.Replication.Client;
|
||||
using Strata.Analytics.Biz.Database;
|
||||
using Strata.Analytics.Biz.Schema.Jobs;
|
||||
using Strata.Analytics.Biz.Schema;
|
||||
using OfficeOpenXml;
|
||||
using Strata.Analytics.Biz.Export.Services;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public static class AnalyticsServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddAnalyticsServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
// EPPlus license
|
||||
ExcelPackage.LicenseContext = LicenseContext.Commercial;
|
||||
|
||||
services.AddUsersServiceClient();
|
||||
// core services
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddClaimsPrincipalAccessor();
|
||||
|
||||
// external services
|
||||
services.AddIdServiceClient();
|
||||
services.AddSystemSettingsServiceClient();
|
||||
services.AddReplicationServiceClient();
|
||||
|
||||
// postgres
|
||||
services.AddPostgres<AnalyticsDbContext>((options, _) =>
|
||||
{
|
||||
options.ConnectionString = AnalyticsDbContext.GetConnectionString(configuration);
|
||||
});
|
||||
|
||||
//jazz db
|
||||
services.AddAsyncDbContextFactory<JazzDbContext>(options =>
|
||||
{
|
||||
options
|
||||
.WithConnectionString((provider, token) => provider.GetConnectionStringFromSmc(token))
|
||||
.WithDbContextOptions((connectionString, serviceProvider, builder) => builder.UseSqlServer(connectionString));
|
||||
});
|
||||
|
||||
services.AddScoped<JazzDbContext>(provider =>
|
||||
{
|
||||
var factory = provider.GetRequiredService<IAsyncDbContextFactory<JazzDbContext>>();
|
||||
var httpContext = provider.GetRequiredService<IHttpContextAccessor>();
|
||||
var dbContext = factory.CreateDbContextAsync(httpContext?.HttpContext?.RequestAborted ?? default)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
|
||||
return dbContext;
|
||||
});
|
||||
|
||||
services.AddScoped<ISystemSettingService, SystemSettingService>();
|
||||
services.AddIdServiceClient();
|
||||
services.AddFeatureFlagServiceClient();
|
||||
services.AddDataSchemaService();
|
||||
|
||||
services.AddDataQueryService(configuration);
|
||||
|
||||
|
||||
services.AddHangfireJobDependencies();
|
||||
|
||||
services.AddExtractServices(configuration);
|
||||
|
||||
services.AddScoped<IDataSourceService, DataSourceService>();
|
||||
services.AddScoped<IWorkbookService, WorkbookService>();
|
||||
services.AddScoped<IEncounterService, EncounterService>();
|
||||
services.AddScoped<ISavedFilterService, SavedFilterService>();
|
||||
services.AddScoped<IContentMigrationService, ContentMigrationService>();
|
||||
services.AddScoped<IDataMappingService, DataMappingService>();
|
||||
services.AddScoped<IStandardDataSourceService, StandardDataSourceService>();
|
||||
services.AddScoped<IDatabaseService, DatabaseService>();
|
||||
services.AddScoped<IPublishStandardDataSourcesJob, PublishStandardDataSourcesJob>();
|
||||
services.AddScoped<IStandardDataSourcePublishService, StandardDataSourcePublishService>();
|
||||
|
||||
services.AddScoped<IExportService, ExportService>();
|
||||
services.AddScoped<IExcelService, ExcelService>();
|
||||
services.AddScoped<ICsvService, CsvService>();
|
||||
|
||||
services.AddAutoMapper(Assembly.GetExecutingAssembly());
|
||||
|
||||
services.ConfigureHangfireOptionsFromAws(options =>
|
||||
{
|
||||
options.Schema = "analytics";
|
||||
});
|
||||
services.ConfigureTaskManagementOptions(options => options.SourceName = "Analytics");
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Registers dependencies for batch and recurring jobs
|
||||
/// </summary>
|
||||
private static void AddHangfireJobDependencies(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IRecurringJobService, RecurringJobService>();
|
||||
services.TryAddScoped<IBatchJobClient, BatchJobClient>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers instance of <see cref="IDataQueryService"/> and all its necessary dependencies
|
||||
/// </summary>
|
||||
internal static IServiceCollection AddDataQueryService(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
|
||||
// snowflake
|
||||
services.AddSnowflake(configuration);
|
||||
services.AddSnowflakeConfigurationOptions<SnowflakeOptions>(configuration);
|
||||
services.AddSingleton<ISnowflakeDatabaseContextFactory, SnowflakeDatabaseContextFactory>();
|
||||
services.AddScoped<ISnowflakeDatabaseContext, SnowflakeDatabaseContext>(provider =>
|
||||
{
|
||||
var claimsPrincipalAccessor = provider.GetRequiredService<IClaimsPrincipalAccessor>();
|
||||
var snowflakeDatabaseContextFactory = provider.GetRequiredService<ISnowflakeDatabaseContextFactory>();
|
||||
return snowflakeDatabaseContextFactory.Create(claimsPrincipalAccessor.GetCurrentClaimsPrincipal().GetDatabaseGuid().ToString("N"));
|
||||
});
|
||||
|
||||
services.AddScoped<IDataQueryService, DataQueryService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers all dependencies for Extracts services
|
||||
/// </summary>
|
||||
internal static IServiceCollection AddExtractServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
|
||||
services.Configure<ExtractConfiguration>(o =>
|
||||
{
|
||||
o.MaxFileSize = configuration.GetValue<long>("extracts:maxFileSize", 1073741824);
|
||||
o.MaxRowsPerFile = configuration.GetValue<long>("extracts:maxRowsPerFile", 1000000);
|
||||
o.MaxS3Keys = configuration.GetValue<int>("extracts:maxS3Keys", 100);
|
||||
o.SnowflakeMaxFileSize = configuration.GetValue<long>("extracts:snowflakeMaxFileSize", 16777216);
|
||||
o.FileExtension = configuration.GetValue("extracts:fileExtension", "txt");
|
||||
o.OutboundFileExtension = configuration.GetValue("extracts:outboundFileExtension", "csv");
|
||||
}
|
||||
);
|
||||
|
||||
AWSConfigsS3.UseSignatureVersion4 = true;
|
||||
services.AddAWSService<IAmazonS3>();
|
||||
services.AddCachedSmcServiceClient();
|
||||
services.AddScoped<IS3BucketService, S3BucketService>();
|
||||
services.AddScoped<ISftpService, SftpService>();
|
||||
services.AddScoped<IExtractService, ExtractService>();
|
||||
services.AddScoped<IExtractExportHistoryService, ExtractExportHistoryService>();
|
||||
services.AddScoped<IExtractScheduleService, ExtractScheduleService>();
|
||||
services.AddScoped<IExtractScheduleLoader, ExtractScheduleLoader>();
|
||||
services.AddScoped<IExtractExportJob, ExtractExportJob>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using AutoMapper;
|
||||
using Strata.Analytics.Biz.ContentMigration.Models;
|
||||
using Strata.Analytics.Models.Workbooks.WorksheetContent;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration
|
||||
{
|
||||
public class ContentMigrationAutoMapperProfile : Profile
|
||||
{
|
||||
public ContentMigrationAutoMapperProfile()
|
||||
{
|
||||
CreateMap<DataMapping, DataMapping>();
|
||||
CreateMap<Filter, CalculationFilter>().ReverseMap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
using Strata.Analytics.Biz.ContentMigration.Models;
|
||||
using Strata.Analytics.Biz.ContentMigration.Models.Calculations;
|
||||
using Strata.DataSchema.Models.Query;
|
||||
using Strata.DataSchema.Models.Schema;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using WorksheetContent = Strata.Analytics.Models.Workbooks.WorksheetContent;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Helpers
|
||||
{
|
||||
public static class ContentMigrationHelpers
|
||||
{
|
||||
#region Filters
|
||||
public static WorksheetContent.Filter CrosswalkFilter(DataColumn dataColumn, int dataSourceId, string name, IEnumerable<DSCubeSearchCondition> searchConditions, MigrationResult migrationResult, DatePart? datePart)
|
||||
{
|
||||
var filter = new WorksheetContent.Filter(dataColumn, dataSourceId, name, datePart ?? DatePart.FiscalYear);
|
||||
foreach (var condition in searchConditions)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(condition.Value)) continue;
|
||||
|
||||
var members = condition.Value
|
||||
.Split(',')
|
||||
.Select(m => m.Trim())
|
||||
.Where(m => !string.IsNullOrWhiteSpace(m))
|
||||
.Select(v => (object)v);
|
||||
|
||||
if (condition.FilterOp == eFilterOperation.In || condition.FilterOp == eFilterOperation.NotIn)
|
||||
{
|
||||
if (datePart == DatePart.Month) members = TranslateMonths(members);
|
||||
filter.Values = members;
|
||||
filter.ListUseNotIn = condition.FilterOp == eFilterOperation.NotIn;
|
||||
}
|
||||
else
|
||||
{
|
||||
var filterCondition = condition.FilterOp switch
|
||||
{
|
||||
eFilterOperation.Equal => ContentMigrationHelpers.GetFilterCondition(FilterOperator.Equals, LogicalOperator.Or, members),
|
||||
eFilterOperation.NotEqual => ContentMigrationHelpers.GetFilterCondition(FilterOperator.NotEquals, LogicalOperator.Or, members),
|
||||
eFilterOperation.GreaterThan => ContentMigrationHelpers.GetFilterCondition(FilterOperator.GreaterThan, LogicalOperator.Or, members),
|
||||
eFilterOperation.GreaterThanEqualTo => ContentMigrationHelpers.GetFilterCondition(FilterOperator.GreaterThanOrEqualTo, LogicalOperator.Or, members),
|
||||
eFilterOperation.LessThan => ContentMigrationHelpers.GetFilterCondition(FilterOperator.LessThan, LogicalOperator.Or, members),
|
||||
eFilterOperation.LessThanEqualTo => ContentMigrationHelpers.GetFilterCondition(FilterOperator.LessThanOrEqualTo, LogicalOperator.Or, members),
|
||||
_ => null
|
||||
};
|
||||
if (filterCondition == null)
|
||||
{
|
||||
migrationResult.GeneralErrors.Add($"Can not migrate filter condition {condition.FilterOp} for {name}");
|
||||
continue;
|
||||
};
|
||||
filter.FilterType = FilterType.Conditions;
|
||||
filter.Conditions = filter.Conditions.Append(filterCondition);
|
||||
}
|
||||
};
|
||||
return filter;
|
||||
}
|
||||
|
||||
public static WorksheetContent.Filter CrosswalkDateFilter(DateFilterValue parameter, DataColumn dataColumn, int dataSourceId, string name)
|
||||
{
|
||||
double amountBy30 = parameter.RollingAmount / 30;
|
||||
var filter = parameter.DateTypeVal switch
|
||||
{
|
||||
// rolling amount is less than 8 and greater than 0 -> default to previous 7 days
|
||||
var op when op == DateTypeEnum.RollingDays && parameter.RollingAmount < 8 && parameter.RollingAmount > 0 => new WorksheetContent.Filter(dataColumn, dataSourceId, name)
|
||||
{
|
||||
FilterType = FilterType.Timeframe,
|
||||
DatePart = DatePart.Day,
|
||||
DateTimeFrame = WorksheetContent.DateTimeFrame.Previous,
|
||||
DateTimeFrameOffset = -7,
|
||||
DateTimeFrameCount = 7,
|
||||
},
|
||||
// rolling amount is greater than or equal to 8 and less than or equal to 30 -> default to previous 30 days
|
||||
var op when op == DateTypeEnum.RollingDays && parameter.RollingAmount >= 8 && parameter.RollingAmount <= 30 => new WorksheetContent.Filter(dataColumn, dataSourceId, name)
|
||||
{
|
||||
FilterType = FilterType.Timeframe,
|
||||
DatePart = DatePart.Day,
|
||||
DateTimeFrame = WorksheetContent.DateTimeFrame.Previous,
|
||||
DateTimeFrameOffset = -30,
|
||||
DateTimeFrameCount = 30,
|
||||
},
|
||||
// rolling amount is greater than 30 and less than 60 -> default to previous 30 days
|
||||
var op when op == DateTypeEnum.RollingDays && parameter.RollingAmount > 30 && parameter.RollingAmount < 60 => new WorksheetContent.Filter(dataColumn, dataSourceId, name)
|
||||
{
|
||||
FilterType = FilterType.Timeframe,
|
||||
DatePart = DatePart.Day,
|
||||
DateTimeFrame = WorksheetContent.DateTimeFrame.Previous,
|
||||
DateTimeFrameOffset = -30,
|
||||
DateTimeFrameCount = 30,
|
||||
},
|
||||
// rolling amount is greater than 60 -> default to custom months
|
||||
var op when op == DateTypeEnum.RollingDays && parameter.RollingAmount >= 60 => new WorksheetContent.Filter(dataColumn, dataSourceId, name)
|
||||
{
|
||||
FilterType = FilterType.Timeframe,
|
||||
DatePart = DatePart.Month,
|
||||
DateTimeFrame = WorksheetContent.DateTimeFrame.Previous,
|
||||
IsCustom = true,
|
||||
DateTimeFrameOffset = (int)(Math.Round(amountBy30) * -1),
|
||||
DateTimeFrameCount = (int)(Math.Round(amountBy30)),
|
||||
DateTimeFrameCountDisplay = (int)(Math.Round(amountBy30))
|
||||
},
|
||||
// create custom date time month filter for rolling months with x offset
|
||||
var op when op == DateTypeEnum.RollingMonth => new WorksheetContent.Filter(dataColumn, dataSourceId, name)
|
||||
{
|
||||
FilterType = FilterType.Timeframe,
|
||||
DatePart = DatePart.Month,
|
||||
DateTimeFrame = WorksheetContent.DateTimeFrame.Previous,
|
||||
IsCustom = true,
|
||||
DateTimeFrameOffset = parameter.RollingAmount * -1,
|
||||
DateTimeFrameCount = parameter.RollingAmount,
|
||||
DateTimeFrameCountDisplay = parameter.RollingAmount,
|
||||
},
|
||||
// default to current month whenever current month is included
|
||||
var op when op == DateTypeEnum.CurrentMonthandCurrentFiscalYear || op == DateTypeEnum.CurrentMonthandCurrentFiscalYearYTD
|
||||
|| op == DateTypeEnum.CurrentMonthandPriorFiscalYear || op == DateTypeEnum.CurrentMonthandPriorFiscalYearYTD => new WorksheetContent.Filter(dataColumn, dataSourceId, name)
|
||||
{
|
||||
FilterType = FilterType.Timeframe,
|
||||
DatePart = DatePart.Month,
|
||||
DateTimeFrame = WorksheetContent.DateTimeFrame.This,
|
||||
DateTimeFrameOffset = 0,
|
||||
DateTimeFrameCount = 1,
|
||||
},
|
||||
// defualt to previous month whenever previous month is included
|
||||
var op when op == DateTypeEnum.PriorMonthandCurrentFiscalYear || op == DateTypeEnum.PriorMonthandCurrentFiscalYearYTD
|
||||
|| op == DateTypeEnum.PriorMonthandPriorFiscalYear || op == DateTypeEnum.PriorMonthandPriorFiscalYearYTD => new WorksheetContent.Filter(dataColumn, dataSourceId, name)
|
||||
{
|
||||
FilterType = FilterType.Timeframe,
|
||||
DatePart = DatePart.Month,
|
||||
DateTimeFrame = WorksheetContent.DateTimeFrame.Previous,
|
||||
DateTimeFrameOffset = -1,
|
||||
DateTimeFrameCount = 1,
|
||||
},
|
||||
DateTypeEnum.PriorFiscalYear => new WorksheetContent.Filter(dataColumn, dataSourceId, name)
|
||||
{
|
||||
FilterType = FilterType.Timeframe,
|
||||
DatePart = DatePart.FiscalYear,
|
||||
DateTimeFrame = WorksheetContent.DateTimeFrame.Previous,
|
||||
DateTimeFrameOffset = -1,
|
||||
DateTimeFrameCount = 1,
|
||||
},
|
||||
DateTypeEnum.CurrentFiscalYear => new WorksheetContent.Filter(dataColumn, dataSourceId, name)
|
||||
{
|
||||
FilterType = FilterType.Timeframe,
|
||||
DatePart = DatePart.FiscalYear,
|
||||
DateTimeFrame = WorksheetContent.DateTimeFrame.This,
|
||||
DateTimeFrameOffset = 0,
|
||||
DateTimeFrameCount = 1,
|
||||
},
|
||||
_ => null
|
||||
};
|
||||
filter.RelatedDataColumnIds = new List<int> { dataColumn.DataColumnId };
|
||||
return filter;
|
||||
}
|
||||
|
||||
private static FilterCondition GetFilterCondition(FilterOperator filterOp, LogicalOperator logicalOp, IEnumerable<object> values)
|
||||
{
|
||||
return new FilterCondition
|
||||
{
|
||||
Operator = filterOp,
|
||||
LogicalOperator = logicalOp,
|
||||
Values = values
|
||||
};
|
||||
}
|
||||
|
||||
private static IEnumerable<object> TranslateMonths(IEnumerable<object> members)
|
||||
{
|
||||
return members.Select(m =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return DateTime.ParseExact(m.ToString(), "MMMM", System.Globalization.CultureInfo.InvariantCulture).ToString("MM-yyyy");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}).Where(m => m != null);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Calculations
|
||||
public static List<CalculationDetail> GetDependentCalculations(IEnumerable<CalculationDetail> calculationDetails) =>
|
||||
GetDependentCalculationsRecursive(calculationDetails, calculationDetails);
|
||||
|
||||
public static List<CalculationDetail> GetDependentCalculationsRecursive(IEnumerable<CalculationDetail> searchList, IEnumerable<CalculationDetail> refList, int depth = 0)
|
||||
{
|
||||
if (depth++ > 20) throw new Exception("calc circular ref?");
|
||||
var names = refList.Select(c => c.Name);
|
||||
|
||||
var calcs = searchList.ToList();
|
||||
foreach (var calcDetail in calcs)
|
||||
{
|
||||
var dependents = (from calculation in calcDetail.Calculation.DataColumns
|
||||
where names.Contains(calculation.Name)
|
||||
select calculation.Name);
|
||||
|
||||
calcDetail.DependentCalculations = GetDependentCalculationsRecursive(refList.Where(c => dependents.Contains(c.Name)), refList, depth);
|
||||
}
|
||||
|
||||
return calcs;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Strata.Analytics.Models.Workbooks;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models
|
||||
{
|
||||
public class AdhocMigrationResult : MigrationResult
|
||||
{
|
||||
public Worksheet Worksheet { get; set; }
|
||||
|
||||
public AdhocMigrationResult(string name) : base (name)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using Strata.Analytics.Biz.ContentMigration.Models.MigrationPlan;
|
||||
using Strata.Analytics.Biz.ContentMigration.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models.Calculations
|
||||
{
|
||||
[DataContract]
|
||||
[Serializable]
|
||||
public class AggregateCalculation : CalculationBase
|
||||
{
|
||||
[DataMember(Name = "MeasureID")]
|
||||
public string MeasureId;
|
||||
[DataMember]
|
||||
public string MeasureName;
|
||||
[DataMember]
|
||||
public string MeasureType;
|
||||
|
||||
public override string GetJazzFormula() => $"{MeasureType}([{MeasureName}])";
|
||||
|
||||
public override IEnumerable<Mapping> GetMappings(IDataMappingService dataMappingService)
|
||||
{
|
||||
return new List<Mapping>() { dataMappingService.GetMeasureMapping(MeasureName) };
|
||||
}
|
||||
|
||||
public override void SetDataColumnInfo(IDataMappingService dataMappingService)
|
||||
{
|
||||
var mapping = Mapping?.Mappings?.FirstOrDefault(m => m.SourceField == MeasureName);
|
||||
var measure = mapping != null && mapping.IsTargetCalculatedMeasure ? null : dataMappingService.GetDataColumn(mapping?.TargetDataColId);
|
||||
var columns = new List<DataColumnInfo>()
|
||||
{
|
||||
new DataColumnInfo
|
||||
{
|
||||
Name = MeasureName,
|
||||
DataColumn = measure
|
||||
}
|
||||
};
|
||||
DataColumns = columns;
|
||||
}
|
||||
|
||||
public override string GetFormula(IEnumerable<CalculationDetail> dependentCalculations, IDataMappingService dataMappingService)
|
||||
{
|
||||
// Average could be "Average" or "Avg"
|
||||
var measureType = MeasureType == "Average" ? "Avg" : MeasureType;
|
||||
var column = DataColumns.FirstOrDefault(d => d.Name == MeasureName);
|
||||
if (column == null || column.DataColumn == null) return $"{measureType}([{MeasureName}])";
|
||||
var formula = $"{measureType}([{column.DataColumn.DataColumnId}])";
|
||||
return formula;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using Strata.Analytics.Biz.ContentMigration.Models.MigrationPlan;
|
||||
using Strata.Analytics.Biz.ContentMigration.Services;
|
||||
using Strata.Analytics.Biz.Utils;
|
||||
using Strata.Analytics.Models.Workbooks.WorksheetContent;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models.Calculations
|
||||
{
|
||||
[DataContract]
|
||||
[Serializable]
|
||||
public abstract class CalculationBase
|
||||
{
|
||||
public IEnumerable<DataColumnInfo> DataColumns { get; set; }
|
||||
public CalculationMapping Mapping { get; set; }
|
||||
public abstract void SetDataColumnInfo(IDataMappingService dataMappingService);
|
||||
public abstract string GetFormula(IEnumerable<CalculationDetail> dependentCalculations, IDataMappingService dataMappingService = null);
|
||||
public abstract string GetJazzFormula();
|
||||
|
||||
public abstract IEnumerable<Mapping> GetMappings(IDataMappingService dataMappingService);
|
||||
|
||||
public void SetCalculationMapping(CalculationMapping mapping)
|
||||
{
|
||||
Mapping = mapping;
|
||||
}
|
||||
|
||||
public Value GetCalculationValue(CalculationDetail calculation, IEnumerable<CalculationFilter> calculationFilters, IDataMappingService dataMappingService)
|
||||
{
|
||||
var formula = GetCalculationRecursive(calculation, dataMappingService).Formula;
|
||||
|
||||
var allIds = Regex.Matches(formula, @"(?<=\[).*?(?=\])").Select(match => match.Value);
|
||||
if (!allIds.Any()) return null;
|
||||
|
||||
var aliasedIds = allIds.Distinct();
|
||||
var dataColumnIds = allIds.Select(id =>
|
||||
{
|
||||
int.TryParse(id, out var dataColId);
|
||||
return dataColId;
|
||||
});
|
||||
var dupes = dataColumnIds.GroupBy(id => id)
|
||||
.Where(g => g.Count() > 1)
|
||||
.ToDictionary(k => k.Key, v => v.Count());
|
||||
foreach (var id in dataColumnIds.OrderBy(d => d))
|
||||
{
|
||||
if (!dupes.ContainsKey(id)) continue;
|
||||
var aliasedId = $"{id}_{dupes[id] - 1}";
|
||||
aliasedIds = aliasedIds.Append(aliasedId);
|
||||
dupes[id] -= 1;
|
||||
if (dupes[id] == 1) dupes.Remove(id);
|
||||
// only replace that last occurence with this id
|
||||
var regex = new Regex($"\\[{id}\\](?!.*\\[{id}\\])");
|
||||
formula = regex.Replace(formula, $"[{aliasedId}]");
|
||||
}
|
||||
|
||||
// if any parts of the formula did not get replaced with a data column id, we could not migrate the full calculation
|
||||
if (Regex.Matches(formula, @"\[(?!\d+)[^\]]+\]").Any()) return null;
|
||||
return new Value(dataColumnIds, aliasedIds, calculation.Name, formula, calculationFilters);
|
||||
}
|
||||
|
||||
public string ReplaceDependents(string formula, IEnumerable<CalculationDetail> dependentCalculations, IDataMappingService dataMappingService)
|
||||
{
|
||||
var calculatedMeasureMappings = Mapping.Mappings.Where(m => m.IsTargetCalculatedMeasure);
|
||||
foreach (var map in calculatedMeasureMappings)
|
||||
{
|
||||
var measure = dataMappingService.GetCalculatedMeasure(map.TargetDataColId);
|
||||
if (measure == null) continue;
|
||||
var id = DataColumns.FirstOrDefault(d => d.Name == map.SourceField || d.Name == map.SourceField.Split('.')[1].RemoveBrackets())?.Name;
|
||||
formula = formula.Replace($"[{id}]", $"({measure.Calculation})");
|
||||
}
|
||||
var dependents = dependentCalculations.Select(d => d.JazzCalculation.Name);
|
||||
foreach (var col in DataColumns.Where(m => !dependents.Contains(m.Name) && m.DataColumn != null))
|
||||
{
|
||||
formula = formula.Replace($"[{col.Name}]", col.DataColumn?.ColumnType != DataSchema.Models.Schema.ColumnType.Numeric ? $"count(distinct [{col.DataColumn.DataColumnId}])" : $"sum([{col.DataColumn.DataColumnId}])");
|
||||
}
|
||||
return formula;
|
||||
}
|
||||
|
||||
|
||||
private ConvertedCalculation GetCalculationRecursive(CalculationDetail calculation, IDataMappingService dataMappingService, int depth = 0)
|
||||
{
|
||||
if (depth++ > 20) throw new Exception("calc circular ref?");
|
||||
// Replace largest strings first in case there are any substrings present
|
||||
var calcs = calculation.DependentCalculations.Select(p => GetCalculationRecursive(p, dataMappingService, depth))
|
||||
.OrderByDescending(c => c.Name.Length)
|
||||
.ToList();
|
||||
|
||||
var formula = calculation.Calculation.GetFormula(calculation.DependentCalculations, dataMappingService);
|
||||
|
||||
var newFormula = calcs.Aggregate(formula,
|
||||
(current, calc) => current.Replace($"[{calc.Name}]", $"({calc.Formula})"));
|
||||
|
||||
return new ConvertedCalculation
|
||||
{
|
||||
Name = calculation.Name,
|
||||
Formula = newFormula
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
using Strata.Analytics.Biz.ContentMigration.Models.MigrationPlan;
|
||||
using Strata.Analytics.Biz.ContentMigration.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models.Calculations
|
||||
{
|
||||
[DataContract]
|
||||
[Serializable]
|
||||
public class ColumnArithmeticCalculation : CalculationBase
|
||||
{
|
||||
[DataMember]
|
||||
public string Formula;
|
||||
|
||||
[DataMember]
|
||||
public IList<MeasureInfo> Measures { get; set; }
|
||||
|
||||
public override string GetJazzFormula() => Formula;
|
||||
|
||||
public override IEnumerable<Mapping> GetMappings(IDataMappingService dataMappingService)
|
||||
{
|
||||
return Measures.Select(m => dataMappingService.GetMeasureMapping(m.Id));
|
||||
}
|
||||
|
||||
public override void SetDataColumnInfo(IDataMappingService dataMappingService)
|
||||
{
|
||||
var columns = Measures.Select(m =>
|
||||
{
|
||||
var column = dataMappingService.GetDataColumn(Mapping?.Mappings?.FirstOrDefault(map => map.SourceField == m.Id)?.TargetDataColId);
|
||||
return new DataColumnInfo
|
||||
{
|
||||
Name = m.Name,
|
||||
DataColumn = column
|
||||
};
|
||||
});
|
||||
DataColumns = columns;
|
||||
}
|
||||
|
||||
public override string GetFormula(IEnumerable<CalculationDetail> dependentCalculations, IDataMappingService dataMappingService)
|
||||
{
|
||||
return ReplaceDependents(GetJazzFormula(), dependentCalculations, dataMappingService);
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
using Strata.Analytics.Biz.ContentMigration.Models.MigrationPlan;
|
||||
using Strata.Analytics.Biz.ContentMigration.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models.Calculations
|
||||
{
|
||||
[DataContract]
|
||||
[Serializable]
|
||||
public class ColumnDivisionCalculation : CalculationBase
|
||||
{
|
||||
[DataMember]
|
||||
public string DividendMeasure;
|
||||
[DataMember]
|
||||
public string DivisorMeasure;
|
||||
|
||||
public override string GetJazzFormula() => $"[{DividendMeasure}] / [{DivisorMeasure}]";
|
||||
|
||||
public override IEnumerable<Mapping> GetMappings(IDataMappingService dataMappingService)
|
||||
{
|
||||
return new List<Mapping> { dataMappingService.GetMeasureMapping(DividendMeasure), dataMappingService.GetMeasureMapping(DivisorMeasure) };
|
||||
}
|
||||
|
||||
public override void SetDataColumnInfo(IDataMappingService dataMappingService)
|
||||
{
|
||||
var columns = new List<DataColumnInfo>()
|
||||
{
|
||||
new DataColumnInfo
|
||||
{
|
||||
Name = DividendMeasure,
|
||||
DataColumn = dataMappingService.GetDataColumn(Mapping?.Mappings?.FirstOrDefault(m => m.SourceField == DividendMeasure) ?.TargetDataColId)
|
||||
|
||||
},
|
||||
new DataColumnInfo
|
||||
{
|
||||
Name = DivisorMeasure,
|
||||
DataColumn = dataMappingService.GetDataColumn(Mapping?.Mappings?.FirstOrDefault(m => m.SourceField == DivisorMeasure) ?.TargetDataColId)
|
||||
}
|
||||
};
|
||||
|
||||
DataColumns = columns;
|
||||
}
|
||||
|
||||
public override string GetFormula(IEnumerable<CalculationDetail> dependentCalculations, IDataMappingService dataMappingService)
|
||||
{
|
||||
return ReplaceDependents(GetJazzFormula(), dependentCalculations, dataMappingService);
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
using Strata.Analytics.Biz.ContentMigration.Models.MigrationPlan;
|
||||
using Strata.Analytics.Biz.ContentMigration.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models.Calculations
|
||||
{
|
||||
[DataContract]
|
||||
[Serializable]
|
||||
public class ColumnVarianceCalculation : VarianceCalculation
|
||||
{
|
||||
public override string GetJazzFormula() => $"[{MinuendMeasure}] - [{SubtrahendMeasure}]";
|
||||
public override string GetFormula(IEnumerable<CalculationDetail> dependentCalculations, IDataMappingService dataMappingService)
|
||||
{
|
||||
var formula = GetJazzFormula();
|
||||
return ReplaceDependents(formula, dependentCalculations, dataMappingService);
|
||||
}
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
[Serializable]
|
||||
public class VariancePercentCalculation : VarianceCalculation
|
||||
{
|
||||
public override string GetJazzFormula() => $"([{MinuendMeasure}] - [{SubtrahendMeasure}]) / [{SubtrahendMeasure}]";
|
||||
public override string GetFormula(IEnumerable<CalculationDetail> dependentCalculations, IDataMappingService dataMappingService)
|
||||
{
|
||||
var formula = GetJazzFormula();
|
||||
return ReplaceDependents(formula, dependentCalculations, dataMappingService);
|
||||
}
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
[Serializable]
|
||||
public abstract class VarianceCalculation : CalculationBase
|
||||
{
|
||||
[DataMember]
|
||||
public string MinuendMeasure;
|
||||
|
||||
[DataMember]
|
||||
public string SubtrahendMeasure { get; set; }
|
||||
|
||||
public override IEnumerable<Mapping> GetMappings(IDataMappingService dataMappingService)
|
||||
{
|
||||
return new List<Mapping> { dataMappingService.GetMeasureMapping(MinuendMeasure), dataMappingService.GetMeasureMapping(SubtrahendMeasure) };
|
||||
}
|
||||
|
||||
public override void SetDataColumnInfo(IDataMappingService dataMappingService)
|
||||
{
|
||||
var columns = new List<DataColumnInfo>()
|
||||
{
|
||||
new DataColumnInfo
|
||||
{
|
||||
Name = MinuendMeasure,
|
||||
DataColumn = dataMappingService.GetDataColumn(Mapping?.Mappings?.FirstOrDefault(m => m.SourceField == MinuendMeasure)?.TargetDataColId)
|
||||
},
|
||||
new DataColumnInfo
|
||||
{
|
||||
Name = SubtrahendMeasure,
|
||||
DataColumn = dataMappingService.GetDataColumn(Mapping?.Mappings?.FirstOrDefault(m => m.SourceField == SubtrahendMeasure)?.TargetDataColId)
|
||||
}
|
||||
};
|
||||
|
||||
DataColumns = columns;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Strata.DataSchema.Models.Schema;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models.Calculations
|
||||
{
|
||||
public class DataColumnInfo
|
||||
{
|
||||
public string Name;
|
||||
public DataColumn DataColumn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models.Calculations
|
||||
{
|
||||
public class LegacyCalculation
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public Guid CalculationGuid { get; set; }
|
||||
public string AssemblyName { get; set; }
|
||||
public string CalculationXml { get; set; }
|
||||
public string DisplayFormat { get; set; }
|
||||
public string SectionName { get; set; }
|
||||
public Guid SectionGuid { get; set; }
|
||||
public int DisplayOrder { get; set; }
|
||||
}
|
||||
|
||||
public class CalculationDetail
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public CalculationBase Calculation { get; set; }
|
||||
public LegacyCalculation JazzCalculation { get; set; }
|
||||
public IEnumerable<CalculationDetail> DependentCalculations { get; set; }
|
||||
}
|
||||
|
||||
public class ConvertedCalculation
|
||||
{
|
||||
public string Name;
|
||||
public string Formula;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models.Calculations
|
||||
{
|
||||
[Serializable]
|
||||
[DataContract]
|
||||
public class MeasureInfo
|
||||
{
|
||||
[DataMember]
|
||||
public string Id { get; set; }
|
||||
|
||||
[DataMember]
|
||||
public string Name { get; set; }
|
||||
|
||||
public MeasureInfo()
|
||||
{
|
||||
Id = string.Empty;
|
||||
Name = string.Empty;
|
||||
}
|
||||
|
||||
public MeasureInfo(string measureId, string name)
|
||||
{
|
||||
Id = measureId;
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
using Strata.Analytics.Biz.ContentMigration.Models.MigrationPlan;
|
||||
using Strata.Analytics.Biz.ContentMigration.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models.Calculations
|
||||
{
|
||||
[DataContract]
|
||||
[Serializable]
|
||||
public class PercentOfTotalCalculation : CalculationBase
|
||||
{
|
||||
[DataMember]
|
||||
public string ColumnTuple { get; set; }
|
||||
|
||||
public override string GetJazzFormula() => $"percentOfTotal({ColumnTuple})";
|
||||
|
||||
public override IEnumerable<Mapping> GetMappings(IDataMappingService dataMappingService)
|
||||
{
|
||||
var measureName = $"{(ColumnTuple.Contains("Measures") ? "" : "[Measures].")}{ColumnTuple}";
|
||||
return new List<Mapping> { dataMappingService.GetMeasureMapping(measureName) };
|
||||
}
|
||||
|
||||
public override void SetDataColumnInfo(IDataMappingService dataMappingService)
|
||||
{
|
||||
var measureName = $"{(ColumnTuple.Contains("Measures") ? "" : "[Measures].")}{ColumnTuple}";
|
||||
var columns = new List<DataColumnInfo>()
|
||||
{
|
||||
new DataColumnInfo
|
||||
{
|
||||
Name = ColumnTuple,
|
||||
DataColumn = dataMappingService.GetDataColumn(Mapping?.Mappings?.FirstOrDefault(m => m.SourceField == measureName)?.TargetDataColId)
|
||||
}
|
||||
};
|
||||
DataColumns = columns;
|
||||
}
|
||||
|
||||
public override string GetFormula(IEnumerable<CalculationDetail> dependents, IDataMappingService dataMappingService)
|
||||
{
|
||||
var column = DataColumns.FirstOrDefault(d => d.Name == ColumnTuple);
|
||||
if (column == null || column.DataColumn == null) return $"ratioOfTotal({ColumnTuple})";
|
||||
return $"ratioOfTotal([{column.DataColumn.DataColumnId}])"; ;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Strata.SqlTools.Configuration.Common.Multitenant;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models
|
||||
{
|
||||
[NoStrataId]
|
||||
public class DataMapping
|
||||
{
|
||||
public int DataMappingId { get; set; }
|
||||
public string SourceTableName { get; set; }
|
||||
public string SourceColumnName { get; set; }
|
||||
public string DestinationTableName { get; set; }
|
||||
public string DestinationColumnName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models
|
||||
{
|
||||
[DataContract]
|
||||
public enum DateTypeEnum
|
||||
{
|
||||
CurrentMonthandCurrentFiscalYear = 0,
|
||||
CurrentFiscalYear = 1,
|
||||
RollingDays = 2,
|
||||
RollingMonth = 3,
|
||||
CurrentMonthandCurrentFiscalYearYTD = 4,
|
||||
PriorMonthandCurrentFiscalYearYTD = 5,
|
||||
CurrentMonthandPriorFiscalYearYTD = 6,
|
||||
PriorMonthandPriorFiscalYearYTD = 7,
|
||||
CurrentMonthandPriorFiscalYear = 8,
|
||||
PriorMonthandCurrentFiscalYear = 9,
|
||||
PriorMonthandPriorFiscalYear = 10,
|
||||
PriorFiscalYear = 11
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class DateFilterValue
|
||||
{
|
||||
public int DateType { get; set; }
|
||||
public int DateDimension { get; set; }
|
||||
public int RollingAmount { get; set; }
|
||||
public DateTypeEnum DateTypeVal => (DateTypeEnum)DateType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models
|
||||
{
|
||||
[Serializable]
|
||||
[DataContract]
|
||||
public class DSCubeSearchCondition : ISearchCondition
|
||||
{
|
||||
[DataMember(Order = 1)]
|
||||
public eFilterOperation FilterOp { get; set; }
|
||||
|
||||
[DataMember(Order = 2)]
|
||||
public eSearchConditionType SearchType { get; set; }
|
||||
|
||||
[DataMember(Order = 3)]
|
||||
public string Value2 { get; set; } // for between
|
||||
|
||||
[DataMember(Order = 4)]
|
||||
public string Value { get; set; }
|
||||
|
||||
[DataMember(Order = 5)]
|
||||
public IList<string> ValueIDArray { get; set; } // for member selection
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class DSCubeSearchParameterValue
|
||||
{
|
||||
[DataMember(Name = "_SearchConditions")]
|
||||
public List<DSCubeSearchCondition> SearchConditions { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models
|
||||
{
|
||||
public class ExtractMigrationResult : MigrationResult
|
||||
{
|
||||
public int ExtractId { get; set; }
|
||||
|
||||
public ExtractMigrationResult(string name) : base(name)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models
|
||||
{
|
||||
public enum eSearchConditionType
|
||||
{
|
||||
Dimension = 0,
|
||||
Date = 1,
|
||||
Measure = 2
|
||||
}
|
||||
|
||||
public enum eFilterOperation
|
||||
{
|
||||
Equal = 0,
|
||||
NotEqual = 1,
|
||||
GreaterThan = 2,
|
||||
GreaterThanEqualTo = 3,
|
||||
LessThan = 4,
|
||||
LessThanEqualTo = 5,
|
||||
Contains = 6,
|
||||
StartsWith = 7,
|
||||
EndsWith = 8,
|
||||
In = 9,
|
||||
NotIn = 10,
|
||||
NotContains = 11,
|
||||
Between = 12,
|
||||
EqualNumeric = 13,
|
||||
NotEqualNumeric = 14,
|
||||
NotBetween = 15,
|
||||
BetweenStrings = 16,
|
||||
Exclude = 17,
|
||||
BetweenDates = 18,
|
||||
DoesNotStartWith = 19,
|
||||
BetweenDateFilter = 20,
|
||||
}
|
||||
|
||||
public interface ISearchCondition
|
||||
{
|
||||
eFilterOperation FilterOp { get; set; }
|
||||
|
||||
string Value2 { get; set; } // for between
|
||||
|
||||
string Value { get; set; }
|
||||
|
||||
string Description { get; }
|
||||
|
||||
IList<string> ValueIDArray { get; set; } // for member selection
|
||||
|
||||
eSearchConditionType SearchType { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
using Strata.Analytics.Models.ContentMigration;
|
||||
using System;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models
|
||||
{
|
||||
public class LegacyRow
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string SectionName { get; set; }
|
||||
public Guid SectionGuid { get; set; }
|
||||
public string DimName { get; set; }
|
||||
public string TableName { get; set; }
|
||||
public string ColumnName { get; set; }
|
||||
public string AttributeId { get; set; }
|
||||
public string DimensionId { get; set; }
|
||||
public int DisplayOrder { get; set; }
|
||||
public int VisibilityStatusValue { get; set; }
|
||||
public bool ShowGrandTotal { get; set; }
|
||||
public bool ShowSubtotal { get; set; }
|
||||
public bool IsOnColumn { get; set; }
|
||||
public SortOrder SortOrder { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Strata.Analytics.Models.ContentMigration;
|
||||
using System;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models
|
||||
{
|
||||
public class LegacyValue
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string SectionName { get; set; }
|
||||
public Guid SectionGuid { get; set; }
|
||||
public string MeasureName { get; set; }
|
||||
public string MeasureId { get; set; }
|
||||
public int DisplayOrder { get; set; }
|
||||
public AggregateType AggregateType { get; set; }
|
||||
public string DisplayFormat { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models
|
||||
{
|
||||
public class Migration
|
||||
{
|
||||
public int WorkbookId { get; set; }
|
||||
public int DataSourceId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Strata.Analytics.Biz.ContentMigration.Models.Calculations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models.MigrationPlan
|
||||
{
|
||||
public class CalculationMapping
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Calculation { get; set; }
|
||||
public IList<Mapping> Mappings { get; set; }
|
||||
|
||||
public CalculationMapping()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public CalculationMapping(CalculationDetail calculationDetail, IList<Mapping> mappings)
|
||||
{
|
||||
Id = calculationDetail.JazzCalculation.CalculationGuid;
|
||||
Name = calculationDetail.Name;
|
||||
Calculation = calculationDetail.Calculation.GetJazzFormula();
|
||||
Mappings = mappings;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models.MigrationPlan
|
||||
{
|
||||
public class ColumnGroupMapping
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public IList<Mapping> Pivots { get; set; }
|
||||
public IList<Mapping> Values { get; set; }
|
||||
public IList<CalculationMapping> Calculations { get; set; }
|
||||
public IList<Mapping> Filters { get; set; }
|
||||
|
||||
public ColumnGroupMapping(Guid id, string name)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Strata.DataSchema.Models.Schema;
|
||||
using System;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models.MigrationPlan
|
||||
{
|
||||
public class Mapping
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string SourceField { get; set; }
|
||||
public int? TargetDataColId { get; set; }
|
||||
public bool IsTargetCalculatedMeasure { get; set; }
|
||||
public string Status { get; set; }
|
||||
|
||||
public Mapping()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Mapping(Guid? id, string sourceField, DataColumn column)
|
||||
{
|
||||
Id = id ?? Guid.NewGuid();
|
||||
SourceField = sourceField;
|
||||
TargetDataColId = column?.DataColumnId;
|
||||
IsTargetCalculatedMeasure = false;
|
||||
Status = column == null ? "Not Mapped" : "Mapped";
|
||||
}
|
||||
|
||||
public Mapping(Guid? id, string sourceField, CalculatedMeasure measure)
|
||||
{
|
||||
Id = id ?? Guid.NewGuid();
|
||||
SourceField = sourceField;
|
||||
TargetDataColId = measure?.CalculatedMeasureId;
|
||||
IsTargetCalculatedMeasure = true;
|
||||
Status = measure == null ? "Not Mapped" : "Mapped";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models.MigrationPlan
|
||||
{
|
||||
public class Plan
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public IList<Mapping> Filters { get; set; }
|
||||
public IList<Mapping> Rows { get; set; }
|
||||
public IList<ColumnGroupMapping> ColumnGroups { get; set; }
|
||||
|
||||
public Plan(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models
|
||||
{
|
||||
public class MigrationResult
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public IList<string> DataFieldErrors { get; set; }
|
||||
public IList<string> GeneralErrors { get; set; }
|
||||
|
||||
public MigrationResult(string name)
|
||||
{
|
||||
Name = name;
|
||||
DataFieldErrors = new List<string>();
|
||||
GeneralErrors = new List<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models
|
||||
{
|
||||
public class ReDSCubeReportParameter
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid ReportGuid { get; set; }
|
||||
public string SectionName { get; set; }
|
||||
public Guid SectionGuid { get; set; }
|
||||
public string Name { get; set; }
|
||||
public int DisplayOrder { get; set; }
|
||||
public string ParameterXml { get; set; }
|
||||
public string DimensionId { get; set; }
|
||||
public string AttributeId { get; set; }
|
||||
public string MeasureId { get; set; }
|
||||
public string ParameterValueAqn { get; set; }
|
||||
public string TableName { get; set; }
|
||||
public string ColumnName { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models
|
||||
{
|
||||
public class ReReport
|
||||
{
|
||||
public Guid ReportGuid { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string AssemblyQualifiedName { get; set; }
|
||||
public string ReportDetailsXml { get; set; }
|
||||
public DateTime DateAccessed { get; set; }
|
||||
public string AuthorFullName { get; set; }
|
||||
public Guid AuthorUserGuid { get; set; }
|
||||
public string DataSourceAssembly { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Models
|
||||
{
|
||||
public class ReReportDisplay
|
||||
{
|
||||
public Guid ReportGuid { get; set; }
|
||||
public string Name { get; set; }
|
||||
public DateTime DateAccessed { get; set; }
|
||||
public string AuthorFullName { get; set; }
|
||||
public string ReportType { get; set; }
|
||||
public string[] DataSources { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Strata.Analytics.Models.ContentMigration;
|
||||
using Strata.CoreLib.Claims;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using Strata.Analytics.Biz.DbContexts.Jazz;
|
||||
using Strata.Analytics.Models.Workbooks;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Strata.DataSchema.Models.Schema;
|
||||
using Strata.Analytics.Biz.ContentMigration.Helpers;
|
||||
using Strata.Analytics.Biz.ContentMigration.Models.Calculations;
|
||||
using Strata.Analytics.Biz.Extracts.Models;
|
||||
using Strata.Analytics.Biz.DbContexts;
|
||||
using Strata.Analytics.Biz.Extracts.Models.ExtractContent;
|
||||
using AutoMapper;
|
||||
using Strata.Analytics.Biz.Extracts.Services;
|
||||
using Strata.Analytics.Biz.ContentMigration.Models.MigrationPlan;
|
||||
using Strata.DataSchema.Models.Query;
|
||||
using Strata.Analytics.Biz.ContentMigration.Models;
|
||||
using Strata.Analytics.Models.Workbooks.WorksheetContent;
|
||||
using Row = Strata.Analytics.Models.Workbooks.WorksheetContent.Row;
|
||||
using CalculationFilter = Strata.Analytics.Models.Workbooks.WorksheetContent.CalculationFilter;
|
||||
using Filter = Strata.Analytics.Models.Workbooks.WorksheetContent.Filter;
|
||||
using Value = Strata.Analytics.Models.Workbooks.WorksheetContent.Value;
|
||||
using Strata.Analytics.Biz.Utils;
|
||||
using FilterGroup = Strata.Analytics.Models.Workbooks.WorksheetContent.FilterGroup;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Services
|
||||
{
|
||||
public class ContentMigrationService : IContentMigrationService
|
||||
{
|
||||
private readonly JazzDbContext _jazzDbContext;
|
||||
private readonly AnalyticsDbContext _analyticsDbContext;
|
||||
private readonly IDataMappingService _dataMappingService;
|
||||
private readonly ClaimsPrincipal _user;
|
||||
private readonly JsonSerializerOptions _jsonSerializerOptions;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IExtractService _extractService;
|
||||
|
||||
public ContentMigrationService(JazzDbContext jazzDbContext, AnalyticsDbContext analyticsDbContext, IDataMappingService dataMappingService, IClaimsPrincipalAccessor claimsPrincipalAccessor, IMapper mapper, IExtractService extractService)
|
||||
{
|
||||
_jazzDbContext = jazzDbContext;
|
||||
_analyticsDbContext = analyticsDbContext;
|
||||
_dataMappingService = dataMappingService;
|
||||
_user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal();
|
||||
_extractService = extractService;
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
|
||||
_jsonSerializerOptions = options;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReReportDisplay>> GetAdHocReports(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
var userGuid = _user.GetUserGuid();
|
||||
IQueryable<ReReport> query = null;
|
||||
if (_user.IsSdtEmployee())
|
||||
{
|
||||
query = _jazzDbContext.Reports;
|
||||
}
|
||||
else
|
||||
{
|
||||
query = _jazzDbContext.Reports.GetAllReportsByUser(userGuid);
|
||||
}
|
||||
|
||||
var dataSources = _jazzDbContext.GetAllDataSourcesForReports().ToLookup(r => r.reportGuid, d => d.dataSourceName);
|
||||
var reports = await query
|
||||
.Where(r => (r.AssemblyQualifiedName.Contains("DSCubeReport") || r.AssemblyQualifiedName.Contains("ExtractReport") || r.AssemblyQualifiedName.Contains("ClientReport")) && r.DateAccessed > DateTime.Today.AddMonths(-18))
|
||||
.Select(r => new ReReportDisplay
|
||||
{
|
||||
ReportGuid = r.ReportGuid,
|
||||
Name = r.Name,
|
||||
DateAccessed = r.DateAccessed,
|
||||
AuthorFullName = r.AuthorUserGuid == Guid.Parse("10101010-1111-0000-1111-101010101010") ? "Strata User" : r.AuthorFullName,
|
||||
ReportType = r.AssemblyQualifiedName.Contains("Extract") ? "Extract" : "Ad Hoc",
|
||||
DataSources = dataSources[r.ReportGuid].ToArray()
|
||||
}).ToListAsync();
|
||||
|
||||
return reports;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<DataMapping>> GetDataMappings(CancellationToken cancellationToken)
|
||||
{
|
||||
return await _analyticsDbContext.StandardDataMappings.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<DataMapping> GetDataMapping(int id, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _analyticsDbContext.StandardDataMappings.SingleOrDefaultAsync(m => m.DataMappingId == id, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task AddDataMappings(IEnumerable<DataMapping> dataMappings, CancellationToken cancellationToken)
|
||||
{
|
||||
_analyticsDbContext.StandardDataMappings.AddRange(dataMappings);
|
||||
await _analyticsDbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<DataMapping> UpdateDataMapping(DataMapping dataMapping, CancellationToken cancellationToken)
|
||||
{
|
||||
var existingMapping = await _analyticsDbContext.StandardDataMappings.SingleOrDefaultAsync(m => m.DataMappingId == dataMapping.DataMappingId, cancellationToken);
|
||||
if (existingMapping == null && dataMapping.DataMappingId != 0) return null;
|
||||
|
||||
_mapper.Map(dataMapping, existingMapping);
|
||||
_analyticsDbContext.StandardDataMappings.Update(existingMapping);
|
||||
await _analyticsDbContext.SaveChangesAsync(cancellationToken);
|
||||
return existingMapping;
|
||||
}
|
||||
|
||||
public async Task DeleteDataMapping(DataMapping dataMapping, CancellationToken cancellationToken)
|
||||
{
|
||||
_analyticsDbContext.StandardDataMappings.Remove(dataMapping);
|
||||
await _analyticsDbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<Plan> GetMigrationPlan(Guid reportGuid, int dataSourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
var report = await _jazzDbContext.Reports.GetReport(reportGuid);
|
||||
if (report == null) return null;
|
||||
var plan = new Plan(report.Name);
|
||||
await _dataMappingService.InitDataSource(dataSourceId, cancellationToken);
|
||||
|
||||
plan = await GetMappings(reportGuid, report.DataSourceAssembly.Contains("Cube"), !report.AssemblyQualifiedName.Contains("DSCube"), plan);
|
||||
return plan;
|
||||
}
|
||||
|
||||
public async Task<AdhocMigrationResult> MigrateReport(Guid reportGuid, int workbookId, int dataSourceId, Plan plan, CancellationToken cancellationToken)
|
||||
{
|
||||
var report = await _jazzDbContext.Reports.GetReport(reportGuid);
|
||||
if (report == null) return null;
|
||||
var migrationResult = new AdhocMigrationResult(report.Name);
|
||||
await _dataMappingService.InitDataSource(dataSourceId, cancellationToken);
|
||||
var adhoc = await CreateAdhoc(reportGuid, report.DataSourceAssembly.Contains("Cube"), !report.AssemblyQualifiedName.Contains("DSCube"), dataSourceId, plan, migrationResult);
|
||||
var worksheet = new Worksheet
|
||||
{
|
||||
WorkbookId = workbookId,
|
||||
Name = report.Name,
|
||||
WorksheetType = WorksheetType.Adhoc,
|
||||
CreatedByUserGuid = _user.GetUserGuid(),
|
||||
UpdatedByUserGuid = _user.GetUserGuid(),
|
||||
UpdatedDate = DateTime.UtcNow,
|
||||
Content = JsonDocument.Parse(JsonSerializer.Serialize(JsonSerializer.Serialize(adhoc, _jsonSerializerOptions)))
|
||||
};
|
||||
migrationResult.Worksheet = worksheet;
|
||||
migrationResult.DataFieldErrors = _dataMappingService.MissingDataFields.Distinct().ToList();
|
||||
migrationResult.GeneralErrors = migrationResult.GeneralErrors.Distinct().ToList();
|
||||
return migrationResult;
|
||||
}
|
||||
|
||||
public async Task<ExtractMigrationResult> MigrateReportToExtract(Guid reportGuid, int dataSourceId, Plan plan, CancellationToken cancellationToken)
|
||||
{
|
||||
var report = await _jazzDbContext.Reports.GetReport(reportGuid);
|
||||
if (report == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var migrationResult = new ExtractMigrationResult(report.Name);
|
||||
await _dataMappingService.InitDataSource(dataSourceId, cancellationToken);
|
||||
var adhoc = await CreateAdhoc(reportGuid, report.DataSourceAssembly.Contains("Cube"), !report.AssemblyQualifiedName.Contains("DSCube"), dataSourceId, plan, migrationResult);
|
||||
var extractConfig = new ExtractConfig(adhoc);
|
||||
var extractSaveInfo = new ExtractCreateInfo
|
||||
{
|
||||
Content = JsonSerializer.Serialize(extractConfig, _jsonSerializerOptions),
|
||||
Name = report.Name,
|
||||
Description = report.Description,
|
||||
ExtractType = ExtractType.DataSource
|
||||
};
|
||||
var extract = await _extractService.CreateAsync(extractSaveInfo, cancellationToken);
|
||||
|
||||
migrationResult.ExtractId = extract.ExtractId;
|
||||
migrationResult.DataFieldErrors = _dataMappingService.MissingDataFields.Distinct().ToList();
|
||||
migrationResult.GeneralErrors = migrationResult.GeneralErrors.Distinct().ToList();
|
||||
|
||||
return migrationResult;
|
||||
}
|
||||
|
||||
private async Task<Plan> GetMappings(Guid reportGuid, bool isDs, bool isClientReport, Plan migrationPlan)
|
||||
{
|
||||
var adhocRows = await _jazzDbContext.GetReportRows(reportGuid, isClientReport, isDs);
|
||||
// only use the first row section
|
||||
var adhocRowSection = adhocRows.Where(r => !r.IsOnColumn).GroupBy(r => r.SectionName).FirstOrDefault().ToList();
|
||||
var adhocValues = await _jazzDbContext.GetReportValues(reportGuid, isClientReport);
|
||||
var adhocCalculations = await _jazzDbContext.GetReportCalculations(reportGuid, isClientReport);
|
||||
var adhocParameters = await _jazzDbContext.GetReportParameters(reportGuid, isClientReport, isDs);
|
||||
|
||||
// rows
|
||||
var rowMappings = adhocRowSection.Select(row =>
|
||||
{
|
||||
var field = isDs ? row.AttributeId : $"[{row.TableName}].[{row.ColumnName}]";
|
||||
return _dataMappingService.GetDimensionMapping(field, row.Id);
|
||||
}).ToList();
|
||||
|
||||
// column groups
|
||||
var allColumnGroups = adhocValues.Select(v => (v.SectionGuid, v.SectionName)).Concat(adhocCalculations.Select(c => (c.SectionGuid, c.SectionName))).Where(s => !adhocRows.Any(r => r.SectionGuid == s.SectionGuid && !r.IsOnColumn)).Distinct();
|
||||
var adhocValuesBySection = adhocValues.GroupBy(v => v.SectionGuid);
|
||||
var allCalculations = GetCalculationDetails(adhocCalculations);
|
||||
var allCalculationNames = allCalculations.Select(c => c.Name);
|
||||
var columnGroupMappings = allColumnGroups.Select(group =>
|
||||
{
|
||||
var columnGroup = new ColumnGroupMapping(group.SectionGuid, group.SectionName);
|
||||
var sectionFilters = adhocParameters.Where(p => p.SectionGuid == group.SectionGuid);
|
||||
var pivots = adhocRows.Where(r => r.IsOnColumn && r.VisibilityStatusValue == 1 && r.SectionGuid == group.SectionGuid).Select(p =>
|
||||
{
|
||||
var field = isDs ? p.AttributeId : $"[{p.TableName}].[{p.ColumnName}]";
|
||||
return _dataMappingService.GetDimensionMapping(field, p.Id);
|
||||
});
|
||||
columnGroup.Pivots = pivots.ToList();
|
||||
|
||||
var calculationFilters = adhocRows.Where(r => r.IsOnColumn && r.VisibilityStatusValue == 0 && r.SectionGuid == group.SectionGuid).Select(r =>
|
||||
{
|
||||
var field = isDs ? r.AttributeId : $"[{r.TableName}].[{r.ColumnName}]";
|
||||
var matchingFilter = sectionFilters.FirstOrDefault(f => isDs ? f.AttributeId == field : $"[{f.TableName}].[{f.ColumnName}]" == field);
|
||||
if (matchingFilter == null) return null;
|
||||
return _dataMappingService.GetDimensionMapping(field, matchingFilter.Id);
|
||||
}).Where(f => f != null);
|
||||
columnGroup.Filters = calculationFilters.ToList();
|
||||
|
||||
var values = adhocValuesBySection.SingleOrDefault(v => v.Key == group.SectionGuid)?.Select(v => _dataMappingService.GetMeasureMapping(v.MeasureId, v.Id)) ?? new List<Mapping>();
|
||||
columnGroup.Values = values.ToList();
|
||||
|
||||
var calculations = allCalculations.Where(c => c.JazzCalculation.SectionGuid == group.SectionGuid).Select(c =>
|
||||
{
|
||||
var mappings = c.Calculation.GetMappings(_dataMappingService).Where(map => !allCalculationNames.Contains(map.SourceField)).ToList();
|
||||
return new CalculationMapping(c, mappings);
|
||||
});
|
||||
columnGroup.Calculations = calculations.ToList();
|
||||
|
||||
return columnGroup;
|
||||
}).ToList();
|
||||
|
||||
// filters
|
||||
var filterMappings = adhocParameters.Where(p => p.SectionGuid == default || p.SectionGuid == adhocRowSection.First().SectionGuid || !string.IsNullOrWhiteSpace(p.MeasureId))
|
||||
.Select(parameter =>
|
||||
{
|
||||
if (parameter.ParameterValueAqn.Contains("PatientPopulationFilter")) return null;
|
||||
var isDim = string.IsNullOrWhiteSpace(parameter.MeasureId);
|
||||
// if it's a date filter we want it to use Calendar Date Time
|
||||
if (parameter.ParameterValueAqn.Contains("DateFilterValue")) parameter.AttributeId = string.Concat(parameter.AttributeId.AsSpan(0, parameter.AttributeId.IndexOf('.')), ".Date");
|
||||
var field = isDs && isDim ? parameter.AttributeId : $"[{parameter.TableName}].[{parameter.ColumnName}]";
|
||||
if (isDim) return _dataMappingService.GetDimensionMapping(field, parameter.Id);
|
||||
return _dataMappingService.GetMeasureMapping(parameter.MeasureId, parameter.Id);
|
||||
}).Where(f => f != null).ToList();
|
||||
|
||||
migrationPlan.Rows = rowMappings;
|
||||
migrationPlan.ColumnGroups = columnGroupMappings;
|
||||
migrationPlan.Filters = filterMappings;
|
||||
|
||||
return migrationPlan;
|
||||
}
|
||||
|
||||
private async Task<Adhoc> CreateAdhoc(Guid reportGuid, bool isDs, bool isClientReport, int dataSourceId, Plan plan, MigrationResult migrationResult)
|
||||
{
|
||||
var adhocRows = await _jazzDbContext.GetReportRows(reportGuid, isClientReport, isDs);
|
||||
// only use the first row section
|
||||
var adhocRowSection = adhocRows.Where(r => !r.IsOnColumn).GroupBy(r => r.SectionName).FirstOrDefault().ToList();
|
||||
var adhocValues = await _jazzDbContext.GetReportValues(reportGuid, isClientReport);
|
||||
var adhocCalculations = await _jazzDbContext.GetReportCalculations(reportGuid, isClientReport);
|
||||
var adhocParameters = await _jazzDbContext.GetReportParameters(reportGuid, isClientReport, isDs);
|
||||
|
||||
// rows
|
||||
var rows = plan.Rows.Select(rowMap =>
|
||||
{
|
||||
var row = adhocRowSection.FirstOrDefault(r => r.Id == rowMap.Id);
|
||||
var column = _dataMappingService.GetDataColumn(rowMap.TargetDataColId);
|
||||
if (column == null) return null;
|
||||
var datePart = _dataMappingService.GetDatePart(column.ColumnType, rowMap.SourceField);
|
||||
return new Row(column, row.DimName, row.SortOrder, datePart);
|
||||
|
||||
}).Where(r => r != null);
|
||||
var visibleRowIndex = adhocRowSection.FindLastIndex(r => r.VisibilityStatusValue == 1);
|
||||
|
||||
// reconcile all calculations against themseleves to properly handle dependent calculations
|
||||
var allCalculations = GetCalculationDetails(adhocCalculations, plan, migrationResult);
|
||||
// columnGroups
|
||||
var columnGroups = plan.ColumnGroups.Select((group, index) =>
|
||||
{
|
||||
var sectionFilters = adhocParameters.Where(p => p.SectionGuid == group.Id);
|
||||
// pivots
|
||||
var dimensions = group.Pivots.Select(pivotMap =>
|
||||
{
|
||||
var pivot = adhocRows.FirstOrDefault(r => r.Id == pivotMap.Id);
|
||||
var column = _dataMappingService.GetDataColumn(pivotMap.TargetDataColId);
|
||||
if (column == null) return null;
|
||||
var datePart = _dataMappingService.GetDatePart(column.ColumnType, pivotMap.SourceField);
|
||||
|
||||
var parameter = sectionFilters?.FirstOrDefault(f => f.AttributeId == pivotMap.SourceField);
|
||||
if (parameter != null)
|
||||
{
|
||||
var filter = GetFilter(parameter, column, pivotMap.SourceField, dataSourceId, migrationResult);
|
||||
return new Pivot(column, dataSourceId, pivot.DimName, filter, datePart);
|
||||
}
|
||||
return new Pivot(column, dataSourceId, pivot.DimName, null, datePart);
|
||||
}).Where(p => p != null);
|
||||
|
||||
// calculation filters
|
||||
var calculationFilters = group.Filters.Select(filterMap =>
|
||||
{
|
||||
var parameter = sectionFilters?.FirstOrDefault(f => f.Id == filterMap.Id);
|
||||
if (parameter != null)
|
||||
{
|
||||
migrationResult.GeneralErrors.Add($"Attribute {filterMap.SourceField} appears to only be used for section filtering purposes. Related attribute filters will be applied to the measurs in this section.");
|
||||
var dataColumn = _dataMappingService.GetDataColumn(filterMap.TargetDataColId);
|
||||
return _mapper.Map(GetFilter(parameter, dataColumn, filterMap.SourceField, dataSourceId, migrationResult), new CalculationFilter());
|
||||
}
|
||||
return null;
|
||||
}).Where(f => f != null);
|
||||
|
||||
// columns
|
||||
var values = group.Values.Select(valueMap =>
|
||||
{
|
||||
var value = adhocValues.FirstOrDefault(v => v.Id == valueMap.Id);
|
||||
if (valueMap.SourceField == "[Measures].[Cases]") value.AggregateType = AggregateType.CountDistinct;
|
||||
if (valueMap.IsTargetCalculatedMeasure)
|
||||
{
|
||||
var calculatedMeasure = _dataMappingService.GetCalculatedMeasure(valueMap.TargetDataColId);
|
||||
if (calculatedMeasure != null) return (value.DisplayOrder, new Value(calculatedMeasure, value.MeasureName, calculationFilters));
|
||||
}
|
||||
var column = _dataMappingService.GetDataColumn(valueMap.TargetDataColId);
|
||||
if (column == null) return (-1, null);
|
||||
return (value.DisplayOrder, new Value(column, value.MeasureName, value.AggregateType, calculationFilters));
|
||||
});
|
||||
|
||||
var calculations = allCalculations.Where(c => c.JazzCalculation.SectionGuid == group.Id).Select(c =>
|
||||
{
|
||||
var calc = c.Calculation.GetCalculationValue(c, calculationFilters, _dataMappingService);
|
||||
if (calc == null) migrationResult.GeneralErrors.Add($"Unable to migrate calculation {c.Name}");
|
||||
return (c.JazzCalculation.DisplayOrder, calc);
|
||||
});
|
||||
|
||||
var columns = values.Concat(calculations).OrderBy(c => c.DisplayOrder).Select(v => v.Item2).Where(v => v != null);
|
||||
return new ColumnGroup(group.Name, dimensions, columns, index);
|
||||
});
|
||||
|
||||
var filters = plan.Filters.Select(filterMap =>
|
||||
{
|
||||
var parameter = adhocParameters.FirstOrDefault(p => p.Id == filterMap.Id);
|
||||
var dataColumn = _dataMappingService.GetDataColumn(filterMap.TargetDataColId);
|
||||
return GetFilter(parameter, dataColumn, filterMap.SourceField, dataSourceId, migrationResult);
|
||||
}).Where(f => f != null).ToList();
|
||||
var filterGroup = new FilterGroup(filters);
|
||||
|
||||
var adhoc = new Adhoc
|
||||
{
|
||||
Name = "",
|
||||
DataSourceId = dataSourceId,
|
||||
DefaultVisibleRowIndex = visibleRowIndex == -1 ? 0 : visibleRowIndex,
|
||||
Rows = rows,
|
||||
ColumnGroups = columnGroups.Any() ? columnGroups : new List<ColumnGroup> { new ColumnGroup() },
|
||||
FilterGroup = new FilterGroup(filterGroup),
|
||||
WithTotals = true
|
||||
};
|
||||
|
||||
return adhoc;
|
||||
}
|
||||
|
||||
private Filter GetFilter(ReDSCubeReportParameter parameter, DataColumn dataColumn, string id, int dataSourceId, MigrationResult migrationResult)
|
||||
{
|
||||
if (dataColumn == null) return null;
|
||||
|
||||
parameter.ParameterXml = parameter.ParameterXml.Replace("Strata.CS.Jazz.Biz.Reporting.Parameters",
|
||||
"Strata.Analytics.Biz.ContentMigration.Models");
|
||||
|
||||
var filter = parameter switch
|
||||
{
|
||||
var param when param.ParameterValueAqn.Contains("DateFilterValue") => GetDateTimeFrameFilter(param, dataColumn, dataSourceId),
|
||||
var param when param.ParameterValueAqn.Contains("DSCubeSearchParameterValue") && dataColumn.ColumnType != ColumnType.Date => GetListFilter(parameter, dataColumn, dataSourceId, migrationResult),
|
||||
var param when param.ParameterValueAqn.Contains("DSCubeSearchParameterValue") && dataColumn.ColumnType == ColumnType.Date => GetDateListFilter(id, param, dataColumn, dataSourceId, migrationResult),
|
||||
_ => null
|
||||
};
|
||||
if (filter == null)
|
||||
{
|
||||
// different wording for migrating the filter with no values migrated filter as blank?
|
||||
migrationResult.GeneralErrors.Add($"{parameter.Name} is an unsupported filter type and was migrated with no values");
|
||||
return new Filter(dataColumn, dataSourceId, parameter.Name);
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
private Filter GetDateListFilter(string id, ReDSCubeReportParameter parameter, DataColumn dataColumn, int dataSourceId, MigrationResult migrationResult)
|
||||
{
|
||||
var datePart = _dataMappingService.GetDatePart(dataColumn.ColumnType, id) ?? DataSchema.Models.Query.DatePart.FiscalYear;
|
||||
var filter = datePart == DataSchema.Models.Query.DatePart.Day ? new Filter(dataColumn, dataSourceId, parameter.Name, datePart) : GetListFilter(parameter, dataColumn, dataSourceId, migrationResult, datePart);
|
||||
return filter;
|
||||
}
|
||||
|
||||
private static Filter GetListFilter(ReDSCubeReportParameter parameter, DataColumn dataColumn, int dataSourceId, MigrationResult migrationResult, DataSchema.Models.Query.DatePart? datePart = null)
|
||||
{
|
||||
var parameterValue = DataContractUtils.Deserialize<DSCubeSearchParameterValue>(parameter.ParameterXml);
|
||||
var searchCondition = parameterValue.SearchConditions.FirstOrDefault();
|
||||
|
||||
Filter filter;
|
||||
if (searchCondition == null)
|
||||
{
|
||||
// If there is no search condition we want to display all values
|
||||
filter = new Filter(dataColumn, dataSourceId, parameter.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
filter = ContentMigrationHelpers.CrosswalkFilter(dataColumn, dataSourceId, parameter.Name, parameterValue.SearchConditions, migrationResult, datePart);
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
private static Filter GetDateTimeFrameFilter(ReDSCubeReportParameter parameter, DataColumn dataColumn, int dataSourceId)
|
||||
{
|
||||
var parameterValue = DataContractUtils.Deserialize<DateFilterValue>(parameter.ParameterXml);
|
||||
var filter = ContentMigrationHelpers.CrosswalkDateFilter(parameterValue, dataColumn, dataSourceId, parameter.Name);
|
||||
return filter;
|
||||
}
|
||||
|
||||
private IEnumerable<CalculationDetail> GetCalculationDetails(IEnumerable<LegacyCalculation> adhocCalcs, Plan plan = null, MigrationResult migrationResult = null)
|
||||
{
|
||||
var duplicateCalcNames = adhocCalcs.GroupBy(c => c.Name).Where(g => g.Count() > 1).Select(g => g.Key);
|
||||
if (duplicateCalcNames.Any())
|
||||
{
|
||||
migrationResult?.GeneralErrors.Add("Cannot migrate calculations due to duplicate calculation names");
|
||||
return new List<CalculationDetail>();
|
||||
}
|
||||
|
||||
var calculations = GetAllCalculationInfo(adhocCalcs, plan, migrationResult);
|
||||
var calcsWithDependents = ContentMigrationHelpers.GetDependentCalculations(calculations);
|
||||
return calcsWithDependents;
|
||||
}
|
||||
|
||||
private IEnumerable<CalculationDetail> GetAllCalculationInfo(IEnumerable<LegacyCalculation> calculations, Plan plan = null, MigrationResult migrationResult = null)
|
||||
{
|
||||
return calculations.Select(calculation =>
|
||||
{
|
||||
var calculationXml = calculation.CalculationXml.Replace(
|
||||
"Strata.CS.Jazz.Biz.Reporting.Calculations",
|
||||
"Strata.Analytics.Biz.ContentMigration.Models.Calculations");
|
||||
CalculationBase calculationBase = calculation switch
|
||||
{
|
||||
var calc when calc.AssemblyName.Contains("ColumnArithmeticCalculation") =>
|
||||
DataContractUtils.Deserialize<ColumnArithmeticCalculation>(calculationXml),
|
||||
var calc when calc.AssemblyName.Contains("ColumnVarianceCalculation") =>
|
||||
DataContractUtils.Deserialize<ColumnVarianceCalculation>(calculationXml),
|
||||
var calc when calc.AssemblyName.Contains("VariancePercentCalculation") =>
|
||||
DataContractUtils.Deserialize<VariancePercentCalculation>(calculationXml),
|
||||
var calc when calc.AssemblyName.Contains("ColumnDivisionCalculation") =>
|
||||
DataContractUtils.Deserialize<ColumnDivisionCalculation>(calculationXml),
|
||||
var calc when calc.AssemblyName.Contains("PercentOfTotalCalculation") =>
|
||||
DataContractUtils.Deserialize<PercentOfTotalCalculation>(calculationXml),
|
||||
var calc when calc.AssemblyName.Contains("AggregateCalculation") =>
|
||||
DataContractUtils.Deserialize<AggregateCalculation>(calculationXml),
|
||||
_ => null
|
||||
};
|
||||
if (calculationBase == null) migrationResult?.GeneralErrors.Add($"Unsupported calculation type for {calculation.Name} calculation");
|
||||
// set group before data col info
|
||||
calculationBase?.SetCalculationMapping(plan?.ColumnGroups.FirstOrDefault(g => g.Id == calculation.SectionGuid)?.Calculations.FirstOrDefault(c => c.Id == calculation.CalculationGuid));
|
||||
calculationBase?.SetDataColumnInfo(_dataMappingService);
|
||||
return new CalculationDetail
|
||||
{
|
||||
Name = calculation.Name,
|
||||
JazzCalculation = calculation,
|
||||
Calculation = calculationBase
|
||||
};
|
||||
}).Where(c => c.Calculation != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using Strata.DataSchema.Client;
|
||||
using Strata.Analytics.Biz.DataSources.Services;
|
||||
using Strata.DataSchema.Models.Schema;
|
||||
using Strata.Analytics.Models.Utils;
|
||||
using Strata.Analytics.Biz.DbContexts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Strata.Analytics.Models.ContentMigration;
|
||||
using Strata.DataSchema.Models.Query;
|
||||
using Strata.Analytics.Biz.ContentMigration.Models;
|
||||
using Strata.DataSchema.Models.Query;
|
||||
using Strata.Analytics.Biz.ContentMigration.Models.MigrationPlan;
|
||||
using System;
|
||||
using Strata.Analytics.Biz.Utils;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Services
|
||||
{
|
||||
public class DataMappingService : IDataMappingService
|
||||
{
|
||||
private readonly IDataSchemaService _dataSchemaService;
|
||||
private readonly IDataSourceService _dataSourceService;
|
||||
private readonly AnalyticsDbContext _analyticsDbContext;
|
||||
private IEnumerable<DataTable> _factTables;
|
||||
private IEnumerable<DataTable> _dimensionTables;
|
||||
private IEnumerable<CalculatedMeasure> _calculatedMeasures;
|
||||
private IEnumerable<DataMapping> _standardDataMappings;
|
||||
public IList<string> MissingDataFields { get; }
|
||||
|
||||
public DataMappingService(IDataSchemaService dataSchemaService, IDataSourceService dataSourceService, AnalyticsDbContext analyticsDbContext)
|
||||
{
|
||||
_dataSchemaService = dataSchemaService;
|
||||
_dataSourceService = dataSourceService;
|
||||
_analyticsDbContext = analyticsDbContext;
|
||||
MissingDataFields = new List<string>();
|
||||
}
|
||||
|
||||
public async Task InitDataSource(int dataSourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
var dataSource = await _dataSourceService.GetByIdAsync(dataSourceId, cancellationToken);
|
||||
|
||||
var dataTableIds = dataSource.Tables.Select(t => t.DataTableId);
|
||||
var factTables = await _dataSchemaService.GetDataTablesAsync(dataTableIds, true, cancellationToken);
|
||||
|
||||
var dimensionTableIds = factTables.SelectMany(t => t.Columns).Where(c => !c.IsHidden).Select(c => c.LinkTableId).Distinct();
|
||||
var dimensionTables = await _dataSchemaService.GetDataTablesAsync(dimensionTableIds, true, cancellationToken);
|
||||
|
||||
var calculatedMeasures = await _dataSchemaService.GetCalculatedMeasuresAsync(cancellationToken);
|
||||
|
||||
_factTables = factTables;
|
||||
_dimensionTables = dimensionTables;
|
||||
_calculatedMeasures = calculatedMeasures;
|
||||
_standardDataMappings = await _analyticsDbContext.StandardDataMappings.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public Mapping GetDimensionMapping(string field, Guid? id = null)
|
||||
{
|
||||
var info = field.RemoveBrackets().Split('.');
|
||||
var table = info.ElementAtOrDefault(0);
|
||||
var column = info.ElementAtOrDefault(1);
|
||||
|
||||
var resultTable = _dimensionTables.FirstOrDefault(t => t.Name == table);
|
||||
var resultColumn = resultTable?.Columns.FirstOrDefault(t => t.Name == column);
|
||||
|
||||
var standardMapping = _standardDataMappings.FirstOrDefault(m => m.SourceTableName == table && m.SourceColumnName == column);
|
||||
if (standardMapping != null) resultColumn = _dimensionTables.FirstOrDefault(t => t.SqlAlias == standardMapping.DestinationTableName)?.Columns.FirstOrDefault(c => c.SqlObjectName == standardMapping.DestinationColumnName);
|
||||
|
||||
if (resultColumn == null) MissingDataFields.Add($"Could not find attribute: {field}");
|
||||
return new Mapping(id, field, resultColumn);
|
||||
}
|
||||
|
||||
public Mapping GetMeasureMapping(string field, Guid? id = null)
|
||||
{
|
||||
var column = field;
|
||||
if (column.Contains('[')) column = field.RemoveBrackets().Split('.')[1];
|
||||
var resultColumn = _factTables.SelectMany(t => t.Columns).FirstOrDefault(c => c.Name == column && !c.IsHidden && c.LinkColumnId == 0);
|
||||
|
||||
var standardMapping = _standardDataMappings.FirstOrDefault(m => m.SourceColumnName == column);
|
||||
if (standardMapping != null) resultColumn = _factTables.FirstOrDefault(t => t.SqlAlias == standardMapping.DestinationTableName)?.Columns.FirstOrDefault(c => c.SqlObjectName.Equals(standardMapping.DestinationColumnName, System.StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// if we found a column, no need to check calculated measures
|
||||
if (resultColumn != null ) return new Mapping(id, field, resultColumn);
|
||||
|
||||
var resultMeasure = _calculatedMeasures.FirstOrDefault(m => m.Name == column);
|
||||
if (standardMapping != null) resultMeasure = _calculatedMeasures.FirstOrDefault(m => m.Name == standardMapping.DestinationColumnName);
|
||||
|
||||
if (resultMeasure == null) MissingDataFields.Add($"Could not find measure: [{column}]");
|
||||
return new Mapping(id, field, resultMeasure);
|
||||
}
|
||||
|
||||
public DatePart? GetDatePart(ColumnType columnType, string dim)
|
||||
{
|
||||
if (columnType != ColumnType.Date) return null;
|
||||
var column = dim.RemoveBrackets().Split('.')[1];
|
||||
if (column.Contains("Calendar Year")) return DatePart.Year;
|
||||
if (column.Contains("Month")) return DatePart.Month;
|
||||
if (column.Contains("Fiscal Year")) return DatePart.FiscalYear;
|
||||
return DatePart.Day;
|
||||
}
|
||||
|
||||
public DataColumn GetDataColumn(int? id)
|
||||
{
|
||||
var column = _factTables.SelectMany(t => t.Columns).FirstOrDefault(c => c.DataColumnId == id);
|
||||
column ??= _dimensionTables.SelectMany(t => t.Columns).FirstOrDefault(c => c.DataColumnId == id);
|
||||
return column;
|
||||
}
|
||||
|
||||
public CalculatedMeasure GetCalculatedMeasure(int? id)
|
||||
{
|
||||
return _calculatedMeasures.FirstOrDefault(m => m.CalculatedMeasureId == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Strata.Analytics.Biz.ContentMigration.Models;
|
||||
using Strata.Analytics.Biz.ContentMigration.Models.MigrationPlan;
|
||||
using Strata.Analytics.Models.ContentMigration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Services
|
||||
{
|
||||
public interface IContentMigrationService
|
||||
{
|
||||
Task<IEnumerable<ReReportDisplay>> GetAdHocReports(CancellationToken cancellationToken);
|
||||
Task<Plan> GetMigrationPlan(Guid reportGuid, int dataSourceId, CancellationToken cancellationToken);
|
||||
Task<AdhocMigrationResult> MigrateReport(Guid reportGuid, int workbookId, int dataSourceId, Plan plan, CancellationToken cancellationToken);
|
||||
Task<ExtractMigrationResult> MigrateReportToExtract(Guid reportGuid, int datasourceId, Plan plan, CancellationToken cancellationToken);
|
||||
Task<DataMapping> UpdateDataMapping(DataMapping dataMapping, CancellationToken cancellationToken);
|
||||
Task<IEnumerable<DataMapping>> GetDataMappings(CancellationToken cancellationToken);
|
||||
Task AddDataMappings(IEnumerable<DataMapping> dataMappings, CancellationToken cancellationToken);
|
||||
Task<DataMapping> GetDataMapping(int id, CancellationToken cancellationToken);
|
||||
Task DeleteDataMapping(DataMapping dataMapping, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Strata.Analytics.Biz.ContentMigration.Models.MigrationPlan;
|
||||
using Strata.DataSchema.Models.Query;
|
||||
using Strata.DataSchema.Models.Schema;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.Analytics.Biz.ContentMigration.Services
|
||||
{
|
||||
public interface IDataMappingService
|
||||
{
|
||||
Task InitDataSource(int dataSourceId, CancellationToken cancellationToken);
|
||||
Mapping GetDimensionMapping(string field, Guid? id);
|
||||
Mapping GetMeasureMapping(string field, Guid? id = null);
|
||||
DatePart? GetDatePart(ColumnType columnType, string name);
|
||||
DataColumn GetDataColumn(int? id);
|
||||
CalculatedMeasure GetCalculatedMeasure(int? id);
|
||||
IList<string> MissingDataFields { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using Dapper;
|
||||
using Snowflake.Data.Client;
|
||||
using Strata.Analytics.Biz.DbContexts.Snowflake;
|
||||
using Strata.Analytics.Biz.Extracts.Export;
|
||||
using Strata.Analytics.Biz.Extracts.Models;
|
||||
using Strata.ApiCommunication.Http;
|
||||
using Strata.ApiCommunication.Http.Exceptions;
|
||||
using Strata.DataSchema.Client;
|
||||
using Strata.DataSchema.Models.Query;
|
||||
using Strata.SnowflakeLib;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.Analytics.Biz.DataQuery
|
||||
{
|
||||
internal class DataQueryService : IDataQueryService
|
||||
{
|
||||
private readonly ISnowflakeDatabaseContext _snowflakeDatabaseContext;
|
||||
private readonly IDataSchemaService _dataSchemaService;
|
||||
|
||||
public DataQueryService(ISnowflakeDatabaseContext snowflakeDatabaseContext, IDataSchemaService dataSchemaService)
|
||||
{
|
||||
_snowflakeDatabaseContext = snowflakeDatabaseContext;
|
||||
_dataSchemaService = dataSchemaService;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<dynamic>> QueryData(QueryConfig queryConfig, CancellationToken cancellationToken)
|
||||
{
|
||||
var sqlResponse = await BuildSqlQuery(queryConfig, cancellationToken);
|
||||
|
||||
return await _snowflakeDatabaseContext.QueryAsync(sqlResponse.SqlQuery, sqlResponse.Parameters);
|
||||
}
|
||||
|
||||
public async Task<SqlResponse> BuildSqlQuery(QueryConfig queryConfig, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _dataSchemaService.BuildSqlQuery(queryConfig, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<object>> GetColumnValues(ColumnQueryConfig columnQueryConfig, CancellationToken cancellationToken)
|
||||
{
|
||||
var sqlResponse = await _dataSchemaService.BuildColumnQuery(columnQueryConfig, cancellationToken);
|
||||
|
||||
var result = await _snowflakeDatabaseContext.QueryAsync(sqlResponse.SqlQuery, sqlResponse.Parameters);
|
||||
return result.Select(value => ((IDictionary<string, object>)value).First().Value);
|
||||
}
|
||||
|
||||
public async Task<ExternalStageUrlTokens> GetExternalStageUrlTokensAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var queryString = ExtractSqlHelper.DescribeExternalStageCommand();
|
||||
|
||||
var response = await ExecuteSqlQueryAsync(queryString);
|
||||
|
||||
return ExtractSqlHelper.ExtractTokensFromQueryResponse(response);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<dynamic>> ExecuteSqlQueryAsync(string sqlQuery, IEnumerable<KeyValuePair<string, object>> parameters = null)
|
||||
{
|
||||
const string tabularReporterWarehouse = "TABULARREPORTER_WH";
|
||||
|
||||
IEnumerable<dynamic> result;
|
||||
var connectionBuilder = await _snowflakeDatabaseContext.GetSnowflakeConnectionStringBuilderAsync();
|
||||
connectionBuilder.Warehouse = tabularReporterWarehouse;
|
||||
|
||||
await using (SnowflakeDbConnection connection = connectionBuilder.CreateConnection())
|
||||
{
|
||||
result = await connection.QueryAsync(sqlQuery, parameters);
|
||||
}
|
||||
|
||||
return result.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Strata.Analytics.Biz.Extracts.Models;
|
||||
using Strata.DataSchema.Models.Query;
|
||||
|
||||
namespace Strata.Analytics.Biz.DataQuery
|
||||
{
|
||||
public interface IDataQueryService
|
||||
{
|
||||
Task<IEnumerable<dynamic>> QueryData(QueryConfig query, CancellationToken cancellationToken);
|
||||
|
||||
Task<IEnumerable<dynamic>> GetColumnValues(ColumnQueryConfig columnQueryConfig, CancellationToken cancellationToken);
|
||||
|
||||
Task<ExternalStageUrlTokens> GetExternalStageUrlTokensAsync(CancellationToken cancellationToken);
|
||||
|
||||
Task<SqlResponse> BuildSqlQuery(QueryConfig queryConfig, CancellationToken cancellationToken);
|
||||
|
||||
Task<IEnumerable<dynamic>> ExecuteSqlQueryAsync(string sqlQuery, IEnumerable<KeyValuePair<string, object>> parameters = null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using AutoMapper;
|
||||
using Strata.Analytics.Biz.DataSources.Models;
|
||||
|
||||
namespace Strata.Analytics.Biz.DataSources
|
||||
{
|
||||
public class DataSourceAutoMapperProfile: Profile
|
||||
{
|
||||
public DataSourceAutoMapperProfile()
|
||||
{
|
||||
CreateMap<DataSourceSaveInfo, DataSource>();
|
||||
CreateMap<DataSourceRoleAssignmentSaveInfo, DataSourceRoleAssignment>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Strata.Analytics.Biz.DataSources.Models
|
||||
{
|
||||
public class DataSource
|
||||
{
|
||||
public DataSource() { }
|
||||
|
||||
public DataSource SetRole(DataSourceRole role)
|
||||
{
|
||||
CurrentUserRole = role;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int StrataId { get; set; }
|
||||
public int DataSourceId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public int WorkbookUsageCount { get; set; }
|
||||
public int TablesCount { get; set; }
|
||||
public DateTimeOffset CreatedDate { get; set; }
|
||||
public DateTimeOffset UpdatedDate { get; set; }
|
||||
public Guid CreatedByUserGuid { get; set; }
|
||||
public Guid UpdatedByUserGuid { get; set; }
|
||||
[NotMapped]
|
||||
public DataSourceRole CurrentUserRole { get; set; }
|
||||
public List<DataSourceTable> Tables { get; set; }
|
||||
public int? StandardDataSourceId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Strata.Analytics.Biz.DataSources.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Roles for data sources
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Goes from least privilege to most privilege
|
||||
/// </remarks>
|
||||
public enum DataSourceRole
|
||||
{
|
||||
[Description("Permission to use data source in data visualization")]
|
||||
Viewer,
|
||||
|
||||
[Description("Permission to edit / delete data source")]
|
||||
Editor
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Strata.Analytics.Models.Security;
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Strata.Analytics.Biz.DataSources.Models
|
||||
{
|
||||
public class DataSourceRoleAssignment
|
||||
{
|
||||
[JsonIgnore]
|
||||
public int StrataId { get; set; }
|
||||
|
||||
public int DataSourceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// UserGuid or UserGroupGuid
|
||||
/// </summary>
|
||||
public Guid IdentityGuid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User's Name or UserGroupName
|
||||
/// </summary>
|
||||
public string IdentityName { get; set; }
|
||||
|
||||
public IdentityType IdentityType { get; set; }
|
||||
|
||||
public DataSourceRole Role { get; set; }
|
||||
|
||||
public string GlobalId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Strata.Analytics.Models.Security;
|
||||
using System;
|
||||
|
||||
namespace Strata.Analytics.Biz.DataSources.Models
|
||||
{
|
||||
public class DataSourceRoleAssignmentSaveInfo
|
||||
{
|
||||
public string GlobalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// UserGuid or UserGroupGuid
|
||||
/// </summary>
|
||||
public Guid IdentityGuid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// UserName or UserGroupName
|
||||
/// </summary>
|
||||
public string IdentityName { get; set; }
|
||||
|
||||
public IdentityType IdentityType { get; set; }
|
||||
|
||||
public DataSourceRole Role { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Strata.Analytics.Biz.DataSources.Models
|
||||
{
|
||||
public class DataSourceSaveInfo
|
||||
{
|
||||
public int DataSourceId { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public int? StandardDataSourceId { get; set; }
|
||||
|
||||
public List<DataSourceTable> Tables { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Strata.Analytics.Biz.DataSources.Models
|
||||
{
|
||||
public class DataSourceTable
|
||||
{
|
||||
public int StrataId { get; set; }
|
||||
public int DataSourceTableId { get; set; }
|
||||
public int DataSourceId { get; set; }
|
||||
public int DataTableId { get; set; }
|
||||
public int WorkbookUsageCount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Strata.Analytics.Biz.DbContexts;
|
||||
using Strata.Analytics.Biz.DataSources.Models;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using Strata.CoreLib.Claims;
|
||||
using System.Security.Claims;
|
||||
using System;
|
||||
using AutoMapper;
|
||||
using System.Linq;
|
||||
using IdentityModel;
|
||||
using Strata.Analytics.Models.Utils;
|
||||
using Strata.Analytics.Models.Security;
|
||||
|
||||
namespace Strata.Analytics.Biz.DataSources.Services
|
||||
{
|
||||
public class DataSourceService : IDataSourceService
|
||||
{
|
||||
private readonly AnalyticsDbContext _dbContext;
|
||||
private readonly ClaimsPrincipal _user;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public DataSourceService(AnalyticsDbContext dbContext, IClaimsPrincipalAccessor claimsPrincipalAccessor, IMapper mapper)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
_user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal();
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<DataSource>> GetAllAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_user.IsSdtEmployee())
|
||||
{
|
||||
var dataSources = await _dbContext.DataSources.ToListAsync();
|
||||
dataSources.ForEach(dataSource => dataSource.SetRole(DataSourceRole.Editor));
|
||||
return dataSources;
|
||||
}
|
||||
|
||||
var userGroups = _user.Claims
|
||||
.Where(c => c.Type == JwtClaimTypes.Role)
|
||||
.Select(c => c.Value);
|
||||
|
||||
return await _dbContext.DataSources.Join(_dbContext.DataSourceRoleAssignments
|
||||
.Where(p => p.IdentityGuid.Equals(_user.GetUserGuid()) || userGroups.Contains(p.IdentityName))
|
||||
.GroupBy(ra => ra.DataSourceId)
|
||||
.Select(ra => new { DataSourceId = ra.Key, Role = ra.Max(ra => ra.Role) }),
|
||||
dm => dm.DataSourceId,
|
||||
ra => ra.DataSourceId,
|
||||
(dm, ra) => dm.SetRole(ra.Role)).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<DataSource> GetByIdAsync(int id, CancellationToken cancellationToken)
|
||||
{
|
||||
var role = await GetCurrentUserRoleAsync(id, cancellationToken);
|
||||
if (!role.HasValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var dataTable = await _dbContext.DataSources
|
||||
.Include(p => p.Tables)
|
||||
.AsNoTracking()
|
||||
.SingleOrDefaultAsync(p => p.DataSourceId == id, cancellationToken);
|
||||
|
||||
if (dataTable != null)
|
||||
{
|
||||
dataTable.CurrentUserRole = role.Value;
|
||||
}
|
||||
|
||||
return dataTable;
|
||||
}
|
||||
|
||||
public async Task<DataSource> CreateAsync(DataSourceSaveInfo saveInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var dataSource = new DataSource()
|
||||
{
|
||||
CreatedByUserGuid = _user.GetUserGuid(),
|
||||
CurrentUserRole = DataSourceRole.Editor
|
||||
};
|
||||
|
||||
_mapper.Map(saveInfo, dataSource);
|
||||
_dbContext.DataSources.Add(dataSource);
|
||||
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
if (!_user.IsSdtEmployee())
|
||||
{
|
||||
var roleAssignment = new DataSourceRoleAssignment()
|
||||
{
|
||||
DataSourceId = dataSource.DataSourceId,
|
||||
IdentityGuid = _user.GetUserGuid(),
|
||||
IdentityName = $"{_user.GetUserFirstName()} {_user.GetUserLastName()}",
|
||||
IdentityType = IdentityType.User,
|
||||
Role = DataSourceRole.Editor
|
||||
};
|
||||
|
||||
_dbContext.DataSourceRoleAssignments.Add(roleAssignment);
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
public async Task<IList<DataSource>> AddDataSourceBatchAsync(IEnumerable<DataSourceSaveInfo> saveInfos, CancellationToken cancellationToken)
|
||||
{
|
||||
var newDataSources = new List<DataSource>();
|
||||
foreach (var saveInfo in saveInfos)
|
||||
{
|
||||
var dataSource = new DataSource()
|
||||
{
|
||||
CreatedByUserGuid = _user.GetUserGuid(),
|
||||
CurrentUserRole = DataSourceRole.Editor
|
||||
};
|
||||
|
||||
_mapper.Map(saveInfo, dataSource);
|
||||
newDataSources.Add(dataSource);
|
||||
}
|
||||
|
||||
_dbContext.DataSources.AddRange(newDataSources);
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
if (!_user.IsSdtEmployee())
|
||||
{
|
||||
foreach (var dataSource in newDataSources)
|
||||
{
|
||||
var roleAssignment = new DataSourceRoleAssignment()
|
||||
{
|
||||
DataSourceId = dataSource.DataSourceId,
|
||||
IdentityGuid = _user.GetUserGuid(),
|
||||
IdentityName = $"{_user.GetUserFirstName()} {_user.GetUserLastName()}",
|
||||
IdentityType = IdentityType.User,
|
||||
Role = DataSourceRole.Editor
|
||||
};
|
||||
|
||||
_dbContext.DataSourceRoleAssignments.Add(roleAssignment);
|
||||
}
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
return newDataSources;
|
||||
}
|
||||
|
||||
public async Task<DataSource> SaveAsync(DataSourceSaveInfo saveInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var dataSource = await _dbContext.DataSources
|
||||
.Include(table => table.Tables)
|
||||
.SingleOrDefaultAsync(x => x.DataSourceId == saveInfo.DataSourceId, cancellationToken);
|
||||
|
||||
if (dataSource == null) return null;
|
||||
|
||||
var previousLinks = dataSource.Tables;
|
||||
|
||||
_mapper.Map(saveInfo, dataSource);
|
||||
|
||||
if (previousLinks != null)
|
||||
foreach (var previousLink in previousLinks)
|
||||
if (!saveInfo.Tables.Any(tl => tl.DataTableId == previousLink.DataTableId))
|
||||
_dbContext.DataSourceTables.Remove(previousLink);
|
||||
|
||||
// set properties that server side should manage
|
||||
dataSource.UpdatedDate = DateTime.UtcNow;
|
||||
dataSource.UpdatedByUserGuid = _user.GetUserGuid();
|
||||
if (_user.IsSdtEmployee())
|
||||
dataSource.SetRole(DataSourceRole.Editor);
|
||||
|
||||
_dbContext.DataSources.Update(dataSource);
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
public async Task<DataSource> UpdateAsync(int dataSourceId, DataSourceSaveInfo saveInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var dataSource = await _dbContext.DataSources.SingleOrDefaultAsync(x => x.DataSourceId == dataSourceId, cancellationToken);
|
||||
|
||||
_mapper.Map(saveInfo, dataSource);
|
||||
|
||||
// set properties that server side should manage
|
||||
dataSource.UpdatedDate = DateTime.UtcNow;
|
||||
|
||||
_dbContext.DataSources.Update(dataSource);
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a data source exists by id.
|
||||
/// </summary>
|
||||
public async Task<bool> ContainsAsync(int dataSourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _dbContext.DataSources.ContainsAsync(new DataSource() { StrataId = _user.GetStrataId(), DataSourceId = dataSourceId }, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a data source by id.
|
||||
/// </summary>
|
||||
public async Task DeleteAsync(int dataSourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
_dbContext.DataSources.Remove(new DataSource() { StrataId = _user.GetStrataId(), DataSourceId = dataSourceId });
|
||||
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<DataSourceTable> AddDataTableAsync(int dataSourceId, int dataTableId, CancellationToken cancellationToken)
|
||||
{
|
||||
var dataSourceTable = await _dbContext.DataSourceTables.FirstOrDefaultAsync(link => link.DataSourceId == dataSourceId && link.DataTableId == dataTableId, cancellationToken);
|
||||
if (dataSourceTable == null)
|
||||
{
|
||||
dataSourceTable = new DataSourceTable() { DataSourceId = dataSourceId, DataTableId = dataTableId };
|
||||
_dbContext.DataSourceTables.Add(dataSourceTable);
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
return dataSourceTable;
|
||||
}
|
||||
|
||||
public async Task DeleteDataTableAsync(int dataSourceId, int dataTableId, CancellationToken cancellationToken)
|
||||
{
|
||||
var dataSourceTable = await _dbContext.DataSourceTables.FirstOrDefaultAsync(link => link.DataSourceId == dataSourceId && link.DataTableId == dataTableId, cancellationToken);
|
||||
if (dataSourceTable != null)
|
||||
{
|
||||
_dbContext.DataSourceTables.Remove(dataSourceTable);
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SetDataTableLinksAsync(IEnumerable<(int DataSourceId, int DataTableId)> dataTableLinks, CancellationToken cancellationToken)
|
||||
{
|
||||
var existingDataSourceTables = await _dbContext.DataSourceTables.Where(x => dataTableLinks.Select(y => y.DataSourceId).Contains(x.DataSourceId)).ToListAsync();
|
||||
var removals = existingDataSourceTables.Where(x => !dataTableLinks.Any(y => y.DataSourceId == x.DataSourceId && y.DataTableId == x.DataTableId));
|
||||
var adds = dataTableLinks.Where(x => !existingDataSourceTables.Any(y => y.DataSourceId == x.DataSourceId && y.DataTableId == x.DataTableId)).Select(x => new DataSourceTable
|
||||
{
|
||||
DataSourceId = x.DataSourceId,
|
||||
DataTableId = x.DataTableId,
|
||||
});
|
||||
_dbContext.DataSourceTables.RemoveRange(removals);
|
||||
_dbContext.DataSourceTables.AddRange(adds);
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<DataSourceRole?> GetCurrentUserRoleAsync(int dataSourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_user.IsSdtEmployee())
|
||||
{
|
||||
return DataSourceRole.Editor;
|
||||
}
|
||||
|
||||
var userGroups = _user.Claims
|
||||
.Where(c => c.Type == JwtClaimTypes.Role)
|
||||
.Select(c => c.Value);
|
||||
|
||||
return await _dbContext.DataSourceRoleAssignments
|
||||
.Where(p => p.DataSourceId == dataSourceId && (p.IdentityGuid.Equals(_user.GetUserGuid()) || userGroups.Contains(p.IdentityName)))
|
||||
.MaxAsync(p => (DataSourceRole?)p.Role, cancellationToken);
|
||||
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<DataSourceRoleAssignment>> GetRoleAssignmentsAsync(IEnumerable<int> dataSourceIds, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = (IQueryable<DataSourceRoleAssignment>)_dbContext.DataSourceRoleAssignments;
|
||||
if (dataSourceIds.Any())
|
||||
query = query.Where(p => dataSourceIds.Contains(p.DataSourceId));
|
||||
return await query.OrderBy(p => p.IdentityName).AsNoTracking().ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<DataSourceRoleAssignment>> SaveRoleAssignmentsAsync(int dataSourceId, Batch<DataSourceRoleAssignmentSaveInfo, Guid> roleAssignments, CancellationToken cancellationToken)
|
||||
{
|
||||
var existingAssignments = await _dbContext.DataSourceRoleAssignments.Where(p => p.DataSourceId == dataSourceId).ToListAsync(cancellationToken);
|
||||
var changedAssignments = new List<DataSourceRoleAssignment>();
|
||||
|
||||
foreach (var identityGuid in roleAssignments.Deletes)
|
||||
{
|
||||
var existing = existingAssignments.FirstOrDefault(p => p.IdentityGuid.Equals(identityGuid));
|
||||
if (existing != null)
|
||||
{
|
||||
_dbContext.DataSourceRoleAssignments.Remove(existing);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var assignment in roleAssignments.Updates)
|
||||
{
|
||||
var existing = existingAssignments.FirstOrDefault(p => p.IdentityGuid.Equals(assignment.IdentityGuid));
|
||||
if (existing != null)
|
||||
{
|
||||
existing.Role = assignment.Role;
|
||||
_dbContext.DataSourceRoleAssignments.Update(existing);
|
||||
changedAssignments.Add(existing);
|
||||
}
|
||||
else
|
||||
{
|
||||
var newAssignment = new DataSourceRoleAssignment() { DataSourceId = dataSourceId };
|
||||
_mapper.Map(assignment, newAssignment);
|
||||
_dbContext.DataSourceRoleAssignments.Add(newAssignment);
|
||||
changedAssignments.Add(newAssignment);
|
||||
}
|
||||
}
|
||||
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return changedAssignments;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Strata.Analytics.Biz.DataSources.Models;
|
||||
using Strata.Analytics.Biz.Utils;
|
||||
using Strata.Analytics.Models.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.Analytics.Biz.DataSources.Services
|
||||
{
|
||||
public interface IDataSourceService
|
||||
{
|
||||
Task<DataSource> GetByIdAsync(int id, CancellationToken cancellationToken);
|
||||
Task<IEnumerable<DataSource>> GetAllAsync(CancellationToken cancellationToken);
|
||||
Task<DataSource> CreateAsync(DataSourceSaveInfo saveInfo, CancellationToken cancellationToken);
|
||||
Task<IList<DataSource>> AddDataSourceBatchAsync(IEnumerable<DataSourceSaveInfo> saveInfos, CancellationToken cancellationToken);
|
||||
Task<DataSource> SaveAsync(DataSourceSaveInfo saveInfo, CancellationToken cancellationToken);
|
||||
Task<DataSource> UpdateAsync(int dataSourceId, DataSourceSaveInfo saveInfo, CancellationToken cancellationToken);
|
||||
|
||||
Task DeleteAsync(int dataSourceId, CancellationToken cancellationToken);
|
||||
Task<bool> ContainsAsync(int dataSourceId, CancellationToken cancellationToken);
|
||||
|
||||
Task<DataSourceTable> AddDataTableAsync(int dataSourceId, int dataTableId, CancellationToken cancellationToken);
|
||||
|
||||
Task DeleteDataTableAsync(int dataSourceId, int dataTableId, CancellationToken cancellationToken);
|
||||
Task SetDataTableLinksAsync(IEnumerable<(int DataSourceId, int DataTableId)> dataTableLinks, CancellationToken cancellationToken);
|
||||
Task<IEnumerable<DataSourceRoleAssignment>> GetRoleAssignmentsAsync(IEnumerable<int> dataSourceIds, CancellationToken cancellationToken);
|
||||
Task<DataSourceRole?> GetCurrentUserRoleAsync(int dataSourceId, CancellationToken cancellationToken);
|
||||
Task<IEnumerable<DataSourceRoleAssignment>> SaveRoleAssignmentsAsync(int dataSourceId, Batch<DataSourceRoleAssignmentSaveInfo, Guid> roleAssignments, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace Strata.Analytics.Biz.Database
|
||||
{
|
||||
public class Database
|
||||
{
|
||||
public string DatabaseName { get; set; }
|
||||
|
||||
public Guid DatabaseGuid { get; set; }
|
||||
|
||||
public int StrataId { get; set; }
|
||||
|
||||
public string OrgPin { get; set; }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Strata.Id.Client;
|
||||
using Strata.Replication.Client;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.Analytics.Biz.Database
|
||||
{
|
||||
public class DatabaseService : IDatabaseService
|
||||
{
|
||||
private readonly IIdServiceClient _idServiceClient;
|
||||
private readonly IReplicationServiceClient _replicationServiceClient;
|
||||
|
||||
public DatabaseService(IIdServiceClient idServiceClient, IReplicationServiceClient replicationServiceClient)
|
||||
{
|
||||
_idServiceClient = idServiceClient;
|
||||
_replicationServiceClient = replicationServiceClient;
|
||||
}
|
||||
|
||||
public async Task<Database> GetDatabaseByStrataId(int strataId, CancellationToken cancellationToken)
|
||||
{
|
||||
var databases = await GetDatabasesAsync(cancellationToken);
|
||||
return databases.FirstOrDefault(database => database.StrataId == strataId);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Database>> GetDatabasesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var dbInfos = await _replicationServiceClient.OnboardedDatabases(cancellationToken);
|
||||
var orgPins = await _idServiceClient.GetStrataIds(cancellationToken);
|
||||
|
||||
var validDtos = dbInfos.Join(orgPins, d => d.StrataId, o => o.StrataId, (d, o) => new { d.DatabaseGuid, d.DatabaseName, d.StrataId, o.OrgPin })
|
||||
.Select(d => new Database
|
||||
{
|
||||
DatabaseGuid = d.DatabaseGuid,
|
||||
DatabaseName = d.DatabaseName,
|
||||
OrgPin = d.OrgPin,
|
||||
StrataId = d.StrataId
|
||||
})
|
||||
.OrderBy(db => db.OrgPin);
|
||||
|
||||
return validDtos;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Database>> GetDatabasesByStrataIdsAsync(IEnumerable<int> strataIds, CancellationToken cancellationToken)
|
||||
{
|
||||
var databases = await GetDatabasesAsync(cancellationToken);
|
||||
return databases.Where(x => strataIds.Contains(x.StrataId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.Analytics.Biz.Database
|
||||
{
|
||||
public interface IDatabaseService
|
||||
{
|
||||
Task<IEnumerable<Database>> GetDatabasesAsync(CancellationToken cancellationToken);
|
||||
Task<Database> GetDatabaseByStrataId(int strataId, CancellationToken cancellationToken);
|
||||
Task<IEnumerable<Database>> GetDatabasesByStrataIdsAsync(IEnumerable<int> strataIds, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Strata.Analytics.Biz.DataSources.Models;
|
||||
using Strata.CoreLib.Claims;
|
||||
using Strata.SqlTools.Configuration.Common.Multitenant;
|
||||
using Strata.SqlTools.Configuration.Postgres;
|
||||
using Strata.Analytics.Models.Workbooks;
|
||||
using Strata.Analytics.Biz.Extracts.Models;
|
||||
using Strata.Analytics.Biz.StandardDataSources.Models;
|
||||
using Strata.Analytics.Models.Schema;
|
||||
using Strata.Analytics.Biz.ContentMigration.Models;
|
||||
|
||||
namespace Strata.Analytics.Biz.DbContexts
|
||||
{
|
||||
public class AnalyticsDbContext : MultitenantDbContext
|
||||
{
|
||||
public static string MigrationsAssembly = typeof(AnalyticsDbContext).Assembly.GetName().Name;
|
||||
|
||||
public AnalyticsDbContext(DbContextOptions<AnalyticsDbContext> options, IClaimsPrincipalAccessor principalAccessor) : base(options, principalAccessor)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DbSet<DataSource> DataSources { get; set; }
|
||||
public DbSet<DataSourceTable> DataSourceTables { get; set; }
|
||||
public DbSet<DataSourceRoleAssignment> DataSourceRoleAssignments { get; set; }
|
||||
public DbSet<Workbook> Workbooks { get; set; }
|
||||
public DbSet<WorkbookRoleAssignment> WorkbookRoleAssignments { get; set; }
|
||||
public DbSet<Worksheet> Worksheets { get; set; }
|
||||
public DbSet<SavedFilter> SavedFilters { get; set; }
|
||||
public DbSet<Extract> Extracts { get; set; }
|
||||
public DbSet<ExtractRoleAssignment> ExtractRoleAssignments { get; set; }
|
||||
public DbSet<ExtractExportHistory> ExtractExportHistory { get; set; }
|
||||
public DbSet<ExtractSchedule> ExtractSchedule { get; set; }
|
||||
public DbSet<StandardDataSource> StandardDataSources { get; set; }
|
||||
public DbSet<StandardDataSourceTable> StandardDataSourceTables { get; set; }
|
||||
public DbSet<StandardDataSourceRoleAssignment> StandardDataSourceRoleAssignments { get; set; }
|
||||
public DbSet<DataMapping> StandardDataMappings { get; set; }
|
||||
|
||||
public DbSet<StandardDataSourcePublication> StandardDataSourcePublications { get; set; }
|
||||
|
||||
public static string GetConnectionString(IConfiguration configuration)
|
||||
{
|
||||
return ConnectionStringBuilder.Build(configuration, "analytics");
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<DataSource>()
|
||||
.Property(x => x.CreatedDate)
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("timezone('utc', now())");
|
||||
|
||||
modelBuilder.Entity<DataSource>()
|
||||
.Property(x => x.UpdatedDate)
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("timezone('utc', now())");
|
||||
|
||||
modelBuilder.Entity<DataSource>()
|
||||
.HasOne<StandardDataSource>()
|
||||
.WithMany()
|
||||
.HasForeignKey(ds => ds.StandardDataSourceId)
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<Workbook>()
|
||||
.Property(x => x.CreatedDate)
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("timezone('utc', now())");
|
||||
|
||||
modelBuilder.Entity<Workbook>()
|
||||
.Property(x => x.UpdatedDate)
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("timezone('utc', now())");
|
||||
|
||||
modelBuilder.Entity<Worksheet>()
|
||||
.Property(x => x.CreatedDate)
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("timezone('utc', now())");
|
||||
|
||||
modelBuilder.Entity<SavedFilter>()
|
||||
.Property(x => x.CreatedDate)
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("timezone('utc', now())");
|
||||
|
||||
modelBuilder.Entity<DataSourceRoleAssignment>()
|
||||
.HasOne<DataSource>()
|
||||
.WithMany()
|
||||
.HasForeignKey(p => p.DataSourceId);
|
||||
|
||||
modelBuilder.Entity<DataSourceRoleAssignment>()
|
||||
.HasKey(c => new { c.DataSourceId, c.IdentityGuid });
|
||||
|
||||
modelBuilder.Entity<WorkbookRoleAssignment>()
|
||||
.HasOne<Workbook>()
|
||||
.WithMany()
|
||||
.HasForeignKey(p => p.WorkbookId);
|
||||
|
||||
modelBuilder.Entity<WorkbookRoleAssignment>()
|
||||
.HasKey(c => new { c.WorkbookId, c.IdentityGuid });
|
||||
|
||||
modelBuilder.Entity<Extract>()
|
||||
.HasKey(c => new { c.ExtractId });
|
||||
|
||||
modelBuilder.Entity<Extract>()
|
||||
.Property(x => x.CreatedDate)
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("timezone('utc', now())");
|
||||
|
||||
modelBuilder.Entity<Extract>()
|
||||
.Property(x => x.UpdatedDate)
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("timezone('utc', now())");
|
||||
|
||||
modelBuilder.Entity<ExtractExportHistory>()
|
||||
.HasKey(c => new { c.ExtractHistoryId });
|
||||
|
||||
modelBuilder.Entity<ExtractExportHistory>()
|
||||
.Property(x => x.CreatedDate)
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("timezone('utc', now())");
|
||||
|
||||
modelBuilder.Entity<ExtractRoleAssignment>()
|
||||
.HasOne<Extract>()
|
||||
.WithMany()
|
||||
.HasForeignKey(p => p.ExtractId);
|
||||
|
||||
modelBuilder.Entity<ExtractRoleAssignment>()
|
||||
.HasKey(c => new { c.ExtractId, c.IdentityGuid });
|
||||
|
||||
|
||||
modelBuilder.Entity<ExtractSchedule>()
|
||||
.Property(x => x.CreatedDate)
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("timezone('utc', now())");
|
||||
|
||||
modelBuilder.Entity<ExtractSchedule>()
|
||||
.Property(x => x.UpdatedDate)
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("timezone('utc', now())");
|
||||
|
||||
modelBuilder.Entity<ExtractSchedule>()
|
||||
.HasKey(c => new { c.ExtractScheduleId });
|
||||
|
||||
modelBuilder.Entity<StandardDataSourceRoleAssignment>()
|
||||
.HasOne<StandardDataSource>()
|
||||
.WithMany()
|
||||
.HasForeignKey(p => p.StandardDataSourceId);
|
||||
|
||||
modelBuilder.Entity<StandardDataSourceRoleAssignment>()
|
||||
.HasKey(c => new { c.StandardDataSourceId, c.IdentityGuid });
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Strata.Configuration.Client.DependencyInjection.ConsoleApps;
|
||||
using Strata.SqlTools.Configuration.Postgres;
|
||||
|
||||
namespace Strata.Analytics.Biz.DbContexts.Factories
|
||||
{
|
||||
public class AnalyticsDbContextFactory : DesignTimeDbContextFactory<AnalyticsDbContext>
|
||||
{
|
||||
public AnalyticsDbContextFactory() : base(GetConnectionString())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private static string GetConnectionString()
|
||||
{
|
||||
var config = StrataConfigurationFactory.GetConfiguration();
|
||||
return AnalyticsDbContext.GetConnectionString(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Strata.Analytics.Biz.ContentMigration.Models;
|
||||
|
||||
namespace Strata.Analytics.Biz.DbContexts.Jazz
|
||||
{
|
||||
public class JazzDbContext : DbContext
|
||||
{
|
||||
public DbSet<ReReport> Reports { get; set; }
|
||||
public DbSet<ReDSCubeReportParameter> Parameters { get; set; }
|
||||
|
||||
public JazzDbContext(DbContextOptions<JazzDbContext> options) : base(options)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<ReReport>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.ReportGuid);
|
||||
entity.ToTable("REReport", "dbo");
|
||||
});
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
using Dapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Strata.Analytics.Biz.ContentMigration.Models;
|
||||
using Strata.Analytics.Biz.ContentMigration.Models.Calculations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.Analytics.Biz.DbContexts.Jazz
|
||||
{
|
||||
public static class ReportExtensions
|
||||
{
|
||||
|
||||
public static async Task<ReReport> GetReport(this DbSet<ReReport> reports, Guid reportGuid)
|
||||
{
|
||||
var result = reports.FromSqlInterpolated($@"select report.ReportGUID as ReportGuid, report.Name as Name, report.Description as Description, report.AssemblyQualifiedName as AssemblyQualifiedName, report.ReportDetailsXML as ReportDetailsXml, report.DateAccessed as DateAccessed, report.AuthorFullName as AuthorFullName, report.AuthorUserGUID as AuthorUserGuid, ISNULL(dataSourceClientLink.ProviderAssemblyQualifiedName, dataSourceCubeLink.ProviderAssemblyQualifiedName) as DataSourceAssembly
|
||||
from dbo.REReport report
|
||||
left join dbo.REClientReportDataSourceLink dataSourceClientLink on report.ReportGUID = dataSourceClientLink.ReportGUID
|
||||
left join dbo.REDSCubeReportDataSourceLink dataSourceCubeLink on report.ReportGUID = dataSourceCubeLink.ReportGUID
|
||||
where report.ReportGUID = {reportGuid}");
|
||||
return await result.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public static IQueryable<ReReport> GetAllReportsByUser(this DbSet<ReReport> reports, Guid userGuid)
|
||||
{
|
||||
return reports.FromSqlInterpolated($@"SELECT r.*
|
||||
FROM dbo.REReport r
|
||||
INNER JOIN dbo.viewS3ACLSimple acl ON r.ReportGUID = acl.LootID
|
||||
AND acl.UserGUID = {userGuid}
|
||||
AND acl.IsWritable = 1");
|
||||
}
|
||||
|
||||
public static IEnumerable<(Guid reportGuid, string dataSourceName)> GetAllDataSourcesForReports(this JazzDbContext db)
|
||||
{
|
||||
var query = @"select datasource.ReportGuid, datasource.Name
|
||||
from (
|
||||
select ReportGuid, dv.Name from REClientReportDataSourceLink ds inner join DataView dv on ds.DataSourceGUID = dv.DataViewGUID
|
||||
union all
|
||||
select ReportGuid, c.Name from REClientReportDataSourceLink ds inner join EMFCubeStub c on ds.DataSourceGUID = c.CubeGUID
|
||||
union all
|
||||
select ReportGuid, c.Name from REDSCubeReportDataSourceLink ds inner join EMFCubeStub c on ds.DataSourceGUID = c.CubeGUID
|
||||
)datasource
|
||||
inner join [dbo].[REReport] r on datasource.ReportGuid = r.ReportGUID
|
||||
where r.DateAccessed >= DATEADD(MONTH, -18, CAST(GETDATE() AS DATE))";
|
||||
var result = db.Database.GetDbConnection().Query<(Guid, string)>(query);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async Task<IEnumerable<ReDSCubeReportParameter>> GetReportParameters(this JazzDbContext db, Guid reportGuid, bool clientReport = false, bool isDs = true)
|
||||
{
|
||||
var query = @"select section.Name as SectionName, section.DSCubeReportSectionGUID as SectionGuid, parameter.DSCubeReportParameterGUID as Id, parameter.Name as Name, parameter.ParameterXML as ParameterXml, parameter.DisplayOrder as DisplayOrder, parameter.DimensionID as DimensionId, parameter.AttributeID as AttributeId, parameter.MeasureID as MeasureId, parameter.ParameterValueAQN as ParameterValueAqn
|
||||
from REDSCubeReportParameter parameter
|
||||
left join REDSCubeReportSectionMeasure measure on parameter.DSCubeReportSectionItemGUID = measure.DSCubeReportSectionMeasureGUID
|
||||
left join REDSCubeReportSectionAttribute att on parameter.DSCubeReportSectionItemGUID = att.DSCubeReportSectionAttributeGUID
|
||||
left join REDSCubeReportSection section on section.DSCubeReportSectionGUID = att.DSCubeReportSectionGUID or section.DSCubeReportSectionGUID = measure.DSCubeReportSectionGUID
|
||||
where parameter.ReportGUID=@reportGuid
|
||||
order by parameter.DisplayOrder";
|
||||
if (clientReport) query = @"select section.Name as SectionName, section.ClientReportSectionGUID as SectionGuid, parameter.ClientReportParameterGUID as Id, parameter.Name as Name, parameter.ParameterXML as ParameterXml, parameter.DisplayOrder as DisplayOrder, parameter.DimensionID as DimensionId, parameter.AttributeID as AttributeId, parameter.MeasureID as MeasureId, parameter.ParameterValueAQN as ParameterValueAqn
|
||||
from REClientReportParameter parameter
|
||||
left join REClientReportSectionMeasure measure on parameter.ClientReportSectionItemGUID = measure.ClientReportSectionMeasureGUID
|
||||
left join REClientReportSectionAttribute att on parameter.ClientReportSectionItemGUID = att.ClientReportSectionAttributeGUID
|
||||
left join REClientReportSection section on section.ClientReportSectionGUID = att.ClientReportSectionGUID or section.ClientReportSectionGUID = measure.ClientReportSectionGUID
|
||||
where parameter.ReportGUID=@reportGuid
|
||||
order by parameter.DisplayOrder";
|
||||
if (clientReport && !isDs) query = @"select section.Name as SectionName, section.ClientReportSectionGUID as SectionGuid, parameter.ClientReportParameterGUID as Id, parameter.Name as Name, parameter.ParameterXML as ParameterXml, parameter.DisplayOrder as DisplayOrder, parameter.DimensionID as DimensionId, parameter.AttributeID as AttributeId, parameter.MeasureID as MeasureId, parameter.ParameterValueAQN as ParameterValueAqn, scoreDim.FriendlyName as TableName, parameter.ColumnName as ColumnName
|
||||
from REClientReportParameter parameter
|
||||
left join REClientReportSectionMeasure measure on parameter.ClientReportParameterGUID = measure.ClientReportSectionMeasureGUID
|
||||
left join REClientReportSectionAttribute att on parameter.ClientReportParameterGUID = att.ClientReportSectionAttributeGUID
|
||||
left join REClientReportSection section on section.ClientReportSectionGUID = att.ClientReportSectionGUID or section.ClientReportSectionGUID = measure.ClientReportSectionGUID
|
||||
left join ScoreDimension scoreDim on parameter.DimensionID = scoreDim.DimensionGuid
|
||||
where parameter.ReportGUID=@reportGuid
|
||||
order by parameter.DisplayOrder";
|
||||
var result = await db.Database.GetDbConnection().QueryAsync<ReDSCubeReportParameter>(query, new { reportGuid });
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async Task<IEnumerable<LegacyRow>> GetReportRows(this JazzDbContext db, Guid reportGuid, bool clientReport = false, bool isDs = true)
|
||||
{
|
||||
var query = @"select section.Name as SectionName, section.DSCubeReportSectionGUID as SectionGuid, dim.DSCubeReportSectionAttributeGUID as Id, dim.Name as DimName, dim.ColumnName as ColumnName, dim.AttributeID as AttributeId, dim.DimensionID as DimensionId, dim.DisplayOrder as DisplayOrder, dim.VisibilityStatusValue as VisibilityStatusValue, section.ShowGrandTotal as ShowGrandTotal, link.IsOnColumn as IsOnColumn, dim.SortOrderValue as SortOrder
|
||||
from REDSCubeReportSectionAttribute dim
|
||||
join REDSCubeReportSectionLink link on dim.DSCubeReportSectionGUID = link.DSCubeReportSectionGUID
|
||||
join REDSCubeReportSection section on link.DSCubeReportSectionGUID = section.DSCubeReportSectionGUID
|
||||
where link.ReportGUID=@reportGuid and section.IsHidden = 'false'
|
||||
order by link.DisplayOrder, dim.DisplayOrder";
|
||||
if (clientReport && isDs) query = @"select section.Name as SectionName, section.ClientReportSectionGUID as SectionGuid, dim.ClientReportSectionAttributeGUID as Id, dim.Name as DimName, dim.ColumnName as ColumnName, dim.AttributeID as AttributeId, dim.DimensionID as DimensionId, dim.DisplayOrder as DisplayOrder, dim.VisibilityStatusValue as VisibilityStatusValue, section.ShowGrandTotal as ShowGrandTotal, link.IsOnColumn as IsOnColumn, dim.SortOrderValue as SortOrder
|
||||
from REClientReportSectionAttribute dim
|
||||
join REClientReportSectionLink link on dim.ClientReportSectionGUID = link.ClientReportSectionGUID
|
||||
join REClientReportSection section on link.ClientReportSectionGUID = section.ClientReportSectionGUID
|
||||
where link.ReportGUID=@reportGuid and section.IsHidden = 'false'
|
||||
order by link.DisplayOrder, dim.DisplayOrder";
|
||||
if (clientReport && !isDs) query = @"select section.Name as SectionName, section.ClientReportSectionGUID as SectionGuid, dim.ClientReportSectionAttributeGUID as Id, dim.Name as DimName, dim.ColumnName as ColumnName, dim.AttributeID as AttributeId, dim.DimensionID as DimensionId, dim.DisplayOrder as DisplayOrder, dim.VisibilityStatusValue as VisibilityStatusValue, section.ShowGrandTotal as ShowGrandTotal, link.IsOnColumn as IsOnColumn, dim.SortOrderValue as SortOrder, scoreDim.FriendlyName as TableName
|
||||
from REClientReportSectionAttribute dim
|
||||
join REClientReportSectionLink link on dim.ClientReportSectionGUID = link.ClientReportSectionGUID
|
||||
join REClientReportSection section on link.ClientReportSectionGUID = section.ClientReportSectionGUID
|
||||
join ScoreDimension scoreDim on dim.DimensionID = scoreDim.DimensionGUID
|
||||
where link.ReportGUID=@reportGuid and section.IsHidden = 'false'
|
||||
order by link.DisplayOrder, dim.DisplayOrder";
|
||||
var result = await db.Database.GetDbConnection().QueryAsync<LegacyRow>(query, new { reportGuid });
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async Task<IEnumerable<LegacyValue>> GetReportValues(this JazzDbContext db, Guid reportGuid, bool clientReport = false)
|
||||
{
|
||||
var query = @"select section.Name as SectionName, section.DSCubeReportSectionGUID as SectionGuid, measure.DSCubeReportSectionMeasureGUID as Id, measure.Name as MeasureName, measure.MeasureID as MeasureId, measure.DisplayOrder as DisplayOrder, measure.AggregationType as AggregationType
|
||||
from REDSCubeReportSectionMeasure measure
|
||||
join REDSCubeReportSectionLink link on measure.DSCubeReportSectionGUID = link.DSCubeReportSectionGUID
|
||||
join REDSCubeReportSection section on link.DSCubeReportSectionGUID = section.DSCubeReportSectionGUID
|
||||
where link.ReportGUID=@reportGuid and section.IsHidden = 'false'
|
||||
order by link.DisplayOrder, measure.DisplayOrder";
|
||||
if (clientReport) query = @"select section.Name as SectionName, section.ClientReportSectionGUID as SectionGuid, measure.ClientReportSectionMeasureGUID as Id, measure.Name as MeasureName, measure.MeasureID as MeasureId, measure.DisplayOrder as DisplayOrder, measure.AggregationType as AggregationType
|
||||
from REClientReportSectionMeasure measure
|
||||
join REClientReportSectionLink link on measure.ClientReportSectionGUID = link.ClientReportSectionGUID
|
||||
join REClientReportSection section on link.ClientReportSectionGUID = section.ClientReportSectionGUID
|
||||
where link.ReportGUID=@reportGuid and section.IsHidden = 'false'
|
||||
order by link.DisplayOrder, measure.DisplayOrder";
|
||||
var result = await db.Database.GetDbConnection().QueryAsync<LegacyValue>(query, new { reportGuid });
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async Task<IEnumerable<LegacyCalculation>> GetReportCalculations(this JazzDbContext db, Guid reportGuid, bool clientReport = false)
|
||||
{
|
||||
var query = @"select calc.Name, calc.DSCubeReportCalculationGUID as CalculationGuid, calc.CalculationAssemblyQualifiedName as AssemblyName, calc.CalculationXML as CalculationXml, calc.DisplayFormat, section.Name as SectionName, section.DSCubeReportSectionGUID as SectionGuid, calc.DisplayOrder
|
||||
from REDSCubeReportCalculation calc
|
||||
join REDSCubeReportSectionLink link on calc.DSCubeReportSectionGUID = link.DSCubeReportSectionGUID
|
||||
join REDSCubeReportSection section on link.DSCubeReportSectionGUID = section.DSCubeReportSectionGUID
|
||||
where link.ReportGUID=@reportGuid and section.IsHidden = 'false'
|
||||
order by link.DisplayOrder, calc.DisplayOrder";
|
||||
if (clientReport) query = @"select calc.Name, calc.ClientReportCalculationGUID as CalculationGuid, calc.CalculationAssemblyQualifiedName as AssemblyName, calc.CalculationXML as CalculationXml, calc.DisplayFormat, section.Name as SectionName, section.ClientReportSectionGUID as SectionGuid, calc.DisplayOrder
|
||||
from REClientReportCalculation calc
|
||||
join REClientReportSectionLink link on calc.ClientReportSectionGUID = link.ClientReportSectionGUID
|
||||
join REClientReportSection section on link.ClientReportSectionGUID = section.ClientReportSectionGUID
|
||||
where link.ReportGUID=@reportGuid and section.IsHidden = 'false'
|
||||
order by link.DisplayOrder, calc.DisplayOrder";
|
||||
var result = await db.Database.GetDbConnection().QueryAsync<LegacyCalculation>(query, new { reportGuid });
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Strata.SnowflakeLib;
|
||||
|
||||
namespace Strata.Analytics.Biz.DbContexts.Snowflake
|
||||
{
|
||||
public interface ISnowflakeDatabaseContext
|
||||
{
|
||||
Task<SnowflakeConnectionStringBuilder> GetSnowflakeConnectionStringBuilderAsync();
|
||||
Task<IEnumerable<dynamic>> QueryAsync(string queryString, IEnumerable<KeyValuePair<string, object>> parameters = null);
|
||||
Task<T> QueryFirstOrDefaultAsync<T>(string queryString, IEnumerable<KeyValuePair<string, object>> parameters = null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Strata.Analytics.Biz.DbContexts.Snowflake
|
||||
{
|
||||
public interface ISnowflakeDatabaseContextFactory
|
||||
{
|
||||
public SnowflakeDatabaseContext Create(string clientId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.Analytics.Biz.DbContexts.Snowflake
|
||||
{
|
||||
public interface ISnowflakeQueryService
|
||||
{
|
||||
Task<IEnumerable<T>> ExecuteQueryAsync<T>(string sql, object parameters = null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Dapper;
|
||||
using Snowflake.Data.Client;
|
||||
using Strata.SnowflakeLib;
|
||||
|
||||
namespace Strata.Analytics.Biz.DbContexts.Snowflake
|
||||
{
|
||||
public class SnowflakeDatabaseContext : ISnowflakeDatabaseContext
|
||||
{
|
||||
private string _clientId { get; }
|
||||
|
||||
private readonly SnowflakeClientDbConnectionStringBuilderFactory _snowflakeClientDbConnectionStringBuilderFactory;
|
||||
|
||||
public SnowflakeDatabaseContext(string clientId, SnowflakeClientDbConnectionStringBuilderFactory snowflakeClientDbConnectionStringBuilderFactory)
|
||||
{
|
||||
_clientId = clientId;
|
||||
_snowflakeClientDbConnectionStringBuilderFactory = snowflakeClientDbConnectionStringBuilderFactory;
|
||||
}
|
||||
|
||||
public async Task<SnowflakeConnectionStringBuilder> GetSnowflakeConnectionStringBuilderAsync()
|
||||
{
|
||||
return await _snowflakeClientDbConnectionStringBuilderFactory.CreateAsync(_clientId);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<dynamic>> QueryAsync(string queryString, IEnumerable<KeyValuePair<string, object>> parameters = null)
|
||||
{
|
||||
const string tabularReporterWarehouse = "TABULARREPORTER_WH";
|
||||
|
||||
IEnumerable<dynamic> result;
|
||||
var connectionBuilder = await GetSnowflakeConnectionStringBuilderAsync();
|
||||
connectionBuilder.Warehouse = tabularReporterWarehouse;
|
||||
|
||||
await using (SnowflakeDbConnection connection = connectionBuilder.CreateConnection())
|
||||
{
|
||||
result = await connection.QueryAsync(queryString, parameters);
|
||||
}
|
||||
|
||||
return result.ToList();
|
||||
}
|
||||
|
||||
public async Task<T> QueryFirstOrDefaultAsync<T>(string queryString, IEnumerable<KeyValuePair<string, object>> parameters = null)
|
||||
{
|
||||
const string tabularReporterWarehouse = "TABULARREPORTER_WH";
|
||||
|
||||
dynamic result;
|
||||
var connectionBuilder = await GetSnowflakeConnectionStringBuilderAsync();
|
||||
connectionBuilder.Warehouse = tabularReporterWarehouse;
|
||||
|
||||
await using (SnowflakeDbConnection connection = connectionBuilder.CreateConnection())
|
||||
{
|
||||
result = await connection.QueryFirstOrDefaultAsync<T>(queryString, parameters);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Strata.SnowflakeLib;
|
||||
|
||||
namespace Strata.Analytics.Biz.DbContexts.Snowflake
|
||||
{
|
||||
public class SnowflakeDatabaseContextFactory : ISnowflakeDatabaseContextFactory
|
||||
{
|
||||
private readonly SnowflakeClientDbConnectionStringBuilderFactory _snowflakeClientDbConnectionStringBuilderFactory;
|
||||
public SnowflakeDatabaseContextFactory(SnowflakeClientDbConnectionStringBuilderFactory snowflakeClientDbConnectionStringBuilderFactory)
|
||||
{
|
||||
_snowflakeClientDbConnectionStringBuilderFactory = snowflakeClientDbConnectionStringBuilderFactory;
|
||||
}
|
||||
|
||||
public SnowflakeDatabaseContext Create(string clientId)
|
||||
{
|
||||
return new SnowflakeDatabaseContext(clientId, _snowflakeClientDbConnectionStringBuilderFactory);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Dapper;
|
||||
using Snowflake.Data.Client;
|
||||
using Strata.SnowflakeLib;
|
||||
|
||||
namespace Strata.Analytics.Biz.DbContexts.Snowflake
|
||||
{
|
||||
public class SnowflakeQueryService : ISnowflakeQueryService
|
||||
{
|
||||
private readonly ISnowflakeDatabaseContext _snowflakeDatabaseContext;
|
||||
|
||||
public SnowflakeQueryService(ISnowflakeDatabaseContext snowflakeDatabaseContext)
|
||||
{
|
||||
_snowflakeDatabaseContext = snowflakeDatabaseContext;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<T>> ExecuteQueryAsync<T>(string sql, object parameters = null)
|
||||
{
|
||||
const string tabularReporterWarehouse = "TABULARREPORTER_WH";
|
||||
|
||||
IEnumerable<T> result;
|
||||
SnowflakeConnectionStringBuilder connectionBuilder = await _snowflakeDatabaseContext.GetSnowflakeConnectionStringBuilderAsync();
|
||||
connectionBuilder.Warehouse = tabularReporterWarehouse;
|
||||
|
||||
await using (SnowflakeDbConnection connection = connectionBuilder.CreateConnection())
|
||||
{
|
||||
result = await connection.QueryAsync<T>(sql, parameters);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Strata.Analytics.Biz.DbContexts.Snowflake;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.Analytics.Biz.Encounters
|
||||
{
|
||||
public class EncounterService : IEncounterService
|
||||
{
|
||||
private readonly ISnowflakeDatabaseContext _snowflakeDatabaseContext;
|
||||
|
||||
public EncounterService(ISnowflakeDatabaseContext snowflakeDatabaseContext)
|
||||
{
|
||||
_snowflakeDatabaseContext = snowflakeDatabaseContext;
|
||||
}
|
||||
|
||||
public async Task<int> GetEncounterIdAsync(string encounterRecordNumber, CancellationToken cancellationToken)
|
||||
{
|
||||
// ok to hardcode column and table name since DimPatientEncounter is a GM table
|
||||
var sql = "select top 1 EncounterId from dss.DimPatientEncounter where EncounterRecordNumber = :ERN";
|
||||
|
||||
return await _snowflakeDatabaseContext.QueryFirstOrDefaultAsync<int>(sql, new Dictionary<string, object>() { { ":ERN", encounterRecordNumber } });
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.Analytics.Biz.Encounters
|
||||
{
|
||||
public interface IEncounterService
|
||||
{
|
||||
Task<int> GetEncounterIdAsync(string encounterRecordNumber, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using Strata.Analytics.Biz.Export.Models;
|
||||
using System;
|
||||
|
||||
namespace Strata.Analytics.Biz.Export.Models
|
||||
{
|
||||
public enum ExportColumnFormat
|
||||
{
|
||||
Default = 0,
|
||||
|
||||
// dates, compatible with DatePart values
|
||||
FiscalYear,
|
||||
FiscalQuarter,
|
||||
Year,
|
||||
Quarter,
|
||||
Month,
|
||||
Week,
|
||||
Day,
|
||||
|
||||
// numbers, compatible with NumberFormat values
|
||||
Currency,
|
||||
CurrencyDecimal,
|
||||
CurrencyLong,
|
||||
CurrencyNoSymbol,
|
||||
CurrencyNoSymbolDecimal,
|
||||
CurrencyNoSymbolLong,
|
||||
Decimal,
|
||||
DecimalShort,
|
||||
DecimalLong,
|
||||
Number,
|
||||
Percent,
|
||||
PercentDecimal,
|
||||
PercentDecimalLong,
|
||||
PercentRawDecimal
|
||||
}
|
||||
}
|
||||
|
||||
public static class ExportColumnFormatExtensions
|
||||
{
|
||||
public static string GetExcelFormatString(this ExportColumnFormat format)
|
||||
{
|
||||
const string GENERAL_FORMAT = "#0;(#0)";
|
||||
const string NUMBER_FORMAT = "#,##0";
|
||||
const string DECIMAL_FORMAT = "#,##0.00";
|
||||
const string DECIMAL_LONG_FORMAT = "#,##0.0000";
|
||||
|
||||
|
||||
// https://support.microsoft.com/en-au/office/number-format-codes-5026bbd6-04bc-48cd-bf33-80f18b4eae68
|
||||
|
||||
return format switch
|
||||
{
|
||||
ExportColumnFormat.Default => GENERAL_FORMAT,
|
||||
ExportColumnFormat.FiscalYear => GENERAL_FORMAT,
|
||||
ExportColumnFormat.FiscalQuarter => GENERAL_FORMAT,
|
||||
ExportColumnFormat.Year => "yyyy",
|
||||
ExportColumnFormat.Quarter => GENERAL_FORMAT,
|
||||
ExportColumnFormat.Month => "MM-yyyy",
|
||||
ExportColumnFormat.Day => "MM-dd-yyyy",
|
||||
ExportColumnFormat.Currency => $"${NUMBER_FORMAT};(${NUMBER_FORMAT})",
|
||||
ExportColumnFormat.CurrencyDecimal => $"${DECIMAL_FORMAT};(${DECIMAL_FORMAT})",
|
||||
ExportColumnFormat.CurrencyLong => $"${DECIMAL_LONG_FORMAT};(${DECIMAL_LONG_FORMAT})",
|
||||
ExportColumnFormat.Decimal => $"{DECIMAL_FORMAT};({DECIMAL_FORMAT})",
|
||||
ExportColumnFormat.DecimalLong => $"{DECIMAL_LONG_FORMAT};({DECIMAL_LONG_FORMAT})",
|
||||
ExportColumnFormat.Number => $"{NUMBER_FORMAT};({NUMBER_FORMAT})",
|
||||
ExportColumnFormat.Percent => $"{NUMBER_FORMAT}%;({NUMBER_FORMAT}%)",
|
||||
ExportColumnFormat.PercentDecimal => $"{DECIMAL_FORMAT}%;({DECIMAL_FORMAT}%)",
|
||||
ExportColumnFormat.PercentDecimalLong => $"{DECIMAL_LONG_FORMAT}%;({DECIMAL_LONG_FORMAT}%)",
|
||||
_ => throw new ArgumentOutOfRangeException("ExportColumnFormat.format", format,
|
||||
"value not handled in switch statement")
|
||||
};
|
||||
}
|
||||
public static string GetCsvFormatString(this ExportColumnFormat format)
|
||||
{
|
||||
const string DEFAULT_FORMAT = "";
|
||||
|
||||
return format switch
|
||||
{
|
||||
ExportColumnFormat.Year => "yyyy",
|
||||
ExportColumnFormat.Month => "MM-yyyy",
|
||||
ExportColumnFormat.Day => "MM-dd-yyyy",
|
||||
_=> DEFAULT_FORMAT
|
||||
};
|
||||
}}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Strata.Analytics.Biz.Export.Models
|
||||
{
|
||||
public class ExportColumnOptions
|
||||
{
|
||||
public string ColumnId { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
public ExportColumnFormat Format { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
using Strata.DataSchema.Models.Query;
|
||||
|
||||
namespace Strata.Analytics.Biz.Export.Models
|
||||
{
|
||||
public class ExportDataSheetRequest
|
||||
{
|
||||
public ExportFileType ExportFileType { get; set; }
|
||||
|
||||
public QueryConfig QueryConfig { get; set; }
|
||||
|
||||
public IEnumerable<ExportColumnOptions> ExportColumnOptions { get; set; }
|
||||
|
||||
public string ReportName { get; set; }
|
||||
|
||||
public string AppliedFilters { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Strata.Analytics.Biz.Export.Models
|
||||
{
|
||||
public enum ExportFileType
|
||||
{
|
||||
Csv = 0,
|
||||
Excel
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CsvHelper.Configuration;
|
||||
using CsvHelper;
|
||||
using Strata.Analytics.Biz.Export.Models;
|
||||
using Strata.Analytics.Biz.Export.Utils;
|
||||
using Strata.Analytics.Biz.Utils;
|
||||
|
||||
|
||||
namespace Strata.Analytics.Biz.Export.Services
|
||||
{
|
||||
internal class CsvService : ICsvService
|
||||
{
|
||||
|
||||
public CsvService()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
public async Task<MemoryStream> GetCsvMemoryStream(ExportDataSheetRequest request, IEnumerable<dynamic> data, CancellationToken cancellationToken)
|
||||
{
|
||||
var formattedData = ExportDataUtils.ConvertDynamicToObjectArray(data, request.ExportColumnOptions).ToList();
|
||||
|
||||
var stringWriter = new StringWriter();
|
||||
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
|
||||
{
|
||||
Delimiter = ","
|
||||
};
|
||||
CsvWriter csvWriter = new(stringWriter, config);
|
||||
|
||||
|
||||
/*
|
||||
Loop through the rows and values to format the output.
|
||||
Dates need to be formatted to match the expected UI format, else the raw data could just be exported as csvWriter.WriteRecords()
|
||||
*/
|
||||
foreach (var (row, rIndex) in formattedData.WithIndex())
|
||||
{
|
||||
foreach (var (value, vIndex) in row.WithIndex())
|
||||
{
|
||||
// write headers
|
||||
if (rIndex == 0) csvWriter.WriteField(value);
|
||||
else
|
||||
{
|
||||
// get data formats for each column
|
||||
var format = request.ExportColumnOptions.ElementAt(vIndex).Format.GetCsvFormatString();
|
||||
csvWriter.WriteField(value is DateTime date ? date.ToString(format) : value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
await csvWriter.NextRecordAsync();
|
||||
}
|
||||
|
||||
return new MemoryStream(Encoding.UTF8.GetBytes(stringWriter.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OfficeOpenXml;
|
||||
using Strata.Analytics.Biz.Export.Models;
|
||||
using Strata.Analytics.Biz.Export.Utils;
|
||||
|
||||
namespace Strata.Analytics.Biz.Export.Services
|
||||
{
|
||||
internal class ExcelService: IExcelService
|
||||
{
|
||||
|
||||
public ExcelService()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public async Task<MemoryStream> GetExcelMemoryStream(ExportDataSheetRequest request, IEnumerable<dynamic> data, CancellationToken cancellationToken)
|
||||
{
|
||||
using var excelPackage = new ExcelPackage();
|
||||
|
||||
AddWorksheets(excelPackage, request, data);
|
||||
|
||||
await excelPackage.SaveAsync(cancellationToken);
|
||||
|
||||
return new MemoryStream(await excelPackage.GetAsByteArrayAsync(cancellationToken));
|
||||
}
|
||||
|
||||
|
||||
private static void AddWorksheets(ExcelPackage excelPackage, ExportDataSheetRequest request, IEnumerable<dynamic> data)
|
||||
{
|
||||
var formattedData = ExportDataUtils.ConvertDynamicToObjectArray(data, request.ExportColumnOptions);
|
||||
var dataSheet = excelPackage.AddDataWorksheet(request.ReportName, formattedData, new ExcelCellAddress("A1"));
|
||||
dataSheet.Columns.AutoFit();
|
||||
|
||||
// Format the columns based on request options
|
||||
for (var i = 0; i < request.ExportColumnOptions.Count(); i++)
|
||||
{
|
||||
// columns are '1' based
|
||||
dataSheet.Column(i + 1).Style.Numberformat.Format = request.ExportColumnOptions.ElementAt(i).Format.GetExcelFormatString();
|
||||
}
|
||||
|
||||
if(string.IsNullOrEmpty(request.AppliedFilters)) return;
|
||||
|
||||
// Add second worksheet to contain the filter text.
|
||||
var filterWorksheet = excelPackage.AddWorksheet($"Filters-{request.ReportName}");
|
||||
|
||||
var headerRange = filterWorksheet.Cells["A1:D1"];
|
||||
headerRange.Merge = true;
|
||||
headerRange.Style.Font.Size = 16;
|
||||
headerRange.Style.Font.Bold = true;
|
||||
filterWorksheet.Cells["A1"].Value = request.ReportName;
|
||||
|
||||
/* filter text contains line breaks and tabs. setting a value produce a result like a Paste would in excel,
|
||||
so adjust rows/cols accordingly for display formatting */
|
||||
var filterLines = request.AppliedFilters.Split('\n');
|
||||
|
||||
var row = 3;
|
||||
foreach (var filterLine in filterLines)
|
||||
{
|
||||
var col = 1 + (filterLine.Count(c => c == '\t'));
|
||||
filterWorksheet.Cells[row, col].Value = filterLine;
|
||||
row++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Strata.Analytics.Biz.DataQuery;
|
||||
using Strata.Analytics.Biz.Export.Models;
|
||||
|
||||
namespace Strata.Analytics.Biz.Export.Services
|
||||
{
|
||||
internal class ExportService: IExportService
|
||||
{
|
||||
private readonly IDataQueryService _dataQueryService;
|
||||
private readonly IExcelService _excelService;
|
||||
private readonly ICsvService _csvService;
|
||||
|
||||
public ExportService(IDataQueryService dataQueryService, IExcelService excelService, ICsvService csvService)
|
||||
{
|
||||
_dataQueryService = dataQueryService;
|
||||
_excelService = excelService;
|
||||
_csvService = csvService;
|
||||
}
|
||||
|
||||
public async Task<MemoryStream> DownloadDataSheet(ExportDataSheetRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
var queryData = await _dataQueryService.QueryData(request.QueryConfig, cancellationToken);
|
||||
var memoryStream = request.ExportFileType switch
|
||||
{
|
||||
ExportFileType.Csv => await _csvService.GetCsvMemoryStream(request, queryData, cancellationToken),
|
||||
ExportFileType.Excel => await _excelService.GetExcelMemoryStream(request, queryData, cancellationToken),
|
||||
_ => throw new ArgumentOutOfRangeException("request.ExportFileType", request.ExportFileType,
|
||||
"value not handled in switch statement")
|
||||
};
|
||||
|
||||
return memoryStream;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Strata.Analytics.Biz.Export.Models;
|
||||
|
||||
namespace Strata.Analytics.Biz.Export.Services
|
||||
{
|
||||
public interface ICsvService
|
||||
{
|
||||
public Task<MemoryStream> GetCsvMemoryStream(ExportDataSheetRequest request, IEnumerable<dynamic> data,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Strata.Analytics.Biz.Export.Models;
|
||||
|
||||
namespace Strata.Analytics.Biz.Export.Services
|
||||
{
|
||||
public interface IExcelService
|
||||
{
|
||||
public Task<MemoryStream> GetExcelMemoryStream(ExportDataSheetRequest request, IEnumerable<dynamic> data,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user