From f9b9acf7fd66589bb09288f09711a45eb2f747d6 Mon Sep 17 00:00:00 2001 From: HenningNormann Date: Mon, 26 Aug 2024 11:38:20 +0200 Subject: [PATCH] A2 migration changes 4 (#476) - Added support for form summary - Added stylesheet for html generation - Added lastchanged as new parameter to MigrationController.CreateDataElement - Support for migrating text in languages not defined in a2 app - Removed playwright - Removed System.Drawing - Added altinn 2 instance event types --- .../Enums/InstanceEventType.cs | 42 +++++++++++++++- src/Storage/Altinn.Platform.Storage.csproj | 3 -- .../Controllers/ContentOnDemandController.cs | 27 ++++++++-- .../Controllers/MigrationController.cs | 50 ++++++++++++------- .../FunctionsAndProcedures/inserta2xsl.sql | 16 +++--- .../FunctionsAndProcedures/reada2xsls.sql | 4 +- .../Migration/v0.11/01-setup-tables.sql | 3 ++ src/Storage/Repository/IA2Repository.cs | 7 +-- src/Storage/Repository/PgA2Repository.cs | 10 ++-- .../Repository/PgApplicationRepository.cs | 7 --- .../Services/A2OndemandFormattingService.cs | 4 ++ 11 files changed, 121 insertions(+), 52 deletions(-) create mode 100644 src/Storage/Migration/v0.11/01-setup-tables.sql diff --git a/src/Storage.Interface/Enums/InstanceEventType.cs b/src/Storage.Interface/Enums/InstanceEventType.cs index 27dc513e..665bdc33 100644 --- a/src/Storage.Interface/Enums/InstanceEventType.cs +++ b/src/Storage.Interface/Enums/InstanceEventType.cs @@ -88,6 +88,46 @@ public enum InstanceEventType /// /// Instance sent to sign event. /// - SentToSign + SentToSign, + + /// + /// Instance sent to payment event. + /// + SentToPayment, + + /// + /// Instance sent to send in event. + /// + SentToSendIn, + + /// + /// Instance sent to form filling event. + /// + SentToFormFill, + + /// + /// Instance forwarded event. + /// + InstanceForwarded, + + /// + /// Instance right revoked event. + /// + InstanceRightRevoked, + + /// + /// Notification sent event. + /// + NotificationSentSms, + + /// + /// Message archived event. + /// + MessageArchived, + + /// + /// Message read event. + /// + MessageRead } } diff --git a/src/Storage/Altinn.Platform.Storage.csproj b/src/Storage/Altinn.Platform.Storage.csproj index 50eae165..c395111b 100644 --- a/src/Storage/Altinn.Platform.Storage.csproj +++ b/src/Storage/Altinn.Platform.Storage.csproj @@ -25,7 +25,6 @@ - @@ -34,8 +33,6 @@ - - diff --git a/src/Storage/Controllers/ContentOnDemandController.cs b/src/Storage/Controllers/ContentOnDemandController.cs index 9edc83d8..6fb166a5 100644 --- a/src/Storage/Controllers/ContentOnDemandController.cs +++ b/src/Storage/Controllers/ContentOnDemandController.cs @@ -6,16 +6,13 @@ using System.Threading.Tasks; using Altinn.Platform.Storage.Clients; using Altinn.Platform.Storage.Configuration; -using Altinn.Platform.Storage.Helpers; using Altinn.Platform.Storage.Interface.Models; using Altinn.Platform.Storage.Repository; using Altinn.Platform.Storage.Services; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Microsoft.Playwright; namespace Altinn.Platform.Storage.Controllers { @@ -135,18 +132,38 @@ public async Task GetFormdataAsPdf([FromRoute] string org, [FromRoute] s /// The formatted content [HttpGet("formdatahtml")] public async Task GetFormdataAsHtml([FromRoute]string org, [FromRoute] string app, [FromRoute] Guid instanceGuid, [FromRoute] Guid dataGuid, [FromRoute] string language) + { + return await GetFormdataAsHtmlInternal(org, app, instanceGuid, dataGuid, language, 3); + } + + /// + /// Gets the formatted content + /// + /// org + /// app + /// instanceGuid + /// dataGuid + /// language + /// The formatted content + [HttpGet("formsummaryhtml")] + public async Task GetFormSummaryAsHtml([FromRoute] string org, [FromRoute] string app, [FromRoute] Guid instanceGuid, [FromRoute] Guid dataGuid, [FromRoute] string language) + { + return await GetFormdataAsHtmlInternal(org, app, instanceGuid, dataGuid, language, 2); + } + + private async Task GetFormdataAsHtmlInternal(string org, string app, Guid instanceGuid, Guid dataGuid, string language, int viewType) { (Instance instance, _) = await _instanceRepository.GetOne(instanceGuid, true); DataElement htmlElement = instance.Data.First(d => d.Id == dataGuid.ToString()); string htmlFormId = htmlElement.Metadata.First(m => m.Key == "formid").Value; DataElement xmlElement = instance.Data.First(d => d.Metadata.First(m => m.Key == "formid").Value == htmlFormId && d.Id != htmlElement.Id); - string? visiblePagesString = xmlElement.Metadata.FirstOrDefault(m => m.Key == "A2VisiblePages")?.Value; + string visiblePagesString = xmlElement.Metadata.FirstOrDefault(m => m.Key == "A2VisiblePages")?.Value; List visiblePages = !string.IsNullOrEmpty(visiblePagesString) ? visiblePagesString.Split(';').Select(int.Parse).ToList() : null; PrintViewXslBEList xsls = []; int lformid = int.Parse(xmlElement.Metadata.First(m => m.Key == "lformid").Value); int pageNumber = 1; - foreach (var xsl in await _a2Repository.GetXsls(org, app, lformid, language)) + foreach (var xsl in await _a2Repository.GetXsls(org, app, lformid, language, viewType)) { if (visiblePages == null || visiblePages.Contains(pageNumber)) { diff --git a/src/Storage/Controllers/MigrationController.cs b/src/Storage/Controllers/MigrationController.cs index dcc226a7..0c1f90c0 100644 --- a/src/Storage/Controllers/MigrationController.cs +++ b/src/Storage/Controllers/MigrationController.cs @@ -22,7 +22,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Altinn.Platform.Storage.Controllers @@ -98,7 +97,7 @@ public async Task> CreateInstance([FromBody] Instance ins { //// TODO Open issue: what about createdby and lastchangedby? - Instance storedInstance = null; + Instance storedInstance; try { int a2ArchiveReference = int.Parse(instance.DataValues["A2ArchRef"]); @@ -124,7 +123,8 @@ public async Task> CreateInstance([FromBody] Instance ins /// Inserts new data element /// /// The instanceGuid. - /// Element timestamp ticks + /// Element created timestamp ticks + /// Element last changed timestamp ticks /// Element data type /// A2 form id /// A2 logical form id @@ -141,14 +141,16 @@ public async Task> CreateInstance([FromBody] Instance ins [DisableRequestSizeLimit] public async Task> CreateDataElement( [FromRoute]Guid instanceGuid, - [FromQuery(Name = "timestampticks")]long timestampTicks, + [FromQuery(Name = "createdticks")]long createdTicks, + [FromQuery(Name = "changedticks")]long changedTicks, [FromQuery(Name = "datatype")]string dataType, [FromQuery(Name = "formid")]string formid, [FromQuery(Name = "lformid")]string lformid, [FromQuery(Name = "prestext")]string presenationText, [FromQuery(Name = "vispages")]string visiblePages) { - DateTime timestamp = new DateTime(timestampTicks, DateTimeKind.Utc).ToLocalTime(); + DateTime created = new DateTime(createdTicks, DateTimeKind.Utc).ToLocalTime(); + DateTime lastChanged = new DateTime(changedTicks, DateTimeKind.Utc).ToLocalTime(); // TODO Open issue: what about createdby and lastchangedby? Ref. instance @@ -165,12 +167,12 @@ public async Task> CreateDataElement( DataElement dataElement = new() { Id = dataElementId, - Created = timestamp, + Created = created, CreatedBy = instance.CreatedBy, DataType = dataType, InstanceGuid = instanceGuid.ToString(), IsRead = true, - LastChanged = timestamp, + LastChanged = lastChanged, LastChangedBy = instance.LastChangedBy, // TODO: Find out what to populate here BlobStoragePath = DataElementHelper.DataFileName(instance.AppId, instanceGuid.ToString(), dataElementId), Metadata = formid == null ? null : new() @@ -203,6 +205,7 @@ public async Task> CreateDataElement( "signature-presentation" => "ondemand/signature", "ref-data-as-pdf" => "ondemand/formdatapdf", "ref-data-as-html" => "ondemand/formdatahtml", + "ref-summary-data-as-html" => "ondemand/formsummaryhtml", "payment-presentation" => "ondemand/payment", _ => throw new ArgumentException(dataElement.DataType), }; @@ -303,20 +306,32 @@ public async Task> CreateText([FromRoute] string org, [Fr { try { - // There is allways an existing text from the app migration TextResource textResource = await _textRepository.Get(org, app, language); using var reader = new StreamReader(Request.Body); - var resource = textResource.Resources.Find(resource => resource.Id == key); - if (resource == null) + if (textResource == null) { - textResource.Resources.Add(new() { Id = key, Value = await reader.ReadToEndAsync() }); + textResource = await _textRepository.Create(org, app, new TextResource() + { + Id = (await _applicationRepository.FindOne($"{org}/{app}", org)).Id, + Language = language, + Org = org, + Resources = [new TextResourceElement() { Id = key, Value = await reader.ReadToEndAsync() }] + }); } else { - resource.Value = await reader.ReadToEndAsync(); - } + var resource = textResource.Resources.Find(resource => resource.Id == key); + if (resource == null) + { + textResource.Resources.Add(new() { Id = key, Value = await reader.ReadToEndAsync() }); + } + else + { + resource.Value = await reader.ReadToEndAsync(); + } - textResource = await _textRepository.Update(org, app, textResource); + textResource = await _textRepository.Update(org, app, textResource); + } return Created((string)null, textResource); } @@ -396,16 +411,17 @@ public async Task CreateImage() /// A2 logical form id /// Page number /// Language + /// Xsl type /// Ok [AllowAnonymous] - [HttpPost("xsl/{org}/{app}/{lformid}/{pagenumber}/{language}")] + [HttpPost("xsl/{org}/{app}/{lformid}/{pagenumber}/{language}/{xsltype}")] [DisableFormValueModelBinding] [ProducesResponseType(StatusCodes.Status201Created)] [Produces("application/json")] - public async Task CreateXsl([FromRoute] string org, [FromRoute] string app, [FromRoute] int lformid, [FromRoute] int pagenumber, [FromRoute] string language) + public async Task CreateXsl([FromRoute] string org, [FromRoute] string app, [FromRoute] int lformid, [FromRoute] int pagenumber, [FromRoute] string language, [FromRoute] int xsltype) { using var reader = new StreamReader(Request.Body); - await _a2Repository.CreateXsl(org, app, lformid, language, pagenumber, await reader.ReadToEndAsync()); + await _a2Repository.CreateXsl(org, app, lformid, language, pagenumber, await reader.ReadToEndAsync(), xsltype); return Created(); } diff --git a/src/Storage/Migration/FunctionsAndProcedures/inserta2xsl.sql b/src/Storage/Migration/FunctionsAndProcedures/inserta2xsl.sql index 3d6d7234..67fb01b6 100644 --- a/src/Storage/Migration/FunctionsAndProcedures/inserta2xsl.sql +++ b/src/Storage/Migration/FunctionsAndProcedures/inserta2xsl.sql @@ -1,4 +1,4 @@ -CREATE OR REPLACE PROCEDURE storage.inserta2xsl (_org TEXT, _app TEXT, _lformid INT, _language TEXT, _pagenumber INT, _xsl TEXT) +CREATE OR REPLACE PROCEDURE storage.inserta2xsl (_org TEXT, _app TEXT, _lformid INT, _language TEXT, _pagenumber INT, _xsl TEXT, _xsltype INT) LANGUAGE 'plpgsql' AS $BODY$ DECLARE @@ -7,16 +7,16 @@ DECLARE _applicationinternalid INTEGER; BEGIN SELECT id into _applicationinternalid FROM storage.applications WHERE org = _org AND app = _app; - SELECT id into _parentId FROM storage.a2xsls WHERE org = _org AND app = _app AND xsl = _xsl; + SELECT id into _parentId FROM storage.a2xsls WHERE org = _org AND app = _app AND xsl = _xsl AND xsltype = _xsltype; SELECT id into _appId FROM storage.applications WHERE org = _org AND app = _app; IF _parentId IS NOT NULL THEN - INSERT INTO storage.a2xsls (org, app, applicationinternalid, parentid, lformid, language, pagenumber, xsl) VALUES - (_org, _app, _applicationinternalid, _parentId, _lformid, _language, _pagenumber, NULL) - ON CONFLICT (app, org, lformid, pagenumber, language) DO NOTHING; + INSERT INTO storage.a2xsls (org, app, applicationinternalid, parentid, lformid, language, pagenumber, xsl, xsltype) VALUES + (_org, _app, _applicationinternalid, _parentId, _lformid, _language, _pagenumber, NULL, _xsltype) + ON CONFLICT (app, org, lformid, pagenumber, language, xsltype) DO NOTHING; ELSE - INSERT INTO storage.a2xsls (org, app, applicationinternalid, parentid, lformid, language, pagenumber, xsl) VALUES - (_org, _app, _applicationinternalid, NULL, _lformid, _language, _pagenumber, _xsl) - ON CONFLICT (app, org, lformid, pagenumber, language) DO UPDATE SET xsl = _xsl; + INSERT INTO storage.a2xsls (org, app, applicationinternalid, parentid, lformid, language, pagenumber, xsl, xsltype) VALUES + (_org, _app, _applicationinternalid, NULL, _lformid, _language, _pagenumber, _xsl, _xsltype) + ON CONFLICT (app, org, lformid, pagenumber, language, xsltype) DO UPDATE SET xsl = _xsl; END IF; END; $BODY$; \ No newline at end of file diff --git a/src/Storage/Migration/FunctionsAndProcedures/reada2xsls.sql b/src/Storage/Migration/FunctionsAndProcedures/reada2xsls.sql index 3701eb6a..286f32a5 100644 --- a/src/Storage/Migration/FunctionsAndProcedures/reada2xsls.sql +++ b/src/Storage/Migration/FunctionsAndProcedures/reada2xsls.sql @@ -1,4 +1,4 @@ -CREATE OR REPLACE FUNCTION storage.reada2xsls(_org TEXT, _app TEXT, _lformid INT, _language TEXT) +CREATE OR REPLACE FUNCTION storage.reada2xsls(_org TEXT, _app TEXT, _lformid INT, _language TEXT, _xsltype INT) RETURNS TABLE (xsl TEXT) LANGUAGE 'plpgsql' @@ -11,7 +11,7 @@ RETURN QUERY ELSE (SELECT p.xsl FROM storage.a2xsls p WHERE p.id = x.parentid) END FROM storage.a2xsls x - WHERE x.org = _org and x.app = _app and x.lformid = _lformid and x.language = _language + WHERE x.org = _org and x.app = _app and x.lformid = _lformid and x.language = _language and x.xsltype = _xsltype ORDER BY pagenumber; END; diff --git a/src/Storage/Migration/v0.11/01-setup-tables.sql b/src/Storage/Migration/v0.11/01-setup-tables.sql new file mode 100644 index 00000000..b34374aa --- /dev/null +++ b/src/Storage/Migration/v0.11/01-setup-tables.sql @@ -0,0 +1,3 @@ +ALTER TABLE storage.a2xsls ADD COLUMN IF NOT EXISTS xsltype INT NOT NULL DEFAULT -1; +ALTER TABLE storage.a2xsls DROP CONSTRAINT a2xslsalternateid; +ALTER TABLE storage.a2xsls ADD CONSTRAINT a2xslsalternateid UNIQUE (app, org, lformid, pagenumber, language, xsltype); \ No newline at end of file diff --git a/src/Storage/Repository/IA2Repository.cs b/src/Storage/Repository/IA2Repository.cs index 406e9afc..6857194c 100644 --- a/src/Storage/Repository/IA2Repository.cs +++ b/src/Storage/Repository/IA2Repository.cs @@ -1,9 +1,6 @@ using System.Collections.Generic; -using System.IO; using System.Threading.Tasks; -using Altinn.Platform.Storage.Interface.Models; - namespace Altinn.Platform.Storage.Repository { /// @@ -15,12 +12,12 @@ public interface IA2Repository /// Get the stylesheets for a data element (a2 sub/main form) /// /// A list of stylesheets - Task> GetXsls(string org, string app, int lformId, string language); + Task> GetXsls(string org, string app, int lformId, string language, int xslType); /// /// Insert a stylesheet for a data element (a2 sub/main form page) /// - Task CreateXsl(string org, string app, int lformId, string language, int pageNumber, string xsl); + Task CreateXsl(string org, string app, int lformId, string language, int pageNumber, string xsl, int xslType); /// /// Insert an a2 codelist diff --git a/src/Storage/Repository/PgA2Repository.cs b/src/Storage/Repository/PgA2Repository.cs index bf7786a5..650138d5 100644 --- a/src/Storage/Repository/PgA2Repository.cs +++ b/src/Storage/Repository/PgA2Repository.cs @@ -17,8 +17,8 @@ namespace Altinn.Platform.Storage.Repository /// public class PgA2Repository : IA2Repository { - private static readonly string _readXslSql = "select * from storage.reada2xsls (@_org, @_app, @_lformid, @_language)"; - private static readonly string _insertXslSql = "call storage.inserta2xsl (@_org, @_app, @_lformid, @_language, @_pagenumber, @_xsl)"; + private static readonly string _readXslSql = "select * from storage.reada2xsls (@_org, @_app, @_lformid, @_language, @_xsltype)"; + private static readonly string _insertXslSql = "call storage.inserta2xsl (@_org, @_app, @_lformid, @_language, @_pagenumber, @_xsl, @_xsltype)"; private static readonly string _insertCodelistSql = "call storage.inserta2codelist (@_name, @_language, @_version, @_codelist)"; private static readonly string _insertImageSql = "call storage.inserta2image (@_name, @_image)"; private static readonly string _readCodelistSql = "select * from storage.reada2codelist (@_name, @_language)"; @@ -53,7 +53,7 @@ public PgA2Repository( } /// - public async Task CreateXsl(string org, string app, int lformId, string language, int pageNumber, string xsl) + public async Task CreateXsl(string org, string app, int lformId, string language, int pageNumber, string xsl, int xslType) { await using NpgsqlCommand pgcom = _dataSource.CreateCommand(_insertXslSql); pgcom.Parameters.AddWithValue("_org", NpgsqlDbType.Text, org); @@ -62,6 +62,7 @@ public async Task CreateXsl(string org, string app, int lformId, string language pgcom.Parameters.AddWithValue("_language", NpgsqlDbType.Text, language); pgcom.Parameters.AddWithValue("_pagenumber", NpgsqlDbType.Integer, pageNumber); pgcom.Parameters.AddWithValue("_xsl", NpgsqlDbType.Text, xsl); + pgcom.Parameters.AddWithValue("_xsltype", NpgsqlDbType.Integer, xslType); using TelemetryTracker tracker = new(_telemetryClient, pgcom); await pgcom.ExecuteNonQueryAsync(); @@ -98,7 +99,7 @@ public async Task CreateImage(string name, byte[] image) } /// - public async Task> GetXsls(string org, string app, int lformId, string language) + public async Task> GetXsls(string org, string app, int lformId, string language, int xslType) { List xsls = []; @@ -107,6 +108,7 @@ public async Task> GetXsls(string org, string app, int lformId, str pgcom.Parameters.AddWithValue("_app", NpgsqlDbType.Text, app); pgcom.Parameters.AddWithValue("_lformid", NpgsqlDbType.Integer, lformId); pgcom.Parameters.AddWithValue("_language", NpgsqlDbType.Text, language); + pgcom.Parameters.AddWithValue("_xsltype", NpgsqlDbType.Integer, xslType); using TelemetryTracker tracker = new(_telemetryClient, pgcom); await using NpgsqlDataReader reader = await pgcom.ExecuteReaderAsync(); diff --git a/src/Storage/Repository/PgApplicationRepository.cs b/src/Storage/Repository/PgApplicationRepository.cs index 371d3930..82e9eb07 100644 --- a/src/Storage/Repository/PgApplicationRepository.cs +++ b/src/Storage/Repository/PgApplicationRepository.cs @@ -1,19 +1,12 @@ using System; using System.Collections.Generic; using System.Data; -using System.Data.Common; -using System.Drawing; -using System.Linq; -using System.Net; using System.Text; -using System.Threading; using System.Threading.Tasks; using Altinn.Platform.Storage.Configuration; using Altinn.Platform.Storage.Interface.Models; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Npgsql; using NpgsqlTypes; diff --git a/src/Storage/Services/A2OndemandFormattingService.cs b/src/Storage/Services/A2OndemandFormattingService.cs index 6c03792c..e4bd07a6 100644 --- a/src/Storage/Services/A2OndemandFormattingService.cs +++ b/src/Storage/Services/A2OndemandFormattingService.cs @@ -20,6 +20,8 @@ namespace Altinn.Platform.Storage.Services /// public class A2OndemandFormattingService : IA2OndemandFormattingService { + private const string _css = ""; + private readonly IA2Repository _a2Repository; private readonly ILogger _logger; private readonly IMemoryCache _memoryCache; @@ -81,6 +83,8 @@ private string GetFormdataHtmlInternal( htmlToTranslate = htmlWriterOutput.ToString(); } + htmlToTranslate = htmlToTranslate.Replace("", _css); + // Add the archive time stamp and the list of attachments htmlToTranslate = SetArchiveTimeStampToHtml(archiveStamp, htmlToTranslate);