diff --git a/src/Altinn.App.ModelGenerator.Tests/Altinn.App.ModelGenerator.Tests.csproj b/src/Altinn.App.ModelGenerator.Tests/Altinn.App.ModelGenerator.Tests.csproj new file mode 100644 index 00000000..8d207c3f --- /dev/null +++ b/src/Altinn.App.ModelGenerator.Tests/Altinn.App.ModelGenerator.Tests.csproj @@ -0,0 +1,31 @@ + + + + net6.0 + enable + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + Always + + + + diff --git a/src/Altinn.App.ModelGenerator.Tests/LayoutToModelTests.cs b/src/Altinn.App.ModelGenerator.Tests/LayoutToModelTests.cs new file mode 100644 index 00000000..370d25bc --- /dev/null +++ b/src/Altinn.App.ModelGenerator.Tests/LayoutToModelTests.cs @@ -0,0 +1,33 @@ +using System.Text.Json; +using System.Collections.Generic; +using Altinn.App.ModelGenerator; +using Xunit; + +namespace Altinn.App.ModelGenerator.Tests; + +public class LayoutToModelTests +{ + [Theory] + [FileData("LayoutToModel/FormLayout.json", "LayoutToModel/Summary.json", "LayoutToModel/bindings.json", "LayoutToModel/model.cs")] + public void TestGetModelDataBindings(string FormLayout, string summary, string expectedBindings, string expectedModel) + { + // while (!System.Diagnostics.Debugger.IsAttached) + // System.Threading.Thread.Sleep(500); + // System.Diagnostics.Debugger.Break(); + + var bindings = LayoutToModel.GetDataModelBindings(new string[]{FormLayout, summary}); + Assert.Equal(JsonSerializer.Deserialize>(expectedBindings), bindings); + var generatedModel = LayoutToModel.Convert(bindings, "Altinn.App.Models.Model"); + Assert.Equal( + expectedModel.Replace("\r\n","\n"), //Github actions has configured git to translate LF to CRLF + generatedModel); + } + [Theory] + [InlineData("Altinn.App.Models.KRT1226Gjenopprettingsplaner_M", "Altinn.App.Models", "KRT1226Gjenopprettingsplaner_M")] + public void TestSplitNamespace(string fullClassName, string expectedNs, string expectedClassName) + { + var(ns, className) = LayoutToModel.SplitFullClassname(fullClassName); + Assert.Equal(expectedNs, ns); + Assert.Equal(expectedClassName, className); + } +} \ No newline at end of file diff --git a/src/Altinn.App.ModelGenerator.Tests/TestData/FileDataAttribute.cs b/src/Altinn.App.ModelGenerator.Tests/TestData/FileDataAttribute.cs new file mode 100644 index 00000000..ad5e765a --- /dev/null +++ b/src/Altinn.App.ModelGenerator.Tests/TestData/FileDataAttribute.cs @@ -0,0 +1,20 @@ +using Xunit.Sdk; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Linq; + +namespace Altinn.App.ModelGenerator.Tests; + +public class FileDataAttribute : DataAttribute +{ + private readonly string[] Files; + public FileDataAttribute(params string[] files) + { + Files = files; + } + public override IEnumerable GetData(MethodInfo methodInfo) + { + return new List{Files.Select(f=>File.ReadAllText(Path.Join("TestData",f))).ToArray()}; + } +} \ No newline at end of file diff --git a/src/Altinn.App.ModelGenerator.Tests/TestData/LayoutToModel/FormLayout.json b/src/Altinn.App.ModelGenerator.Tests/TestData/LayoutToModel/FormLayout.json new file mode 100644 index 00000000..35b91efb --- /dev/null +++ b/src/Altinn.App.ModelGenerator.Tests/TestData/LayoutToModel/FormLayout.json @@ -0,0 +1,332 @@ +{ + "$schema": "https://altinncdn.no/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "id": "Info", + "type": "Paragraph", + "textResourceBindings": { + "title": "0.info" + }, + "dataModelBindings": {} + }, + { + "id": "Header1", + "type": "Header", + "textResourceBindings": { + "title": "1.generalInfo" + }, + "dataModelBindings": {}, + "size": "S", + "readOnly": true + }, + { + "id": "1-1", + "type": "Input", + "textResourceBindings": { + "title": "1.1.orgNr" + }, + "dataModelBindings": { + "simpleBinding": "rapport.innsender.foretak.organisasjonsnummer.value" + }, + "required": true, + "readOnly": true, + "grid": { + "labelGrid": { + "sm": 6 + }, + "innerGrid": { + "sm": 6 + } + } + }, + { + "id": "1-2", + "type": "Input", + "textResourceBindings": { + "title": "1.2.name" + }, + "dataModelBindings": { + "simpleBinding": "rapport.innsender.foretak.navn.value" + }, + "grid": { + "labelGrid": { + "sm": 6 + }, + "innerGrid": { + "sm": 6 + } + }, + "required": true, + "readOnly": true + }, + { + "id": "1-3-address", + "type": "Input", + "textResourceBindings": { + "title": "1.3.address" + }, + "dataModelBindings": { + "simpleBinding": "rapport.innsender.adresse.adresselinje1.value" + }, + "required": false, + "readOnly": true, + "grid": { + "labelGrid": { + "sm": 6 + }, + "innerGrid": { + "sm": 6 + } + } + }, + { + "id": "1-4-postcode", + "type": "Input", + "textResourceBindings": { + "title": "1.4.postCode" + }, + "dataModelBindings": { + "simpleBinding": "rapport.innsender.adresse.postnummer.value" + }, + "required": false, + "readOnly": true, + "grid": { + "sm": 6, + "innerGrid": { + "sm": 6 + } + } + }, + { + "id": "1-5-postcity", + "type": "Input", + "textResourceBindings": { + "title": "1.5.postCity" + }, + "dataModelBindings": { + "simpleBinding": "rapport.innsender.adresse.poststed.value" + }, + "grid": { + "sm": 4 + }, + "required": false, + "readOnly": true + }, + { + "id": "Header2", + "type": "Header", + "textResourceBindings": { + "title": "2.contactInfo" + }, + "dataModelBindings": {}, + "size": "S", + "readOnly": true + }, + { + "id": "2-1", + "type": "Input", + "textResourceBindings": { + "title": "2.1.contact1.name" + }, + "dataModelBindings": { + "simpleBinding": "rapport.rapportering.kontaktperson1.navn1.value" + }, + "required": true, + "readOnly": false, + "grid": { + "labelGrid": { + "sm": 6 + }, + "innerGrid": { + "sm": 6 + } + } + }, + { + "id": "2-2", + "type": "Input", + "textResourceBindings": { + "title": "2.2.contact1.mail" + }, + "dataModelBindings": { + "simpleBinding": "rapport.rapportering.kontaktperson1.epost1.value" + }, + "required": true, + "readOnly": false, + "grid": { + "labelGrid": { + "sm": 6 + }, + "innerGrid": { + "sm": 6 + }, + "formatting": { + "Input": "Email" + } + } + }, + { + "id": "2-3Prefix", + "type": "Input", + "textResourceBindings": { + "title": "2.3.contact1.prefix" + }, + "dataModelBindings": { + "simpleBinding": "rapport.rapportering.kontaktperson1.telefonprefiks1.value" + }, + "grid": { + "sm": 6, + "innerGrid": { + "sm": 3 + } + }, + "required": true, + "readOnly": false + }, + { + "id": "2-3", + "type": "Input", + "textResourceBindings": { + "title": "2.3.contact1.tlf" + }, + "dataModelBindings": { + "simpleBinding": "rapport.rapportering.kontaktperson1.telefonnummer1.value" + }, + "grid": { + "sm": 6, + "innerGrid": { + "sm": 6 + } + }, + "required": true, + "readOnly": false, + "formatting": { + "number": { + "format": "### ## ###" + } + } + }, + { + "id": "2-4", + "type": "Input", + "textResourceBindings": { + "title": "2.4.contact2.name" + }, + "dataModelBindings": { + "simpleBinding": "rapport.rapportering.kontaktperson2.navn2.value" + }, + "required": false, + "readOnly": false, + "grid": { + "labelGrid": { + "sm": 6 + }, + "innerGrid": { + "sm": 6 + } + } + }, + { + "id": "2-5", + "type": "Input", + "textResourceBindings": { + "title": "2.5.contact2.mail" + }, + "dataModelBindings": { + "simpleBinding": "rapport.rapportering.kontaktperson2.epost2.value" + }, + "required": false, + "readOnly": false, + "grid": { + "labelGrid": { + "sm": 6 + }, + "innerGrid": { + "sm": 6 + } + } + }, + { + "id": "2-6Prefix", + "type": "Input", + "textResourceBindings": { + "title": "2.6.contact2.prefix" + }, + "dataModelBindings": { + "simpleBinding": "rapport.rapportering.kontaktperson2.telefonprefiks2.value" + }, + "grid": { + "sm": 6, + "innerGrid": { + "sm": 3 + } + }, + "required": false, + "readOnly": false + }, + { + "id": "I2-6", + "type": "Input", + "textResourceBindings": { + "title": "2.6.contact2.tlf" + }, + "dataModelBindings": { + "simpleBinding": "rapport.rapportering.kontaktperson2.telefonnummer2.value" + }, + "grid": { + "sm": 6, + "innerGrid": { + "sm": 6 + } + }, + "required": false, + "readOnly": false, + "formatting": { + "number": { + "format": "### ## ###" + } + } + }, + { + "id": "FilOpplasting", + "type": "FileUpload", + "textResourceBindings": { + "title": "Vedlegg" + }, + "dataModelBindings": {}, + "maxFileSizeInMB": 190, + "maxNumberOfAttachments": 25, + "minNumberOfAttachments": 1, + "displayMode": "list", + "required": true, + "hasCustomFileEndings": true, + "validFileEndings": ".pdf, .xls, .xlsx, .doc, .docx, .rtf, .jpg, .jpeg, .txt, .pptx, .ppt, .zip, .odp, .ods, .odt" + }, + { + "id": "4-0", + "type": "TextArea", + "textResourceBindings": { + "title": "4.description", + "help": "4.description.hint" + }, + "dataModelBindings": { + "simpleBinding": "rapport.rapportering.beskrivelse.value" + }, + "required": false, + "readOnly": false + }, + { + "id": "Tilbake-knapp", + "type": "NavigationButtons", + "componentType": "NavigationButtons", + "textResourceBindings": { + "next": "button.next", + "back": "back" + }, + "dataModelBindings": {}, + "showBackButton": false + } + ] + } +} \ No newline at end of file diff --git a/src/Altinn.App.ModelGenerator.Tests/TestData/LayoutToModel/Summary.json b/src/Altinn.App.ModelGenerator.Tests/TestData/LayoutToModel/Summary.json new file mode 100644 index 00000000..d3bf5857 --- /dev/null +++ b/src/Altinn.App.ModelGenerator.Tests/TestData/LayoutToModel/Summary.json @@ -0,0 +1,137 @@ +{ + "$schema": "https://altinncdn.no/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "id": "summary-1header", + "type": "Summary", + "componentRef": "Header1", + "pageRef": "FormLayout" + + }, + { + "id": "summary-11-orgnr", + "type": "Summary", + "componentRef": "1-1", + "pageRef": "FormLayout" + }, + { + "id": "summary-12-orgname", + "type": "Summary", + "componentRef": "1-2", + "pageRef": "FormLayout" + }, + { + "id": "summary-13-orgadresse", + "type": "Summary", + "componentRef": "1-3-address", + "pageRef": "FormLayout" + }, + { + "id": "summary-14-orgpostcode", + "type": "Summary", + "componentRef": "1-4-postcode", + "pageRef": "FormLayout", + "grid": { + "sm": 4 + } + }, + { + "id": "summary-15-orgpostcity", + "type": "Summary", + "componentRef": "1-5-postcity", + "pageRef": "FormLayout", + "grid": { + "sm": 8 + } + }, + { + "id": "summary-2header", + "type": "Summary", + "componentRef": "Header2", + "pageRef": "FormLayout" + + }, + { + "id": "summary-21-name1", + "type": "Summary", + "componentRef": "2-1", + "pageRef": "FormLayout" + }, + { + "id": "summary-22-email1", + "type": "Summary", + "componentRef": "2-2", + "pageRef": "FormLayout" + }, + { + "id": "summary-23-tlfprefix1", + "type": "Summary", + "componentRef": "2-3Prefix", + "pageRef": "FormLayout", + "grid": { + "sm": 4 + } + }, + { + "id": "summary-23-tlf1", + "type": "Summary", + "componentRef": "2-3", + "pageRef": "FormLayout", + "grid": { + "sm": 8 + } + }, + { + "id": "summary-24-name2", + "type": "Summary", + "componentRef": "2-4", + "pageRef": "FormLayout" + }, + { + "id": "summary-25-email2", + "type": "Summary", + "componentRef": "2-5", + "pageRef": "FormLayout" + }, + { + "id": "summary-26-tlfprefix2", + "type": "Summary", + "componentRef": "2-6Prefix", + "pageRef": "FormLayout", + "grid": { + "sm": 4 + } + }, + { + "id": "summary-26-tlf2", + "type": "Summary", + "componentRef": "I2-6", + "pageRef": "FormLayout", + "grid": { + "sm": 8 + } + }, + { + "id": "summary-16", + "type": "Summary", + "componentRef": "FilOpplasting", + "pageRef": "FormLayout" + }, + { + "id": "summary-4", + "type": "Summary", + "componentRef": "4-0", + "pageRef": "FormLayout" + }, + { + "id": "88b9ba10-cb72-44b8-b218-a724fd9106fb", + "type": "Button", + "textResourceBindings": { + "title": "Send inn" + }, + "dataModelBindings": {} + } + ] + } + } \ No newline at end of file diff --git a/src/Altinn.App.ModelGenerator.Tests/TestData/LayoutToModel/bindings.json b/src/Altinn.App.ModelGenerator.Tests/TestData/LayoutToModel/bindings.json new file mode 100644 index 00000000..80684d2a --- /dev/null +++ b/src/Altinn.App.ModelGenerator.Tests/TestData/LayoutToModel/bindings.json @@ -0,0 +1,16 @@ +[ + "rapport.innsender.foretak.organisasjonsnummer.value", + "rapport.innsender.foretak.navn.value", + "rapport.innsender.adresse.adresselinje1.value", + "rapport.innsender.adresse.postnummer.value", + "rapport.innsender.adresse.poststed.value", + "rapport.rapportering.kontaktperson1.navn1.value", + "rapport.rapportering.kontaktperson1.epost1.value", + "rapport.rapportering.kontaktperson1.telefonprefiks1.value", + "rapport.rapportering.kontaktperson1.telefonnummer1.value", + "rapport.rapportering.kontaktperson2.navn2.value", + "rapport.rapportering.kontaktperson2.epost2.value", + "rapport.rapportering.kontaktperson2.telefonprefiks2.value", + "rapport.rapportering.kontaktperson2.telefonnummer2.value", + "rapport.rapportering.beskrivelse.value" +] diff --git a/src/Altinn.App.ModelGenerator.Tests/TestData/LayoutToModel/model.cs b/src/Altinn.App.ModelGenerator.Tests/TestData/LayoutToModel/model.cs new file mode 100644 index 00000000..2e021230 --- /dev/null +++ b/src/Altinn.App.ModelGenerator.Tests/TestData/LayoutToModel/model.cs @@ -0,0 +1,156 @@ +// Auto-generated +#nullable enable +namespace Altinn.App.Models +{ + +public partial class Model +{ + public rapport? rapport { get; set; } +} + +public partial class rapport +{ + public innsender? innsender { get; set; } + public rapportering? rapportering { get; set; } +} + +public partial class innsender +{ + public foretak? foretak { get; set; } + public adresse? adresse { get; set; } +} + +public partial class foretak +{ + public organisasjonsnummer? organisasjonsnummer { get; set; } + public navn? navn { get; set; } +} + +public partial class organisasjonsnummer +{ + public string? value { get; set; } +} + + + +public partial class navn +{ + public string? value { get; set; } +} + + + +public partial class adresse +{ + public adresselinje1? adresselinje1 { get; set; } + public postnummer? postnummer { get; set; } + public poststed? poststed { get; set; } +} + +public partial class adresselinje1 +{ + public string? value { get; set; } +} + + + +public partial class postnummer +{ + public string? value { get; set; } +} + + + +public partial class poststed +{ + public string? value { get; set; } +} + + + +public partial class rapportering +{ + public kontaktperson1? kontaktperson1 { get; set; } + public kontaktperson2? kontaktperson2 { get; set; } + public beskrivelse? beskrivelse { get; set; } +} + +public partial class kontaktperson1 +{ + public navn1? navn1 { get; set; } + public epost1? epost1 { get; set; } + public telefonprefiks1? telefonprefiks1 { get; set; } + public telefonnummer1? telefonnummer1 { get; set; } +} + +public partial class navn1 +{ + public string? value { get; set; } +} + + + +public partial class epost1 +{ + public string? value { get; set; } +} + + + +public partial class telefonprefiks1 +{ + public string? value { get; set; } +} + + + +public partial class telefonnummer1 +{ + public string? value { get; set; } +} + + + +public partial class kontaktperson2 +{ + public navn2? navn2 { get; set; } + public epost2? epost2 { get; set; } + public telefonprefiks2? telefonprefiks2 { get; set; } + public telefonnummer2? telefonnummer2 { get; set; } +} + +public partial class navn2 +{ + public string? value { get; set; } +} + + + +public partial class epost2 +{ + public string? value { get; set; } +} + + + +public partial class telefonprefiks2 +{ + public string? value { get; set; } +} + + + +public partial class telefonnummer2 +{ + public string? value { get; set; } +} + + + +public partial class beskrivelse +{ + public string? value { get; set; } +} + + +} diff --git a/src/Altinn.App.ModelGenerator/Altinn.App.ModelGenerator.csproj b/src/Altinn.App.ModelGenerator/Altinn.App.ModelGenerator.csproj new file mode 100644 index 00000000..a7922e72 --- /dev/null +++ b/src/Altinn.App.ModelGenerator/Altinn.App.ModelGenerator.csproj @@ -0,0 +1,53 @@ + + + + netstandard2.0 + 10.0 + enable + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + $(GetTargetPathDependsOn);GetDependencyTargetPaths + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Altinn.App.ModelGenerator/LayoutToModel.cs b/src/Altinn.App.ModelGenerator/LayoutToModel.cs new file mode 100644 index 00000000..f00610ce --- /dev/null +++ b/src/Altinn.App.ModelGenerator/LayoutToModel.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Nodes; +namespace Altinn.App.ModelGenerator +{ + public class LayoutToModel + { + public static string Convert(IEnumerable bindings, string fullClassName) + { + var (ns, className) = SplitFullClassname(fullClassName); + var three = new ThreeRep(className); + foreach (var binding in bindings) + { + three.AddDottedPath(binding); + } + return $"// Auto-generated\n#nullable enable\nnamespace {ns}\n{{\n\n{three.ToPoco()}\n}}\n"; + } + + public static (string, string) SplitFullClassname(string fullClassName) + { + var index = fullClassName.LastIndexOf('.'); + return (fullClassName.Substring(0,index), fullClassName.Substring(index+1)); + } + + public static List GetDataModelBindings(IEnumerable layouts) + { + var ret = new List(); + foreach (var layout in layouts) + { + if (JsonNode.Parse(layout)?["data"]?["layout"] is JsonArray node) + { + foreach (var component in node) + { + if (component?["dataModelBindings"] is JsonObject bindings) + { + foreach (var binding in bindings) + { + if ((binding.Value as JsonValue)?.TryGetValue(out string? value) ?? false) + { + ret.Add(value!); + } + } + } + } + } + } + return ret; + } + } +} \ No newline at end of file diff --git a/src/Altinn.App.ModelGenerator/ModelSourceGenerator.cs b/src/Altinn.App.ModelGenerator/ModelSourceGenerator.cs new file mode 100644 index 00000000..39311dcf --- /dev/null +++ b/src/Altinn.App.ModelGenerator/ModelSourceGenerator.cs @@ -0,0 +1,53 @@ +using System.Linq; +using System.IO; +using System.Text.Json.Nodes; +using Microsoft.CodeAnalysis; + +namespace Altinn.App.ModelGenerator +{ + [Generator] + public class ModelSourceGenerator : ISourceGenerator + { + public void Execute(GeneratorExecutionContext context) + { + // while (!System.Diagnostics.Debugger.IsAttached) + // System.Threading.Thread.Sleep(500); + // System.Diagnostics.Debugger.Break(); + + var layouts = context.AdditionalFiles.Where(af => PathUtils.IsLayoutPath(af.Path)).ToDictionary(af => PathUtils.FileNameFromPath(af.Path), af => af.GetText()!.ToString()); + var resources = context.AdditionalFiles.Where(af => PathUtils.IsResourcePath(af.Path)).ToDictionary(af => PathUtils.FileNameFromPath(af.Path), af => af.GetText()!.ToString()); + var applicationMetadata = context.AdditionalFiles.FirstOrDefault(af => PathUtils.IsApplicationmetadata(af.Path))?.GetText()?.ToString(); + var settings = context.AdditionalFiles.FirstOrDefault(af => PathUtils.IsSettings(af.Path))?.GetText()?.ToString(); + + ValidateJsonFiles(context); + + // Generate code; + var modelName = (JsonNode.Parse(applicationMetadata!)?["dataTypes"] as JsonArray)?.FirstOrDefault(dt => dt?["appLogic"] != null)?["appLogic"]?["classRef"]?.GetValue(); + // modelName = "Altinn.App.Models.KRT1226Gjenopprettingsplaner_M"; + var bindings = LayoutToModel.GetDataModelBindings(layouts.Values); + var generatedModel = LayoutToModel.Convert(bindings, modelName!); + + + context.AddSource("model.g.cs", generatedModel); + } + + public void ValidateJsonFiles(GeneratorExecutionContext context) + { + foreach(var jsonFile in context.AdditionalFiles.Where(af=>af.Path.EndsWith(".json"))) + { + var path = jsonFile?.Path; + var content = jsonFile?.GetText()?.ToString(); + if(path == null || content == null) continue; + foreach(var diagnostic in JsonValidator.GetJsonParseDiagnostics(path, content)) + { + context.ReportDiagnostic(diagnostic); + }; + } + } + + public void Initialize(GeneratorInitializationContext context) + { + // No initialization required for this one + } + } +} \ No newline at end of file diff --git a/src/Altinn.App.ModelGenerator/models/ThreeRep.cs b/src/Altinn.App.ModelGenerator/models/ThreeRep.cs new file mode 100644 index 00000000..505481b7 --- /dev/null +++ b/src/Altinn.App.ModelGenerator/models/ThreeRep.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Altinn.App.ModelGenerator +{ + public class ThreeRep + { + private readonly string Name; + public readonly Dictionary dict = new Dictionary(); + + public ThreeRep(string name) + { + Name = name; + } + + public void AddDottedPath(string dottedPath) + { + var path = dottedPath.Split(new char[] { '.' }, 2); + var t = path[0]; + if (!dict.ContainsKey(t)) + { + dict[t] = new ThreeRep(t); + } + if (path.Length == 2) + { + var rest = path[1]; + dict[t].AddDottedPath(rest); + return; + } + } + public string ToPoco() + { + var classname = Name; + var props = dict.Select(element => $"\n\tpublic {(element.Value.dict.Count == 0 ? "string" : element.Key)}? {element.Key} {{ get; set; }}").ToList(); + if (props.Count > 0) + return $"public partial class {classname}\n{{{string.Join("", props)}\n}}\n\n" + string.Join("\n\n", dict.Values.Select(tr => tr.ToPoco())); + + return ""; + } + } +} \ No newline at end of file diff --git a/src/Altinn.App.ModelGenerator/utils/PathUtils.cs b/src/Altinn.App.ModelGenerator/utils/PathUtils.cs new file mode 100644 index 00000000..f6ee5281 --- /dev/null +++ b/src/Altinn.App.ModelGenerator/utils/PathUtils.cs @@ -0,0 +1,48 @@ +using System.IO; + +namespace Altinn.App.ModelGenerator +{ + public static class PathUtils + { + public static bool IsLayoutPath(string filePath) + { + var fileInfo = new FileInfo(filePath); + if(!fileInfo.Extension.Equals(".json", System.StringComparison.InvariantCultureIgnoreCase)) return false; + if(!fileInfo.Directory.Name.Equals("layouts", System.StringComparison.InvariantCultureIgnoreCase)) return false; + if(!fileInfo.Directory.Parent.Name.Equals("ui", System.StringComparison.InvariantCultureIgnoreCase)) return false; + return true; + } + + public static bool IsResourcePath(string filePath) + { + var fileInfo = new FileInfo(filePath); + if(!fileInfo.Extension.Equals(".json", System.StringComparison.InvariantCultureIgnoreCase)) return false; + if(!fileInfo.Name.StartsWith("resource.", System.StringComparison.InvariantCultureIgnoreCase)) return false; + if(!fileInfo.Directory.Name.Equals("texts", System.StringComparison.InvariantCultureIgnoreCase)) return false; + if(!fileInfo.Directory.Parent.Name.Equals("config", System.StringComparison.InvariantCultureIgnoreCase)) return false; + return true; + } + + public static bool IsApplicationmetadata(string filePath) + { + var fileInfo = new FileInfo(filePath); + if(!fileInfo.Name.Equals("applicationmetadata.json", System.StringComparison.InvariantCultureIgnoreCase)) return false; + if(!fileInfo.Directory.Name.Equals("config", System.StringComparison.InvariantCultureIgnoreCase)) return false; + return true; + } + + public static bool IsSettings(string filePath) + { + var fileInfo = new FileInfo(filePath); + if(!fileInfo.Name.Equals("settings.json", System.StringComparison.InvariantCultureIgnoreCase)) return false; + if(!fileInfo.Directory.Name.Equals("ui", System.StringComparison.InvariantCultureIgnoreCase)) return false; + return true; + } + + public static string FileNameFromPath(string filePath) + { + var fileInfo = new FileInfo(filePath); + return fileInfo.Name; + } + } +} \ No newline at end of file diff --git a/src/Altinn.App.ModelGenerator/validators/AltinnDiagnosticsDescriptors.cs b/src/Altinn.App.ModelGenerator/validators/AltinnDiagnosticsDescriptors.cs new file mode 100644 index 00000000..92c1e4f6 --- /dev/null +++ b/src/Altinn.App.ModelGenerator/validators/AltinnDiagnosticsDescriptors.cs @@ -0,0 +1,15 @@ +using Microsoft.CodeAnalysis; + +namespace Altinn.App.ModelGenerator; + +public static class AltinnDiagnosticsDescriptors +{ + public static readonly DiagnosticDescriptor JsonParseError + = new DiagnosticDescriptor( + id: "ALT001", + title: "Error in json parsing", + messageFormat: "{0}", + category: "AltinnAnalyzer", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true ); +} diff --git a/src/Altinn.App.ModelGenerator/validators/JsonValidator.cs b/src/Altinn.App.ModelGenerator/validators/JsonValidator.cs new file mode 100644 index 00000000..fc478ddb --- /dev/null +++ b/src/Altinn.App.ModelGenerator/validators/JsonValidator.cs @@ -0,0 +1,29 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +namespace Altinn.App.ModelGenerator; + +public static class JsonValidator +{ + public static List GetJsonParseDiagnostics(string filename, string content) + { + var ret = new List(); + try + { + var options = new JsonDocumentOptions() + { + CommentHandling = JsonCommentHandling.Skip, // stylecop.json includes comments + }; + JsonNode.Parse(content, null, options); + } + catch (JsonException e) + { + var linePosition = new LinePosition((int)e.LineNumber, (int)e.BytePositionInLine); + ret.Add(Diagnostic.Create(AltinnDiagnosticsDescriptors.JsonParseError, + Location.Create(filename, new TextSpan(0, content.Length), new LinePositionSpan(linePosition, linePosition)), + e.Message)); + } + return ret; + } +} diff --git a/src/App.IntegrationTests/App.IntegrationTestsRef.csproj b/src/App.IntegrationTests/App.IntegrationTestsRef.csproj index b7e0b059..2dae8fd0 100644 --- a/src/App.IntegrationTests/App.IntegrationTestsRef.csproj +++ b/src/App.IntegrationTests/App.IntegrationTestsRef.csproj @@ -1,7 +1,7 @@  - net5.0 + net6.0 false diff --git a/src/App/App.csproj b/src/App/App.csproj index afb2af71..de7faec9 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 Altinn.App Altinn.App @@ -16,6 +16,13 @@ + + + + + + + diff --git a/src/App/AppRef.csproj b/src/App/AppRef.csproj index e6f3bd95..26e2daa8 100644 --- a/src/App/AppRef.csproj +++ b/src/App/AppRef.csproj @@ -1,9 +1,10 @@ - net5.0 + net6.0 Altinn.App Altinn.App + true @@ -18,7 +19,7 @@ - + all runtime; build; native; contentfiles; analyzers @@ -27,6 +28,13 @@ + + + + + + + ..\..\Altinn3.ruleset diff --git a/src/App/config/applicationmetadata.json b/src/App/config/applicationmetadata.json index 82255041..b1433c6c 100644 --- a/src/App/config/applicationmetadata.json +++ b/src/App/config/applicationmetadata.json @@ -5,17 +5,25 @@ "createdBy": "username", "title": { "nb": "Bestillingseksempelapp" }, "dataTypes": [ + { + "id": "model", + "allowedContentTypes": [ "application/xml" ], + "appLogic": { + + "classRef":"Altinn.App.Models.TestModel" + } + }, { "id": "vedlegg", "allowedContentTypes": [ "application/pdf", "image/png", "image/jpeg" ], "minCount": 0, - "taskId": "Task_1", + "taskId": "Task_1" }, { "id": "ref-data-as-pdf", "allowedContentTypes": [ "application/pdf" ], - "minCount": 0, - "taskId": "Task_1", + "minCount": 0, + "taskId": "Task_1" } ], "partyTypesAllowed": { diff --git a/src/App/logic/InstantiationHandler.cs b/src/App/logic/InstantiationHandler.cs index c1b4ac94..160f98a8 100644 --- a/src/App/logic/InstantiationHandler.cs +++ b/src/App/logic/InstantiationHandler.cs @@ -56,6 +56,11 @@ public async Task RunInstantiationValidation(Inst /// External prefill available under instansiation if supplied public async Task DataCreation(Instance instance, object data, Dictionary prefill) { + if (data is Altinn.App.Models.TestModel model) + { + var addr = model?.rapport?.innsender?.adresse; + } + await Task.CompletedTask; } } diff --git a/src/App/ui/Settings.json b/src/App/ui/Settings.json new file mode 100644 index 00000000..cc8eb4a1 --- /dev/null +++ b/src/App/ui/Settings.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://altinncdn.no/schemas/json/layout/layoutSettings.schema.v1.json", + "pages": { + "excludeFromPdf": ["Summary"], + "order": ["FormLayout", "Summary"] + } +} diff --git a/src/App/ui/layouts/FormLayout.json b/src/App/ui/layouts/FormLayout.json new file mode 100644 index 00000000..35b91efb --- /dev/null +++ b/src/App/ui/layouts/FormLayout.json @@ -0,0 +1,332 @@ +{ + "$schema": "https://altinncdn.no/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "id": "Info", + "type": "Paragraph", + "textResourceBindings": { + "title": "0.info" + }, + "dataModelBindings": {} + }, + { + "id": "Header1", + "type": "Header", + "textResourceBindings": { + "title": "1.generalInfo" + }, + "dataModelBindings": {}, + "size": "S", + "readOnly": true + }, + { + "id": "1-1", + "type": "Input", + "textResourceBindings": { + "title": "1.1.orgNr" + }, + "dataModelBindings": { + "simpleBinding": "rapport.innsender.foretak.organisasjonsnummer.value" + }, + "required": true, + "readOnly": true, + "grid": { + "labelGrid": { + "sm": 6 + }, + "innerGrid": { + "sm": 6 + } + } + }, + { + "id": "1-2", + "type": "Input", + "textResourceBindings": { + "title": "1.2.name" + }, + "dataModelBindings": { + "simpleBinding": "rapport.innsender.foretak.navn.value" + }, + "grid": { + "labelGrid": { + "sm": 6 + }, + "innerGrid": { + "sm": 6 + } + }, + "required": true, + "readOnly": true + }, + { + "id": "1-3-address", + "type": "Input", + "textResourceBindings": { + "title": "1.3.address" + }, + "dataModelBindings": { + "simpleBinding": "rapport.innsender.adresse.adresselinje1.value" + }, + "required": false, + "readOnly": true, + "grid": { + "labelGrid": { + "sm": 6 + }, + "innerGrid": { + "sm": 6 + } + } + }, + { + "id": "1-4-postcode", + "type": "Input", + "textResourceBindings": { + "title": "1.4.postCode" + }, + "dataModelBindings": { + "simpleBinding": "rapport.innsender.adresse.postnummer.value" + }, + "required": false, + "readOnly": true, + "grid": { + "sm": 6, + "innerGrid": { + "sm": 6 + } + } + }, + { + "id": "1-5-postcity", + "type": "Input", + "textResourceBindings": { + "title": "1.5.postCity" + }, + "dataModelBindings": { + "simpleBinding": "rapport.innsender.adresse.poststed.value" + }, + "grid": { + "sm": 4 + }, + "required": false, + "readOnly": true + }, + { + "id": "Header2", + "type": "Header", + "textResourceBindings": { + "title": "2.contactInfo" + }, + "dataModelBindings": {}, + "size": "S", + "readOnly": true + }, + { + "id": "2-1", + "type": "Input", + "textResourceBindings": { + "title": "2.1.contact1.name" + }, + "dataModelBindings": { + "simpleBinding": "rapport.rapportering.kontaktperson1.navn1.value" + }, + "required": true, + "readOnly": false, + "grid": { + "labelGrid": { + "sm": 6 + }, + "innerGrid": { + "sm": 6 + } + } + }, + { + "id": "2-2", + "type": "Input", + "textResourceBindings": { + "title": "2.2.contact1.mail" + }, + "dataModelBindings": { + "simpleBinding": "rapport.rapportering.kontaktperson1.epost1.value" + }, + "required": true, + "readOnly": false, + "grid": { + "labelGrid": { + "sm": 6 + }, + "innerGrid": { + "sm": 6 + }, + "formatting": { + "Input": "Email" + } + } + }, + { + "id": "2-3Prefix", + "type": "Input", + "textResourceBindings": { + "title": "2.3.contact1.prefix" + }, + "dataModelBindings": { + "simpleBinding": "rapport.rapportering.kontaktperson1.telefonprefiks1.value" + }, + "grid": { + "sm": 6, + "innerGrid": { + "sm": 3 + } + }, + "required": true, + "readOnly": false + }, + { + "id": "2-3", + "type": "Input", + "textResourceBindings": { + "title": "2.3.contact1.tlf" + }, + "dataModelBindings": { + "simpleBinding": "rapport.rapportering.kontaktperson1.telefonnummer1.value" + }, + "grid": { + "sm": 6, + "innerGrid": { + "sm": 6 + } + }, + "required": true, + "readOnly": false, + "formatting": { + "number": { + "format": "### ## ###" + } + } + }, + { + "id": "2-4", + "type": "Input", + "textResourceBindings": { + "title": "2.4.contact2.name" + }, + "dataModelBindings": { + "simpleBinding": "rapport.rapportering.kontaktperson2.navn2.value" + }, + "required": false, + "readOnly": false, + "grid": { + "labelGrid": { + "sm": 6 + }, + "innerGrid": { + "sm": 6 + } + } + }, + { + "id": "2-5", + "type": "Input", + "textResourceBindings": { + "title": "2.5.contact2.mail" + }, + "dataModelBindings": { + "simpleBinding": "rapport.rapportering.kontaktperson2.epost2.value" + }, + "required": false, + "readOnly": false, + "grid": { + "labelGrid": { + "sm": 6 + }, + "innerGrid": { + "sm": 6 + } + } + }, + { + "id": "2-6Prefix", + "type": "Input", + "textResourceBindings": { + "title": "2.6.contact2.prefix" + }, + "dataModelBindings": { + "simpleBinding": "rapport.rapportering.kontaktperson2.telefonprefiks2.value" + }, + "grid": { + "sm": 6, + "innerGrid": { + "sm": 3 + } + }, + "required": false, + "readOnly": false + }, + { + "id": "I2-6", + "type": "Input", + "textResourceBindings": { + "title": "2.6.contact2.tlf" + }, + "dataModelBindings": { + "simpleBinding": "rapport.rapportering.kontaktperson2.telefonnummer2.value" + }, + "grid": { + "sm": 6, + "innerGrid": { + "sm": 6 + } + }, + "required": false, + "readOnly": false, + "formatting": { + "number": { + "format": "### ## ###" + } + } + }, + { + "id": "FilOpplasting", + "type": "FileUpload", + "textResourceBindings": { + "title": "Vedlegg" + }, + "dataModelBindings": {}, + "maxFileSizeInMB": 190, + "maxNumberOfAttachments": 25, + "minNumberOfAttachments": 1, + "displayMode": "list", + "required": true, + "hasCustomFileEndings": true, + "validFileEndings": ".pdf, .xls, .xlsx, .doc, .docx, .rtf, .jpg, .jpeg, .txt, .pptx, .ppt, .zip, .odp, .ods, .odt" + }, + { + "id": "4-0", + "type": "TextArea", + "textResourceBindings": { + "title": "4.description", + "help": "4.description.hint" + }, + "dataModelBindings": { + "simpleBinding": "rapport.rapportering.beskrivelse.value" + }, + "required": false, + "readOnly": false + }, + { + "id": "Tilbake-knapp", + "type": "NavigationButtons", + "componentType": "NavigationButtons", + "textResourceBindings": { + "next": "button.next", + "back": "back" + }, + "dataModelBindings": {}, + "showBackButton": false + } + ] + } +} \ No newline at end of file diff --git a/src/App/ui/layouts/Summary.json b/src/App/ui/layouts/Summary.json new file mode 100644 index 00000000..d3bf5857 --- /dev/null +++ b/src/App/ui/layouts/Summary.json @@ -0,0 +1,137 @@ +{ + "$schema": "https://altinncdn.no/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "id": "summary-1header", + "type": "Summary", + "componentRef": "Header1", + "pageRef": "FormLayout" + + }, + { + "id": "summary-11-orgnr", + "type": "Summary", + "componentRef": "1-1", + "pageRef": "FormLayout" + }, + { + "id": "summary-12-orgname", + "type": "Summary", + "componentRef": "1-2", + "pageRef": "FormLayout" + }, + { + "id": "summary-13-orgadresse", + "type": "Summary", + "componentRef": "1-3-address", + "pageRef": "FormLayout" + }, + { + "id": "summary-14-orgpostcode", + "type": "Summary", + "componentRef": "1-4-postcode", + "pageRef": "FormLayout", + "grid": { + "sm": 4 + } + }, + { + "id": "summary-15-orgpostcity", + "type": "Summary", + "componentRef": "1-5-postcity", + "pageRef": "FormLayout", + "grid": { + "sm": 8 + } + }, + { + "id": "summary-2header", + "type": "Summary", + "componentRef": "Header2", + "pageRef": "FormLayout" + + }, + { + "id": "summary-21-name1", + "type": "Summary", + "componentRef": "2-1", + "pageRef": "FormLayout" + }, + { + "id": "summary-22-email1", + "type": "Summary", + "componentRef": "2-2", + "pageRef": "FormLayout" + }, + { + "id": "summary-23-tlfprefix1", + "type": "Summary", + "componentRef": "2-3Prefix", + "pageRef": "FormLayout", + "grid": { + "sm": 4 + } + }, + { + "id": "summary-23-tlf1", + "type": "Summary", + "componentRef": "2-3", + "pageRef": "FormLayout", + "grid": { + "sm": 8 + } + }, + { + "id": "summary-24-name2", + "type": "Summary", + "componentRef": "2-4", + "pageRef": "FormLayout" + }, + { + "id": "summary-25-email2", + "type": "Summary", + "componentRef": "2-5", + "pageRef": "FormLayout" + }, + { + "id": "summary-26-tlfprefix2", + "type": "Summary", + "componentRef": "2-6Prefix", + "pageRef": "FormLayout", + "grid": { + "sm": 4 + } + }, + { + "id": "summary-26-tlf2", + "type": "Summary", + "componentRef": "I2-6", + "pageRef": "FormLayout", + "grid": { + "sm": 8 + } + }, + { + "id": "summary-16", + "type": "Summary", + "componentRef": "FilOpplasting", + "pageRef": "FormLayout" + }, + { + "id": "summary-4", + "type": "Summary", + "componentRef": "4-0", + "pageRef": "FormLayout" + }, + { + "id": "88b9ba10-cb72-44b8-b218-a724fd9106fb", + "type": "Button", + "textResourceBindings": { + "title": "Send inn" + }, + "dataModelBindings": {} + } + ] + } + } \ No newline at end of file diff --git a/src/AppRef.sln b/src/AppRef.sln index cf2295ba..aad63846 100644 --- a/src/AppRef.sln +++ b/src/AppRef.sln @@ -15,6 +15,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Altinn.App.PlatformServices EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Altinn.App.PlatformServices.Tests", "Altinn.App.PlatformServices.Tests\Altinn.App.PlatformServices.Tests.csproj", "{17D7DCE9-7797-4BC1-B448-D0529FD6FB3D}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Altinn.App.ModelGenerator", "Altinn.App.ModelGenerator\Altinn.App.ModelGenerator.csproj", "{1241191A-8D8A-4CD6-854C-FBA5B99B3302}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Altinn.App.ModelGenerator.Tests", "Altinn.App.ModelGenerator.Tests\Altinn.App.ModelGenerator.Tests.csproj", "{2D7A024B-6E20-4FA8-BE41-B54025A78901}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -45,6 +49,14 @@ Global {17D7DCE9-7797-4BC1-B448-D0529FD6FB3D}.Debug|Any CPU.Build.0 = Debug|Any CPU {17D7DCE9-7797-4BC1-B448-D0529FD6FB3D}.Release|Any CPU.ActiveCfg = Release|Any CPU {17D7DCE9-7797-4BC1-B448-D0529FD6FB3D}.Release|Any CPU.Build.0 = Release|Any CPU + {1241191A-8D8A-4CD6-854C-FBA5B99B3302}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1241191A-8D8A-4CD6-854C-FBA5B99B3302}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1241191A-8D8A-4CD6-854C-FBA5B99B3302}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1241191A-8D8A-4CD6-854C-FBA5B99B3302}.Release|Any CPU.Build.0 = Release|Any CPU + {2D7A024B-6E20-4FA8-BE41-B54025A78901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D7A024B-6E20-4FA8-BE41-B54025A78901}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D7A024B-6E20-4FA8-BE41-B54025A78901}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D7A024B-6E20-4FA8-BE41-B54025A78901}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE