Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
07727e2
TD-4086-Adds tableau JWT token helper class and settings
kevwhitt-hee Jun 24, 2024
3a0b5de
Adds users.primaryemail to JWT claims
kevwhitt-hee Jun 24, 2024
801ea9b
Starts to add view and controller code
kevwhitt-hee Jun 24, 2024
49250ce
Removes duplicate using using directives from Startup
kevwhitt-hee Jun 24, 2024
70fe40c
Sends JWT auth token to Tableau and redirects to dashboard if successful
kevwhitt-hee Jun 26, 2024
8a949fc
TD-4086 Reworks JWT token generation to add iss and kid to token headers
kevwhitt-hee Jul 9, 2024
6bfece6
Squashed commit of the following:
kevwhitt-hee Jul 25, 2024
028e860
TD-4086 Reworks the JWT token code to generate correct format with co…
kevwhitt-hee Jul 29, 2024
c5208b5
TD-4086 removing unneeded comment
kevwhitt-hee Aug 9, 2024
0955280
Removes unnecessary using statements
kevwhitt-hee Aug 9, 2024
b95bfd5
Reworks tableau code to use tableau's embed visualisation library
kevwhitt-hee Sep 11, 2024
face0f8
Reworks the embed view and fixes embed scope
kevwhitt-hee Sep 12, 2024
0415f9a
Squashed commit of the following:
kevwhitt-hee Sep 12, 2024
e5ac58c
Improves navigation and tweaks email JWT param name
kevwhitt-hee Sep 18, 2024
4cc9cbb
Revert "Squashed commit of the following:"
kevwhitt-hee Sep 18, 2024
fb4ce8a
Revert "Squashed commit of the following:"
kevwhitt-hee Sep 18, 2024
19a263d
Update param name to match reports
kevwhitt-hee Sep 24, 2024
6c53130
Tweaks name of user attribute being passed and improves styling of la…
kevwhitt-hee Sep 25, 2024
9d5b6d2
Corrects wording of link to make consistent with page title
kevwhitt-hee Sep 26, 2024
326cf13
Tweaks email attribute name and feature flags the dashboard link and …
kevwhitt-hee Sep 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,17 @@ public static class ConfigurationExtensions

private const string LearningHubUserAPIUserAPIUrl = "LearningHubUserApi:UserApiUrl";
private const string UserResearchUrlName = "UserResearchUrl";

private const string TableauSectionKey = "TableauDashboards";
private const string TableauClientId = "ClientId";
private const string TableauClientSecretId = "ClientSecretId";
private const string TableauClientSecret = "ClientSecret";
private const string TableauUsername = "Username";
private const string TableauClientName = "ClientName";
private const string TableauSiteUrl = "SiteUrl";
private const string TableauWorkbookName = "WorkBookName";
private const string TableauViewName = "ViewName";
private const string TableauSiteName = "SiteName";
private const string TableauAuthApi = "AuthApiPath";
public static string GetAppRootPath(this IConfiguration config)
{
return config[AppRootPathName]!;
Expand Down Expand Up @@ -180,7 +190,7 @@ public static int GetExportQueryRowLimit(this IConfiguration config)
}
public static int GetMaxBulkUploadRowsLimit(this IConfiguration config)
{
int.TryParse(config[MaxBulkUploadRowsLimitKey],out int limitKey);
int.TryParse(config[MaxBulkUploadRowsLimitKey], out int limitKey);
return limitKey;
}

Expand All @@ -201,7 +211,7 @@ public static string GetLearningHubAuthenticationClientSecret(this IConfiguratio

public static long GetFreshdeskCreateTicketGroupId(this IConfiguration config)
{
long.TryParse(config[FreshdeskCreateTicketGroupId], out long ticketGroupId);
long.TryParse(config[FreshdeskCreateTicketGroupId], out long ticketGroupId);
return ticketGroupId;
}
public static long GetFreshdeskCreateTicketProductId(this IConfiguration config)
Expand All @@ -218,5 +228,46 @@ public static string GetUserResearchUrl(this IConfiguration config)
{
return config[UserResearchUrlName]!;
}
public static string GetTableauClientName(this IConfiguration config)
{
return config[$"{TableauSectionKey}:{TableauClientName}"]!;
}
public static string GetTableauClientId(this IConfiguration config)
{
return config[$"{TableauSectionKey}:{TableauClientId}"]!;
}
public static string GetTableauClientSecret(this IConfiguration config)
{
return config[$"{TableauSectionKey}:{TableauClientSecret}"]!;
}
public static string GetTableauClientSecretId(this IConfiguration config)
{
return config[$"{TableauSectionKey}:{TableauClientSecretId}"]!;
}
public static string GetTableauUser(this IConfiguration config)
{
return config[$"{TableauSectionKey}:{TableauUsername}"]!;
}
public static string GetTableauSiteUrl(this IConfiguration config)
{
return config[$"{TableauSectionKey}:{TableauSiteUrl}"]!;
}
public static string GetTableauAuthApi(this IConfiguration config)
{
return config[$"{TableauSectionKey}:{TableauAuthApi}"]!;
}
public static string GetTableauSiteName(this IConfiguration config)
{
return config[$"{TableauSectionKey}:{TableauSiteName}"]!;
}
public static string GetTableauWorkbookName(this IConfiguration config)
{
return config[$"{TableauSectionKey}:{TableauWorkbookName}"]!;
}
public static string GetTableauViewName(this IConfiguration config)
{
return config[$"{TableauSectionKey}:{TableauViewName}"]!;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
using System;
using DigitalLearningSolutions.Data.Utilities;
using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.Reports;
using DigitalLearningSolutions.Web.Helpers.ExternalApis;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using DigitalLearningSolutions.Data.Extensions;

[FeatureGate(FeatureFlags.RefactoredTrackingSystem)]
[Authorize(Policy = CustomPolicies.UserCentreAdmin)]
Expand All @@ -20,15 +24,26 @@
public class SelfAssessmentReportsController : Controller
{
private readonly ISelfAssessmentReportService selfAssessmentReportService;
private readonly ITableauConnectionHelperService tableauConnectionHelper;
private readonly IClockUtility clockUtility;

private readonly string tableauUrl;
private readonly string tableauSiteName;
private readonly string workbookName;
private readonly string viewName;
public SelfAssessmentReportsController(
ISelfAssessmentReportService selfAssessmentReportService,
IClockUtility clockUtility
ITableauConnectionHelperService tableauConnectionHelper,
IClockUtility clockUtility,
IConfiguration config
)
{
this.selfAssessmentReportService = selfAssessmentReportService;
this.tableauConnectionHelper = tableauConnectionHelper;
this.clockUtility = clockUtility;
tableauUrl = config.GetTableauSiteUrl();
tableauSiteName = config.GetTableauSiteName();
workbookName = config.GetTableauWorkbookName();
viewName = config.GetTableauViewName();
}
public IActionResult Index()
{
Expand Down Expand Up @@ -63,5 +78,19 @@ public IActionResult DownloadSelfAssessmentReport(int selfAssessmentId)
fileName
);
}
[HttpGet]
[Route("TableauCompetencyDashboard")]
public IActionResult TableauCompetencyDashboard()
{
var userEmail = User.GetUserPrimaryEmail();
var jwt = tableauConnectionHelper.GetTableauJwt(userEmail);
ViewBag.SiteName = tableauSiteName;
ViewBag.TableauServerUrl = tableauUrl;
ViewBag.WorkbookName = workbookName;
ViewBag.ViewName = viewName;
ViewBag.JwtToken = jwt;

return View();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@
using System.Net.Http;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Net.Http.Headers;
using DigitalLearningSolutions.Data.Models.External.Filtered;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using DigitalLearningSolutions.Data.Utilities;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
namespace DigitalLearningSolutions.Web.Helpers.ExternalApis
{
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Text;
using System;
using Microsoft.Extensions.Configuration;
using DigitalLearningSolutions.Data.Extensions;

public interface ITableauConnectionHelperService
{
string GetTableauJwt(string email);
}
public class TableauConnectionHelper : ITableauConnectionHelperService
{
private readonly string connectedAppSecretKey;
private readonly string connectedAppSecretId;
private readonly string connectedAppClientId;
private readonly string user;
public TableauConnectionHelper(IConfiguration config)
{
connectedAppClientId = config.GetTableauClientId();
connectedAppSecretId = config.GetTableauClientSecretId();
connectedAppSecretKey = config.GetTableauClientSecret();
user = config.GetTableauUser();
}
public string GetTableauJwt(string email)
{
var key = Encoding.UTF8.GetBytes(connectedAppSecretKey);
var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256);
var header = new JwtHeader(signingCredentials)
{
{ "kid", connectedAppSecretId },
{ "iss", connectedAppClientId }
};

var payload = new JwtPayload
{
{ "jti", Guid.NewGuid().ToString()},
{ "iss", connectedAppClientId },
{ "aud", "tableau" },
{ "exp", new DateTimeOffset(DateTime.UtcNow.AddMinutes(5)).ToUnixTimeSeconds() },
{ "sub", user },
{ "scp", new[] { "tableau:views:embed" } },
{ "ExernalUserEmail", new [] { email } }
};
var token = new JwtSecurityToken(header, payload);
var tokenHandler = new JwtSecurityTokenHandler();
var tokenString = tokenHandler.WriteToken(token);
return tokenString;
}
}
}
1 change: 1 addition & 0 deletions DigitalLearningSolutions.Web/Helpers/FeatureFlags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ public static class FeatureFlags
public const string UserFeedbackBar = "UserFeedbackBar";
public const string ShowSelfAssessmentProgressButtons = "ShowSelfAssessmentProgressButtons";
public const string LoginWithLearningHub = "LoginWithLearningHub";
public const string TableauSelfAssessmentDashboards = "TableauSelfAssessmentDashboards";
}
}
12 changes: 3 additions & 9 deletions DigitalLearningSolutions.Web/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,13 @@ namespace DigitalLearningSolutions.Web
using Microsoft.Extensions.Hosting;
using Microsoft.FeatureManagement;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Http;
using System.Linq;
using Microsoft.AspNetCore.Identity;
using AspNetCoreRateLimit;
using static DigitalLearningSolutions.Data.DataServices.ICentreApplicationsDataService;
using static DigitalLearningSolutions.Web.Services.ICentreApplicationsService;
using static DigitalLearningSolutions.Web.Services.ICentreSelfAssessmentsService;
using System;
using IsolationLevel = System.Transactions.IsolationLevel;
using System.Collections.Concurrent;
using Serilog;
using static DigitalLearningSolutions.Data.DataServices.ICentreApplicationsDataService;
using static DigitalLearningSolutions.Web.Services.ICentreApplicationsService;
using static DigitalLearningSolutions.Web.Services.ICentreSelfAssessmentsService;

public class Startup
{
Expand Down Expand Up @@ -560,6 +552,7 @@ private static void RegisterHttpClients(IServiceCollection services)
services.AddHttpClient<ILearningHubReportApiClient, LearningHubReportApiClient>();
services.AddScoped<IFreshdeskApiClient, FreshdeskApiClient>();
services.AddScoped<ILearningHubUserApiClient, LearningHubUserApiClient>();
services.AddScoped<ITableauConnectionHelperService, TableauConnectionHelper>();
}

private static void RegisterWebServiceFilters(IServiceCollection services)
Expand Down Expand Up @@ -592,12 +585,13 @@ private static void RegisterWebServiceFilters(IServiceCollection services)

public void Configure(IApplicationBuilder app, IMigrationRunner migrationRunner, IFeatureManager featureManager)
{
var tableauServerUrl = config.GetTableauSiteUrl();
app.UseMiddleware<DLSIPRateLimitMiddleware>();
app.Use(async (context, next) =>
{
context.Response.Headers.Add("content-security-policy",
"default-src 'self'; " +
"script-src 'self' 'unsafe-hashes' 'sha256-oywvD6W6okwID679n4cvPJtWLowSS70Pz87v1ryS0DU=' 'sha256-kbHtQyYDQKz4SWMQ8OHVol3EC0t3tHEJFPCSwNG9NxQ' 'sha256-YoDy5WvNzQHMq2kYTFhDYiGnEgPrvAY5Il6eUu/P4xY=' 'sha256-/n13APBYdqlQW71ZpWflMB/QoXNSUKDxZk1rgZc+Jz8=' https://script.hotjar.com https://www.google-analytics.com https://static.hotjar.com https://www.googletagmanager.com https://cdnjs.cloudflare.com 'sha256-+6WnXIl4mbFTCARd8N3COQmT3bJJmo32N8q8ZSQAIcU=' 'sha256-VQKp2qxuvQmMpqE/U/ASQ0ZQ0pIDvC3dgQPPCqDlvBo=';" +
$"script-src 'self' 'nonce-random772362' https://script.hotjar.com https://www.google-analytics.com https://static.hotjar.com https://www.googletagmanager.com https://cdnjs.cloudflare.com {tableauServerUrl} 'unsafe-hashes' 'sha256-oywvD6W6okwID679n4cvPJtWLowSS70Pz87v1ryS0DU=' 'sha256-kbHtQyYDQKz4SWMQ8OHVol3EC0t3tHEJFPCSwNG9NxQ' 'sha256-YoDy5WvNzQHMq2kYTFhDYiGnEgPrvAY5Il6eUu/P4xY=' 'sha256-/n13APBYdqlQW71ZpWflMB/QoXNSUKDxZk1rgZc+Jz8=' 'sha256-+6WnXIl4mbFTCARd8N3COQmT3bJJmo32N8q8ZSQAIcU=' 'sha256-VQKp2qxuvQmMpqE/U/ASQ0ZQ0pIDvC3dgQPPCqDlvBo=';" +
"style-src 'self' 'unsafe-inline' https://use.fontawesome.com; " +
"font-src https://script.hotjar.com https://assets.nhs.uk/; " +
"connect-src 'self' http: ws:; " +
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@using DigitalLearningSolutions.Web.Models.Enums
@using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.Reports
@using DigitalLearningSolutions.Web.Helpers
@model SelfAssessmentReportsViewModel
@{
ViewData["Title"] = "Self assessment reports";
Expand All @@ -18,7 +19,14 @@
<span class="nhsuk-u-visually-hidden"> - </span>
</span>@ViewData["Title"]
</h1>
<p class="nhsuk-lede-text">Use this page to download Excel learner activity reports for competency self assessments at your centre.</p>
<feature name="@(FeatureFlags.TableauSelfAssessmentDashboards)">
@if (Model.SelfAssessmentSelects.Any())
{
<vc:action-link asp-controller="SelfAssessmentReports" asp-action="TableauCompetencyDashboard" asp-all-route-data="@new Dictionary<string, string>();" link-text="View Tableau supervised self assessments dashboard" />
}
</feature>
<h2>Excel learner activity reports</h2>
<p class="nhsuk-lede-text">Download Excel competency self assessments activity reports for your centre.</p>
<ul>
<li>
<a asp-controller="SelfAssessmentReports" asp-action="DownloadDigitalCapabilityToExcel">Digital Skills Assessment Tool - Download report</a>
Expand All @@ -41,5 +49,7 @@
}
}
</ul>


</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@using DigitalLearningSolutions.Web.Helpers
@{
var tableauServerUrl = ViewBag.TableauServerUrl;
var workbookName = ViewBag.WorkbookName;
var viewName = ViewBag.ViewName;
var jwtToken = ViewBag.JwtToken;
var siteName = ViewBag.SiteName;
var srcUrl = $"{tableauServerUrl}/t/{siteName}/views/{workbookName}/{viewName}";
ViewData["Title"] = "Supervised self assessments dashboard";
}
<div class="nhsuk-back-link">
<a class="nhsuk-back-link__link" role="button" asp-action="Index">
<svg class="nhsuk-icon nhsuk-icon__chevron-left" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" height="24" width="24">
<path d="M8.5 12c0-.3.1-.5.3-.7l5-5c.4-.4 1-.4 1.4 0s.4 1 0 1.4L10.9 12l4.3 4.3c.4.4.4 1 0 1.4s-1 .4-1.4 0l-5-5c-.2-.2-.3-.4-.3-.7z"></path>
</svg>
Back
</a>
</div>
<h1>@ViewData["Title"]</h1>
<feature name="@(FeatureFlags.TableauSelfAssessmentDashboards)">
<tableau-viz id='tableau-viz'
src='@srcUrl' token='@jwtToken' toolbar='bottom'>
If the dashboard doesn't appear after a few seconds, <a href="#">reload the page</a>
</tableau-viz>
@section scripts {
@* We are not using Yarn/npm for the Tableau JS becaue of errors during installation relating to a missing dependency *@
<script type="module" src="@tableauServerUrl/javascripts/api/tableau.embedding.3.latest.min.js"></script>
}
</feature>
<feature name="@(FeatureFlags.TableauSelfAssessmentDashboards)" negate="true">
<h2>Oops! We are still working on this area of the site</h2>
<p class="nhsuk-lede-text">This feature is under development and should be available soon.</p>
</feature>
3 changes: 2 additions & 1 deletion DigitalLearningSolutions.Web/appsettings.Production.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"UserFeedbackBar": true,
"ExportQueryRowLimit": 250,
"MaxBulkUploadRows": 200,
"LoginWithLearningHub": true
"LoginWithLearningHub": true,
"TableauSelfAssessmentDashboards": false
},
"LearningHubOpenAPIBaseUrl": "https://learninghubnhsuk-openapi-prod.azurewebsites.net",
"LearningHubReportAPIConfig": {
Expand Down
16 changes: 15 additions & 1 deletion DigitalLearningSolutions.Web/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"UseSignposting": true,
"PricingPage": true,
"ShowSelfAssessmentProgressButtons": false,
"LoginWithLearningHub": true
"LoginWithLearningHub": true,
"TableauSelfAssessmentDashboards": true
},
"LearningHubOpenAPIBaseUrl": "https://uks-learninghubnhsuk-openapi-test.azurewebsites.net",
"LearningHubOpenAPIKey": "",
Expand Down Expand Up @@ -78,5 +79,18 @@
"LearningHubUserApi": {
"UserApiUrl": "https://userapi.learninghub.nhs.uk/api/"
},
"TableauDashboards": {
"SiteUrl": "https://tabuat.data.england.nhs.uk",
"SiteName": "monitor",
"AuthApiPath": "/api/3.21/auth/signin",
"WorkBookName": "DLSIdentifiableDataNHSEUAT",
"ViewName": "Cover",
"CompetencyDashboardUrl": "https://tabuat.data.england.nhs.uk/#/site/monitor/views/DLSIdentifiableDataNHSEUAT/Cover",
"Username": "svc-tel-dls",
"ClientName": "tel_dls",
"ClientId": "a7906ce3-e0c9-403e-a169-8eb78d858f8a",
"ClientSecretId": "38b99058-a806-4ee2-bf7a-ad1933a81ae5",
"ClientSecret": ""
},
"UserResearchUrl": "https://forms.office.com/e/nKcK8AdHRX"
}
Loading