Skip to content

Commit 97783c5

Browse files
authored
Merge pull request #2774 from TechnologyEnhancedLearning/Develop/Features/TD-4086-CentreAdminLaunchTableau
TD-4086 Allow centre admins to launch tableau from reports page
2 parents 8588475 + 326cf13 commit 97783c5

File tree

10 files changed

+203
-19
lines changed

10 files changed

+203
-19
lines changed

DigitalLearningSolutions.Data/Extensions/ConfigurationExtensions.cs

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,17 @@ public static class ConfigurationExtensions
4949

5050
private const string LearningHubUserAPIUserAPIUrl = "LearningHubUserApi:UserApiUrl";
5151
private const string UserResearchUrlName = "UserResearchUrl";
52-
52+
private const string TableauSectionKey = "TableauDashboards";
53+
private const string TableauClientId = "ClientId";
54+
private const string TableauClientSecretId = "ClientSecretId";
55+
private const string TableauClientSecret = "ClientSecret";
56+
private const string TableauUsername = "Username";
57+
private const string TableauClientName = "ClientName";
58+
private const string TableauSiteUrl = "SiteUrl";
59+
private const string TableauWorkbookName = "WorkBookName";
60+
private const string TableauViewName = "ViewName";
61+
private const string TableauSiteName = "SiteName";
62+
private const string TableauAuthApi = "AuthApiPath";
5363
public static string GetAppRootPath(this IConfiguration config)
5464
{
5565
return config[AppRootPathName]!;
@@ -180,7 +190,7 @@ public static int GetExportQueryRowLimit(this IConfiguration config)
180190
}
181191
public static int GetMaxBulkUploadRowsLimit(this IConfiguration config)
182192
{
183-
int.TryParse(config[MaxBulkUploadRowsLimitKey],out int limitKey);
193+
int.TryParse(config[MaxBulkUploadRowsLimitKey], out int limitKey);
184194
return limitKey;
185195
}
186196

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

202212
public static long GetFreshdeskCreateTicketGroupId(this IConfiguration config)
203213
{
204-
long.TryParse(config[FreshdeskCreateTicketGroupId], out long ticketGroupId);
214+
long.TryParse(config[FreshdeskCreateTicketGroupId], out long ticketGroupId);
205215
return ticketGroupId;
206216
}
207217
public static long GetFreshdeskCreateTicketProductId(this IConfiguration config)
@@ -218,5 +228,46 @@ public static string GetUserResearchUrl(this IConfiguration config)
218228
{
219229
return config[UserResearchUrlName]!;
220230
}
231+
public static string GetTableauClientName(this IConfiguration config)
232+
{
233+
return config[$"{TableauSectionKey}:{TableauClientName}"]!;
234+
}
235+
public static string GetTableauClientId(this IConfiguration config)
236+
{
237+
return config[$"{TableauSectionKey}:{TableauClientId}"]!;
238+
}
239+
public static string GetTableauClientSecret(this IConfiguration config)
240+
{
241+
return config[$"{TableauSectionKey}:{TableauClientSecret}"]!;
242+
}
243+
public static string GetTableauClientSecretId(this IConfiguration config)
244+
{
245+
return config[$"{TableauSectionKey}:{TableauClientSecretId}"]!;
246+
}
247+
public static string GetTableauUser(this IConfiguration config)
248+
{
249+
return config[$"{TableauSectionKey}:{TableauUsername}"]!;
250+
}
251+
public static string GetTableauSiteUrl(this IConfiguration config)
252+
{
253+
return config[$"{TableauSectionKey}:{TableauSiteUrl}"]!;
254+
}
255+
public static string GetTableauAuthApi(this IConfiguration config)
256+
{
257+
return config[$"{TableauSectionKey}:{TableauAuthApi}"]!;
258+
}
259+
public static string GetTableauSiteName(this IConfiguration config)
260+
{
261+
return config[$"{TableauSectionKey}:{TableauSiteName}"]!;
262+
}
263+
public static string GetTableauWorkbookName(this IConfiguration config)
264+
{
265+
return config[$"{TableauSectionKey}:{TableauWorkbookName}"]!;
266+
}
267+
public static string GetTableauViewName(this IConfiguration config)
268+
{
269+
return config[$"{TableauSectionKey}:{TableauViewName}"]!;
270+
}
271+
221272
}
222273
}

DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/SelfAssessmentReports/SelfAssessmentReportsController.cs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
using System;
1212
using DigitalLearningSolutions.Data.Utilities;
1313
using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.Reports;
14+
using DigitalLearningSolutions.Web.Helpers.ExternalApis;
15+
using System.Threading.Tasks;
16+
using Microsoft.Extensions.Configuration;
17+
using DigitalLearningSolutions.Data.Extensions;
1418

1519
[FeatureGate(FeatureFlags.RefactoredTrackingSystem)]
1620
[Authorize(Policy = CustomPolicies.UserCentreAdmin)]
@@ -20,15 +24,26 @@
2024
public class SelfAssessmentReportsController : Controller
2125
{
2226
private readonly ISelfAssessmentReportService selfAssessmentReportService;
27+
private readonly ITableauConnectionHelperService tableauConnectionHelper;
2328
private readonly IClockUtility clockUtility;
24-
29+
private readonly string tableauUrl;
30+
private readonly string tableauSiteName;
31+
private readonly string workbookName;
32+
private readonly string viewName;
2533
public SelfAssessmentReportsController(
2634
ISelfAssessmentReportService selfAssessmentReportService,
27-
IClockUtility clockUtility
35+
ITableauConnectionHelperService tableauConnectionHelper,
36+
IClockUtility clockUtility,
37+
IConfiguration config
2838
)
2939
{
3040
this.selfAssessmentReportService = selfAssessmentReportService;
41+
this.tableauConnectionHelper = tableauConnectionHelper;
3142
this.clockUtility = clockUtility;
43+
tableauUrl = config.GetTableauSiteUrl();
44+
tableauSiteName = config.GetTableauSiteName();
45+
workbookName = config.GetTableauWorkbookName();
46+
viewName = config.GetTableauViewName();
3247
}
3348
public IActionResult Index()
3449
{
@@ -63,5 +78,19 @@ public IActionResult DownloadSelfAssessmentReport(int selfAssessmentId)
6378
fileName
6479
);
6580
}
81+
[HttpGet]
82+
[Route("TableauCompetencyDashboard")]
83+
public IActionResult TableauCompetencyDashboard()
84+
{
85+
var userEmail = User.GetUserPrimaryEmail();
86+
var jwt = tableauConnectionHelper.GetTableauJwt(userEmail);
87+
ViewBag.SiteName = tableauSiteName;
88+
ViewBag.TableauServerUrl = tableauUrl;
89+
ViewBag.WorkbookName = workbookName;
90+
ViewBag.ViewName = viewName;
91+
ViewBag.JwtToken = jwt;
92+
93+
return View();
94+
}
6695
}
6796
}

DigitalLearningSolutions.Web/Helpers/ExternalApis/FilteredApiHelper.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@
66
using System.Net.Http;
77
using System.Security.Claims;
88
using System.Text;
9-
using System.Text.Json;
109
using System.Threading.Tasks;
1110
using Newtonsoft.Json;
1211
using System.Net.Http.Headers;
1312
using DigitalLearningSolutions.Data.Models.External.Filtered;
14-
using System.Collections;
1513
using System.Collections.Generic;
1614
using System.Linq;
1715
using DigitalLearningSolutions.Data.Utilities;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
namespace DigitalLearningSolutions.Web.Helpers.ExternalApis
2+
{
3+
using Microsoft.IdentityModel.Tokens;
4+
using System.IdentityModel.Tokens.Jwt;
5+
using System.Text;
6+
using System;
7+
using Microsoft.Extensions.Configuration;
8+
using DigitalLearningSolutions.Data.Extensions;
9+
10+
public interface ITableauConnectionHelperService
11+
{
12+
string GetTableauJwt(string email);
13+
}
14+
public class TableauConnectionHelper : ITableauConnectionHelperService
15+
{
16+
private readonly string connectedAppSecretKey;
17+
private readonly string connectedAppSecretId;
18+
private readonly string connectedAppClientId;
19+
private readonly string user;
20+
public TableauConnectionHelper(IConfiguration config)
21+
{
22+
connectedAppClientId = config.GetTableauClientId();
23+
connectedAppSecretId = config.GetTableauClientSecretId();
24+
connectedAppSecretKey = config.GetTableauClientSecret();
25+
user = config.GetTableauUser();
26+
}
27+
public string GetTableauJwt(string email)
28+
{
29+
var key = Encoding.UTF8.GetBytes(connectedAppSecretKey);
30+
var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256);
31+
var header = new JwtHeader(signingCredentials)
32+
{
33+
{ "kid", connectedAppSecretId },
34+
{ "iss", connectedAppClientId }
35+
};
36+
37+
var payload = new JwtPayload
38+
{
39+
{ "jti", Guid.NewGuid().ToString()},
40+
{ "iss", connectedAppClientId },
41+
{ "aud", "tableau" },
42+
{ "exp", new DateTimeOffset(DateTime.UtcNow.AddMinutes(5)).ToUnixTimeSeconds() },
43+
{ "sub", user },
44+
{ "scp", new[] { "tableau:views:embed" } },
45+
{ "ExernalUserEmail", new [] { email } }
46+
};
47+
var token = new JwtSecurityToken(header, payload);
48+
var tokenHandler = new JwtSecurityTokenHandler();
49+
var tokenString = tokenHandler.WriteToken(token);
50+
return tokenString;
51+
}
52+
}
53+
}

DigitalLearningSolutions.Web/Helpers/FeatureFlags.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ public static class FeatureFlags
1414
public const string UserFeedbackBar = "UserFeedbackBar";
1515
public const string ShowSelfAssessmentProgressButtons = "ShowSelfAssessmentProgressButtons";
1616
public const string LoginWithLearningHub = "LoginWithLearningHub";
17+
public const string TableauSelfAssessmentDashboards = "TableauSelfAssessmentDashboards";
1718
}
1819
}

DigitalLearningSolutions.Web/Startup.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,13 @@ namespace DigitalLearningSolutions.Web
4646
using Microsoft.Extensions.Hosting;
4747
using Microsoft.FeatureManagement;
4848
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
49-
using Microsoft.IdentityModel.Tokens;
50-
using Microsoft.AspNetCore.Http;
51-
using System.Linq;
5249
using Microsoft.AspNetCore.Identity;
53-
using AspNetCoreRateLimit;
5450
using static DigitalLearningSolutions.Data.DataServices.ICentreApplicationsDataService;
5551
using static DigitalLearningSolutions.Web.Services.ICentreApplicationsService;
5652
using static DigitalLearningSolutions.Web.Services.ICentreSelfAssessmentsService;
5753
using System;
5854
using IsolationLevel = System.Transactions.IsolationLevel;
59-
using System.Collections.Concurrent;
6055
using Serilog;
61-
using static DigitalLearningSolutions.Data.DataServices.ICentreApplicationsDataService;
62-
using static DigitalLearningSolutions.Web.Services.ICentreApplicationsService;
63-
using static DigitalLearningSolutions.Web.Services.ICentreSelfAssessmentsService;
6456

6557
public class Startup
6658
{
@@ -560,6 +552,7 @@ private static void RegisterHttpClients(IServiceCollection services)
560552
services.AddHttpClient<ILearningHubReportApiClient, LearningHubReportApiClient>();
561553
services.AddScoped<IFreshdeskApiClient, FreshdeskApiClient>();
562554
services.AddScoped<ILearningHubUserApiClient, LearningHubUserApiClient>();
555+
services.AddScoped<ITableauConnectionHelperService, TableauConnectionHelper>();
563556
}
564557

565558
private static void RegisterWebServiceFilters(IServiceCollection services)
@@ -592,12 +585,13 @@ private static void RegisterWebServiceFilters(IServiceCollection services)
592585

593586
public void Configure(IApplicationBuilder app, IMigrationRunner migrationRunner, IFeatureManager featureManager)
594587
{
588+
var tableauServerUrl = config.GetTableauSiteUrl();
595589
app.UseMiddleware<DLSIPRateLimitMiddleware>();
596590
app.Use(async (context, next) =>
597591
{
598592
context.Response.Headers.Add("content-security-policy",
599593
"default-src 'self'; " +
600-
"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=';" +
594+
$"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=';" +
601595
"style-src 'self' 'unsafe-inline' https://use.fontawesome.com; " +
602596
"font-src https://script.hotjar.com https://assets.nhs.uk/; " +
603597
"connect-src 'self' http: ws:; " +

DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/SelfAssessmentReports/Index.cshtml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@using DigitalLearningSolutions.Web.Models.Enums
22
@using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.Reports
3+
@using DigitalLearningSolutions.Web.Helpers
34
@model SelfAssessmentReportsViewModel
45
@{
56
ViewData["Title"] = "Self assessment reports";
@@ -18,7 +19,14 @@
1819
<span class="nhsuk-u-visually-hidden"> - </span>
1920
</span>@ViewData["Title"]
2021
</h1>
21-
<p class="nhsuk-lede-text">Use this page to download Excel learner activity reports for competency self assessments at your centre.</p>
22+
<feature name="@(FeatureFlags.TableauSelfAssessmentDashboards)">
23+
@if (Model.SelfAssessmentSelects.Any())
24+
{
25+
<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" />
26+
}
27+
</feature>
28+
<h2>Excel learner activity reports</h2>
29+
<p class="nhsuk-lede-text">Download Excel competency self assessments activity reports for your centre.</p>
2230
<ul>
2331
<li>
2432
<a asp-controller="SelfAssessmentReports" asp-action="DownloadDigitalCapabilityToExcel">Digital Skills Assessment Tool - Download report</a>
@@ -41,5 +49,7 @@
4149
}
4250
}
4351
</ul>
52+
53+
4454
</div>
4555
</div>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
@using DigitalLearningSolutions.Web.Helpers
2+
@{
3+
var tableauServerUrl = ViewBag.TableauServerUrl;
4+
var workbookName = ViewBag.WorkbookName;
5+
var viewName = ViewBag.ViewName;
6+
var jwtToken = ViewBag.JwtToken;
7+
var siteName = ViewBag.SiteName;
8+
var srcUrl = $"{tableauServerUrl}/t/{siteName}/views/{workbookName}/{viewName}";
9+
ViewData["Title"] = "Supervised self assessments dashboard";
10+
}
11+
<div class="nhsuk-back-link">
12+
<a class="nhsuk-back-link__link" role="button" asp-action="Index">
13+
<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">
14+
<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>
15+
</svg>
16+
Back
17+
</a>
18+
</div>
19+
<h1>@ViewData["Title"]</h1>
20+
<feature name="@(FeatureFlags.TableauSelfAssessmentDashboards)">
21+
<tableau-viz id='tableau-viz'
22+
src='@srcUrl' token='@jwtToken' toolbar='bottom'>
23+
If the dashboard doesn't appear after a few seconds, <a href="#">reload the page</a>
24+
</tableau-viz>
25+
@section scripts {
26+
@* We are not using Yarn/npm for the Tableau JS becaue of errors during installation relating to a missing dependency *@
27+
<script type="module" src="@tableauServerUrl/javascripts/api/tableau.embedding.3.latest.min.js"></script>
28+
}
29+
</feature>
30+
<feature name="@(FeatureFlags.TableauSelfAssessmentDashboards)" negate="true">
31+
<h2>Oops! We are still working on this area of the site</h2>
32+
<p class="nhsuk-lede-text">This feature is under development and should be available soon.</p>
33+
</feature>

DigitalLearningSolutions.Web/appsettings.Production.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"UserFeedbackBar": true,
2323
"ExportQueryRowLimit": 250,
2424
"MaxBulkUploadRows": 200,
25-
"LoginWithLearningHub": true
25+
"LoginWithLearningHub": true,
26+
"TableauSelfAssessmentDashboards": false
2627
},
2728
"LearningHubOpenAPIBaseUrl": "https://learninghubnhsuk-openapi-prod.azurewebsites.net",
2829
"LearningHubReportAPIConfig": {

DigitalLearningSolutions.Web/appsettings.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"UseSignposting": true,
1818
"PricingPage": true,
1919
"ShowSelfAssessmentProgressButtons": false,
20-
"LoginWithLearningHub": true
20+
"LoginWithLearningHub": true,
21+
"TableauSelfAssessmentDashboards": true
2122
},
2223
"LearningHubOpenAPIBaseUrl": "https://uks-learninghubnhsuk-openapi-test.azurewebsites.net",
2324
"LearningHubOpenAPIKey": "",
@@ -78,5 +79,18 @@
7879
"LearningHubUserApi": {
7980
"UserApiUrl": "https://userapi.learninghub.nhs.uk/api/"
8081
},
82+
"TableauDashboards": {
83+
"SiteUrl": "https://tabuat.data.england.nhs.uk",
84+
"SiteName": "monitor",
85+
"AuthApiPath": "/api/3.21/auth/signin",
86+
"WorkBookName": "DLSIdentifiableDataNHSEUAT",
87+
"ViewName": "Cover",
88+
"CompetencyDashboardUrl": "https://tabuat.data.england.nhs.uk/#/site/monitor/views/DLSIdentifiableDataNHSEUAT/Cover",
89+
"Username": "svc-tel-dls",
90+
"ClientName": "tel_dls",
91+
"ClientId": "a7906ce3-e0c9-403e-a169-8eb78d858f8a",
92+
"ClientSecretId": "38b99058-a806-4ee2-bf7a-ad1933a81ae5",
93+
"ClientSecret": ""
94+
},
8195
"UserResearchUrl": "https://forms.office.com/e/nKcK8AdHRX"
8296
}

0 commit comments

Comments
 (0)