diff --git a/.editorconfig b/.editorconfig index cf04034335..2c5026ec8d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -84,6 +84,9 @@ csharp_prefer_simple_using_statement = true:suggestion csharp_style_namespace_declarations = block_scoped:silent csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +# CS1591: Fehledes XML-Kommentar für öffentlich sichtbaren Typ oder Element +dotnet_diagnostic.CS1591.severity = none + [*.vb] #### Benennungsstile #### diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000..5592328880 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,106 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: + - master + - rc + pull_request: + branches: + - master + - rc + schedule: + - cron: '38 14 * * 4' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + - language: csharp + build-mode: none + - language: javascript-typescript + build-mode: none + # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Add any setup steps before running the `github/codeql-action/init` action. + # This includes steps like installing compilers or runtimes (`actions/setup-node` + # or others). This is typically only required for manual builds. + # - name: Setup runtime (example) + # uses: actions/setup-example@v1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fa31a30854..be21a0378d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,7 +11,8 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + + uses: actions/checkout@v4 - name: Install .NET SDK uses: actions/setup-dotnet@v3 @@ -26,7 +27,9 @@ jobs: upgrade-assistant analyze BExIS%2B%2B.sln --format json > upgrade-report.json - name: Upload report artifact - uses: actions/upload-artifact@v3.1.3 + + uses: actions/upload-artifact@v4 + with: name: upgrade-report path: upgrade-report.json diff --git a/.gitignore b/.gitignore index ab1af9b0af..0ce8a69fca 100644 --- a/.gitignore +++ b/.gitignore @@ -281,3 +281,4 @@ svelte /Console/BExIS.Web.Shell/Areas/RPM/BExIS.Modules.Rpm.UI/Scripts/svelte /_output /Console/BExIS.Web.Shell/Web.config +/Console/BExIS.Web.Shell/Areas/SMM/BExIS.Modules.SMM.UI/Properties/PublishProfiles/FolderProfile.pubxml diff --git a/BExIS++.sln b/BExIS++.sln index 82a7088ecc..0e2a080257 100644 --- a/BExIS++.sln +++ b/BExIS++.sln @@ -230,6 +230,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BExIS.Ext.Orm.NH", "Compone EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BExIS.Modules.Dim.UI", "Console\BExIS.Web.Shell\Areas\DIM\BExIS.Modules.Dim.UI\BExIS.Modules.Dim.UI.csproj", "{9BFFFD11-03C6-47DF-9CC9-F458A9A49377}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SMM", "SMM", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BExIS.Modules.SMM.UI", "Console\BExIS.Web.Shell\Areas\SMM\BExIS.Modules.SMM.UI\BExIS.Modules.SMM.UI.csproj", "{37402CAB-EB81-4D08-8791-8653949C0FEB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PUM", "PUM", "{96384857-88D4-4282-8EFD-FE8FCB0319B7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BExIS.Modules.PUM.UI", "Console\BExIS.Web.Shell\Areas\PUM\BExIS.Modules.PUM.UI\BExIS.Modules.PUM.UI.csproj", "{C1AE3004-853A-4CCF-9099-AE919C6121C8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1486,6 +1494,42 @@ Global {9BFFFD11-03C6-47DF-9CC9-F458A9A49377}.TestServerRelease|Mixed Platforms.Build.0 = TestServerRelease|Any CPU {9BFFFD11-03C6-47DF-9CC9-F458A9A49377}.TestServerRelease|x86.ActiveCfg = TestServerRelease|Any CPU {9BFFFD11-03C6-47DF-9CC9-F458A9A49377}.TestServerRelease|x86.Build.0 = TestServerRelease|Any CPU + {37402CAB-EB81-4D08-8791-8653949C0FEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37402CAB-EB81-4D08-8791-8653949C0FEB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37402CAB-EB81-4D08-8791-8653949C0FEB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {37402CAB-EB81-4D08-8791-8653949C0FEB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {37402CAB-EB81-4D08-8791-8653949C0FEB}.Debug|x86.ActiveCfg = Debug|Any CPU + {37402CAB-EB81-4D08-8791-8653949C0FEB}.Debug|x86.Build.0 = Debug|Any CPU + {37402CAB-EB81-4D08-8791-8653949C0FEB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37402CAB-EB81-4D08-8791-8653949C0FEB}.Release|Any CPU.Build.0 = Release|Any CPU + {37402CAB-EB81-4D08-8791-8653949C0FEB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {37402CAB-EB81-4D08-8791-8653949C0FEB}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {37402CAB-EB81-4D08-8791-8653949C0FEB}.Release|x86.ActiveCfg = Release|Any CPU + {37402CAB-EB81-4D08-8791-8653949C0FEB}.Release|x86.Build.0 = Release|Any CPU + {37402CAB-EB81-4D08-8791-8653949C0FEB}.TestServerRelease|Any CPU.ActiveCfg = TestServerRelease|Any CPU + {37402CAB-EB81-4D08-8791-8653949C0FEB}.TestServerRelease|Any CPU.Build.0 = TestServerRelease|Any CPU + {37402CAB-EB81-4D08-8791-8653949C0FEB}.TestServerRelease|Mixed Platforms.ActiveCfg = TestServerRelease|Any CPU + {37402CAB-EB81-4D08-8791-8653949C0FEB}.TestServerRelease|Mixed Platforms.Build.0 = TestServerRelease|Any CPU + {37402CAB-EB81-4D08-8791-8653949C0FEB}.TestServerRelease|x86.ActiveCfg = TestServerRelease|Any CPU + {37402CAB-EB81-4D08-8791-8653949C0FEB}.TestServerRelease|x86.Build.0 = TestServerRelease|Any CPU + {C1AE3004-853A-4CCF-9099-AE919C6121C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C1AE3004-853A-4CCF-9099-AE919C6121C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1AE3004-853A-4CCF-9099-AE919C6121C8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {C1AE3004-853A-4CCF-9099-AE919C6121C8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {C1AE3004-853A-4CCF-9099-AE919C6121C8}.Debug|x86.ActiveCfg = Debug|Any CPU + {C1AE3004-853A-4CCF-9099-AE919C6121C8}.Debug|x86.Build.0 = Debug|Any CPU + {C1AE3004-853A-4CCF-9099-AE919C6121C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C1AE3004-853A-4CCF-9099-AE919C6121C8}.Release|Any CPU.Build.0 = Release|Any CPU + {C1AE3004-853A-4CCF-9099-AE919C6121C8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {C1AE3004-853A-4CCF-9099-AE919C6121C8}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {C1AE3004-853A-4CCF-9099-AE919C6121C8}.Release|x86.ActiveCfg = Release|Any CPU + {C1AE3004-853A-4CCF-9099-AE919C6121C8}.Release|x86.Build.0 = Release|Any CPU + {C1AE3004-853A-4CCF-9099-AE919C6121C8}.TestServerRelease|Any CPU.ActiveCfg = TestServerRelease|Any CPU + {C1AE3004-853A-4CCF-9099-AE919C6121C8}.TestServerRelease|Any CPU.Build.0 = TestServerRelease|Any CPU + {C1AE3004-853A-4CCF-9099-AE919C6121C8}.TestServerRelease|Mixed Platforms.ActiveCfg = TestServerRelease|Any CPU + {C1AE3004-853A-4CCF-9099-AE919C6121C8}.TestServerRelease|Mixed Platforms.Build.0 = TestServerRelease|Any CPU + {C1AE3004-853A-4CCF-9099-AE919C6121C8}.TestServerRelease|x86.ActiveCfg = TestServerRelease|Any CPU + {C1AE3004-853A-4CCF-9099-AE919C6121C8}.TestServerRelease|x86.Build.0 = TestServerRelease|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1580,6 +1624,10 @@ Global {AF3D9C1F-968A-46DD-86FB-6B45020DF4E3} = {CD986786-CEA1-4C30-983E-169C33600BA6} {C48DCFC5-DF47-4486-A682-5C648F970318} = {CD986786-CEA1-4C30-983E-169C33600BA6} {9BFFFD11-03C6-47DF-9CC9-F458A9A49377} = {F3354AC7-CDA1-44E2-8E85-8DB532ED8C75} + {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {692AFF14-3A57-47D8-912F-093EC8F561C4} + {37402CAB-EB81-4D08-8791-8653949C0FEB} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {96384857-88D4-4282-8EFD-FE8FCB0319B7} = {692AFF14-3A57-47D8-912F-093EC8F561C4} + {C1AE3004-853A-4CCF-9099-AE919C6121C8} = {96384857-88D4-4282-8EFD-FE8FCB0319B7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9B6E4921-8EBA-487D-A098-3E473A0EAC64} diff --git a/CITATION.cff b/CITATION.cff index 4497c053e1..b02b19aff7 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,8 +1,8 @@ cff-version: 1.2.0 message: "If you use BEXIS2 in your research, please cite it using the following metadata." title: "BEXIS2" -version: 4.1.0 -date-released: 15.08.2025 +version: 4.2.1 +date-released: 05.01.2026 authors: - family-names: "Zander" given-names: "Franziska" diff --git a/Components/AAA/BExIS.Security.Services/Subjects/UserManager.cs b/Components/AAA/BExIS.Security.Services/Subjects/UserManager.cs index 87aaa34c1a..43b7330ae3 100644 --- a/Components/AAA/BExIS.Security.Services/Subjects/UserManager.cs +++ b/Components/AAA/BExIS.Security.Services/Subjects/UserManager.cs @@ -125,6 +125,13 @@ public Task DeleteByIdAsync(long userId) //return Task.FromException(new Exception()); return Task.FromResult(false); + // Logins + var loginsRepository = _guow.GetRepository(); + foreach (var login in loginsRepository.Get(l => l.User.Id == userId)) + { + loginsRepository.Delete(login); + } + // EntityPermissions var entityPermissionRepository = _guow.GetRepository(); foreach (var entityPermission in entityPermissionRepository.Get(e => e.Subject.Id == userId)) diff --git a/Components/AAA/BExIS.Security.Services/Utilities/MessageHelper.cs b/Components/AAA/BExIS.Security.Services/Utilities/MessageHelper.cs index 503d11de4f..0702f4e616 100644 --- a/Components/AAA/BExIS.Security.Services/Utilities/MessageHelper.cs +++ b/Components/AAA/BExIS.Security.Services/Utilities/MessageHelper.cs @@ -160,7 +160,7 @@ public static string GetSendRequestMessage(long datasetid, string title, string public static string GetWithdrawRequestHeader(long datasetid, string requester) { - return $"Data request from {requester} for dataset with ID {datasetid} withdrawn"; + return $"Data request from {requester} for dataset with ID {datasetid} withdrawn"; } public static string GetWithdrawRequestMessage(long datasetid, string title, string requester) @@ -173,7 +173,7 @@ public static string GetWithdrawRequestMessage(long datasetid, string title, str public static string GetAcceptRequestHeader(long datasetid, string requester) { - return $"Data request from {requester} for dataset with ID {datasetid} granted"; + return $"Data request from {requester} for dataset with ID {datasetid} granted"; } public static string GetAcceptRequestMessage(long datasetid, string title) @@ -186,7 +186,7 @@ public static string GetAcceptRequestMessage(long datasetid, string title) public static string GetRejectedRequestHeader(long datasetid, string requester) { - return $"Data request from {requester} for dataset with ID {datasetid} rejected"; + return $"Data request from {requester} for dataset with ID {datasetid} rejected"; } public static string GetRejectedRequestMessage(long datasetid, string title) @@ -342,7 +342,7 @@ public static string GetUnsetPublicMessage(string userName, long datasetid, stri public static string GetPushApiStoreHeader(long datasetid, string title) { - return $"Receive data for dataset '{title}' with ID {datasetid}"; + return $"Receive data for dataset '{title}' with ID {datasetid}"; } public static string GetPushApiStoreMessage(long datasetid, string userName, string[] errors = null) @@ -386,7 +386,7 @@ public static string GetPushApiValidateMessage(long datasetid, string userName, public static string GetPushApiUploadSuccessHeader(long datasetid, string title) { - return $"Upload completed for dataset: '{title}' with ID {datasetid}"; + return $"Upload completed for dataset: '{title}' with ID {datasetid}"; } public static string GetPushApiUploadSuccessMessage(long datasetid, string userName) @@ -396,7 +396,7 @@ public static string GetPushApiUploadSuccessMessage(long datasetid, string userN public static string GetPushApiUploadFailHeader(long datasetid, string title) { - return $"Upload was not successful for dataset '{title}' with ID {datasetid}"; + return $"Upload was not successful for dataset '{title}' with ID {datasetid}"; } public static string GetPushApiUploadFailMessage(long datasetid, string userName, string[] errors) diff --git a/Components/App/BExIS.App.Bootstrap/Attributes/BExISAuthorizeAttribute.cs b/Components/App/BExIS.App.Bootstrap/Attributes/BExISAuthorizeAttribute.cs index d1ad0efd4d..0893ef22d7 100644 --- a/Components/App/BExIS.App.Bootstrap/Attributes/BExISAuthorizeAttribute.cs +++ b/Components/App/BExIS.App.Bootstrap/Attributes/BExISAuthorizeAttribute.cs @@ -4,12 +4,12 @@ using BExIS.Security.Services.Authorization; using BExIS.Security.Services.Objects; using BExIS.Security.Services.Subjects; -using BExIS.Utils.Config; using System; using System.Linq; using System.Net; using System.Web; using System.Web.Mvc; +using BExIS.Utils.Config; namespace BExIS.App.Bootstrap.Attributes { @@ -50,9 +50,6 @@ public override void OnAuthorization(AuthorizationContext filterContext) if (!featurePermissionManager.HasAccessAsync(user.Id, feature.Id).Result) { filterContext.SetResponse(HttpStatusCode.Forbidden); - - - } // update jwt cookie diff --git a/Components/App/BExIS.App.Bootstrap/Attributes/CustomValidateAntiForgeryToken.cs b/Components/App/BExIS.App.Bootstrap/Attributes/CustomValidateAntiForgeryToken.cs new file mode 100644 index 0000000000..474f18c611 --- /dev/null +++ b/Components/App/BExIS.App.Bootstrap/Attributes/CustomValidateAntiForgeryToken.cs @@ -0,0 +1,36 @@ +using BExIS.Security.Entities.Requests; +using System.Web.Helpers; +using System.Web.Mvc; + +namespace BExIS.App.Bootstrap.Attributes +{ + public class CustomValidateAntiForgeryToken: FilterAttribute, IAuthorizationFilter + { + public void OnAuthorization(AuthorizationContext filterContext) + { + var request = filterContext.HttpContext.Request; + + if (filterContext.HttpContext.Request.HttpMethod == "POST") + { + var cookieToken = request.Cookies[AntiForgeryConfig.CookieName]?.Value; + + // check for token in form data + var formToken = request.Form["__RequestVerificationToken"]; + + // check header for post from javascript + + if (formToken==null) + { + formToken = request.Headers["__RequestVerificationToken"]; + } + + if (cookieToken != null) + { + AntiForgery.Validate(cookieToken, formToken); + } + + //AntiForgery.Validate(); + } + } + } +} diff --git a/Components/App/BExIS.App.Bootstrap/Attributes/MinCapacity.cs b/Components/App/BExIS.App.Bootstrap/Attributes/MinCapacity.cs deleted file mode 100644 index 322b38f440..0000000000 --- a/Components/App/BExIS.App.Bootstrap/Attributes/MinCapacity.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections; -using System.ComponentModel.DataAnnotations; - -public class MinCapacityAttribute : ValidationAttribute -{ - private readonly int _minSize; - - public MinCapacityAttribute(int minSize) - { - _minSize = minSize; - } - - protected override ValidationResult IsValid(object value, ValidationContext validationContext) - { - if (value is ICollection collection) - { - if (collection.Count >= _minSize) - { - return ValidationResult.Success; - } - else - { - return new ValidationResult($"The collection must contain at least {_minSize} elements."); - } - } - - return new ValidationResult("This attribute can only be used on ICollection types."); - } -} \ No newline at end of file diff --git a/Components/App/BExIS.App.Bootstrap/Attributes/MinCapacityAttribute.cs b/Components/App/BExIS.App.Bootstrap/Attributes/MinCapacityAttribute.cs new file mode 100644 index 0000000000..40ea64e993 --- /dev/null +++ b/Components/App/BExIS.App.Bootstrap/Attributes/MinCapacityAttribute.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections; +using System.ComponentModel.DataAnnotations; +using System.Linq; + +namespace BExIS.App.Bootstrap.Attributes +{ + public class MinCapacityAttribute : ValidationAttribute + { + private readonly int _minSize; + + public MinCapacityAttribute(int minSize) + { + _minSize = minSize; + } + + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + if (value is ICollection collection) + { + if (collection.Cast().Any(item => item is string s && string.IsNullOrEmpty(s))) + { + return new ValidationResult("Collection cannot contain null or empty strings."); + } + + if (collection.Count >= _minSize) + { + return ValidationResult.Success; + } + else + { + return new ValidationResult($"The collection must contain at least {_minSize} elements."); + } + } + + return new ValidationResult("This attribute can only be used on ICollection types."); + } + } +} + diff --git a/Components/App/BExIS.App.Bootstrap/Attributes/NoNullOrEmptyItemsAttribute.cs b/Components/App/BExIS.App.Bootstrap/Attributes/NoNullOrEmptyItemsAttribute.cs new file mode 100644 index 0000000000..3c69645341 --- /dev/null +++ b/Components/App/BExIS.App.Bootstrap/Attributes/NoNullOrEmptyItemsAttribute.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BExIS.App.Bootstrap.Attributes +{ + public class NoNullOrEmptyItemsAttribute : ValidationAttribute + { + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + if (value is ICollection collection) + { + if (collection.Cast().Any(item => item is string s && string.IsNullOrEmpty(s))) + return new ValidationResult("Collection cannot contain null or empty strings."); + + return ValidationResult.Success; + } + + return new ValidationResult("This attribute can only be used on ICollection types."); + } + } +} diff --git a/Components/App/BExIS.App.Bootstrap/BExIS.App.Bootstrap.csproj b/Components/App/BExIS.App.Bootstrap/BExIS.App.Bootstrap.csproj index c2cdd1832b..0ac9743cfa 100644 --- a/Components/App/BExIS.App.Bootstrap/BExIS.App.Bootstrap.csproj +++ b/Components/App/BExIS.App.Bootstrap/BExIS.App.Bootstrap.csproj @@ -109,8 +109,10 @@ - + + + @@ -130,7 +132,7 @@ BExIS.Ext.Services - {6ead7d02-02f7-42ff-85e4-90bb892d3846} + {6EAD7D02-02F7-42FF-85E4-90BB892D3846} BExIS.Utils.Config diff --git a/Components/App/BExIS.App.Testing/BExIS.App.Testing.csproj b/Components/App/BExIS.App.Testing/BExIS.App.Testing.csproj index d7c7f2497f..d99220c3ab 100644 --- a/Components/App/BExIS.App.Testing/BExIS.App.Testing.csproj +++ b/Components/App/BExIS.App.Testing/BExIS.App.Testing.csproj @@ -103,7 +103,7 @@ - {6ead7d02-02f7-42ff-85e4-90bb892d3846} + {6EAD7D02-02F7-42FF-85E4-90BB892D3846} BExIS.Utils.Config diff --git a/Components/DLM/BExIS.Dlm.Entities/BExIS.Dlm.Entities.csproj b/Components/DLM/BExIS.Dlm.Entities/BExIS.Dlm.Entities.csproj index 635efcf9c0..2f6192e4bd 100644 --- a/Components/DLM/BExIS.Dlm.Entities/BExIS.Dlm.Entities.csproj +++ b/Components/DLM/BExIS.Dlm.Entities/BExIS.Dlm.Entities.csproj @@ -62,6 +62,8 @@ + + diff --git a/Components/DLM/BExIS.Dlm.Entities/Curation/CurationEntry.cs b/Components/DLM/BExIS.Dlm.Entities/Curation/CurationEntry.cs new file mode 100644 index 0000000000..226919ebbe --- /dev/null +++ b/Components/DLM/BExIS.Dlm.Entities/Curation/CurationEntry.cs @@ -0,0 +1,119 @@ +using BExIS.Dlm.Entities.Data; +using BExIS.Security.Entities.Subjects; +using System; +using System.Collections.Generic; +using System.Linq; +using Vaiona.Entities.Common; + +namespace BExIS.Dlm.Entities.Curation +{ + public class CurationEntry:BaseEntity + { + public CurationEntry() + { + // Initialize properties with empty values + Id = 0; + Topic = string.Empty; + Type = CurationEntryType.None; + Dataset = null; + Name = string.Empty; + Description = string.Empty; + Solution = string.Empty; + Position = 0; + Source = string.Empty; + Notes = new List(); + CreationDate = DateTime.MinValue; + Creator = null; + UserIsDone = false; + IsApproved = false; + LastChangeDatetime_User = DateTime.MinValue; + LastChangeDatetime_Curator = DateTime.MinValue; + } + + public CurationEntry(string topic, CurationEntryType type, Dataset dataset, string name, string description, string solution, int position, string source, IEnumerable notes, DateTime creationDate, User creator, bool userIsDone, bool isApproved, DateTime lastChangeDatetime_User, DateTime lastChangeDatetime_Curator) + { + Id = 0; + Topic = topic; + Type = type; + Dataset = dataset; + Name = name; + Description = description; + Solution = solution; + Position = position; + Source = source; + Notes = notes; + CreationDate = creationDate; + Creator = creator; + UserIsDone = userIsDone; + IsApproved = isApproved; + LastChangeDatetime_User = lastChangeDatetime_User; + LastChangeDatetime_Curator = lastChangeDatetime_Curator; + } + + public virtual string Topic { get; set; } + public virtual CurationEntryType Type { get; set; } + public virtual Dataset Dataset { get; set; } + public virtual string Name { get; set; } + public virtual string Description { get; set; } + public virtual string Solution { get; set; } + public virtual int Position { get; set; } + public virtual string Source { get; set; } + public virtual IEnumerable Notes { get; set; } + public virtual DateTime CreationDate { get; set; } + public virtual User Creator { get; set; } + public virtual bool UserIsDone { get; set; } + public virtual bool IsApproved { get; set; } + public virtual DateTime LastChangeDatetime_User { get; set; } + public virtual DateTime LastChangeDatetime_Curator { get; set; } + + public static CurationUserType GetCurationUserType(User user, string curationGroupName) + { + if (user.Groups.Any(g => g.Name.Equals(curationGroupName, StringComparison.CurrentCultureIgnoreCase))) + { + return CurationUserType.Curator; + } + return CurationUserType.User; + } + } + + #region curation types + + public enum CurationEntryType + { + None, + StatusEntryItem, + GeneralEntryItem, + MetadataEntryItem, + PrimaryDataEntryItem, + DatastrutcureEntryItem, + LinkEntryItem, + AttachmentEntryItem + } + + public class MetadataEntryItem + { + public string Name { get; set; } + public string Value { get; set; } + public string Path { get; set; } + public long Id { get; set; } + + } + + public class PrimaryDataEntryItem + { + public string Column { get; set; } + public int Row { get; set; } + public string Value { get; set; } + + } + + public class DataStructureEntryItem + { + public string VarName { get; set; } + public string Type { get; set; } + public string Value { get; set; } + + } + + #endregion +} diff --git a/Components/DLM/BExIS.Dlm.Entities/Curation/CurationNote.cs b/Components/DLM/BExIS.Dlm.Entities/Curation/CurationNote.cs new file mode 100644 index 0000000000..c2b5484867 --- /dev/null +++ b/Components/DLM/BExIS.Dlm.Entities/Curation/CurationNote.cs @@ -0,0 +1,48 @@ +using BExIS.Security.Entities.Subjects; +using System; +using Vaiona.Entities.Common; + +namespace BExIS.Dlm.Entities.Curation +{ + public class CurationNote: BaseEntity + { + public virtual CurationUserType UserType { get; set; } + public virtual DateTime CreationDate { get; set; } + public virtual string Comment { get; set; } + public virtual User User { get; set; } + + public CurationNote() + { + Id = 0; + UserType = CurationUserType.User; + CreationDate = DateTime.Now; + Comment = ""; + User = null; + } + + public CurationNote(int id, CurationUserType userType, DateTime creationDate, string comment, User user) + { + Id = id; + UserType = userType; + CreationDate = creationDate; + Comment = comment; + User = user; + } + + public CurationNote(User user, string comment, CurationUserType userType) + { + Id = 0; + UserType = userType; + CreationDate = DateTime.Now; + Comment = comment; + User = user; + } + + } + + public enum CurationUserType + { + User, + Curator + } +} diff --git a/Components/DLM/BExIS.Dlm.Entities/Data/VariableValue.cs b/Components/DLM/BExIS.Dlm.Entities/Data/VariableValue.cs index addadf9251..b24aa759fc 100644 --- a/Components/DLM/BExIS.Dlm.Entities/Data/VariableValue.cs +++ b/Components/DLM/BExIS.Dlm.Entities/Data/VariableValue.cs @@ -61,7 +61,7 @@ public VariableInstance Variable { get { - if (this.Tuple.DatasetVersion.Dataset.DataStructure.Self is StructuredDataStructure) + if (this.Tuple.DatasetVersion.Dataset.DataStructure != null) { VariableInstance u = (this.Tuple.DatasetVersion.Dataset.DataStructure.Self as StructuredDataStructure).Variables .Where(p => p.Id.Equals(this.VariableId)) diff --git a/Components/DLM/BExIS.Dlm.Orm.NH/BExIS.Dlm.Orm.NH.csproj b/Components/DLM/BExIS.Dlm.Orm.NH/BExIS.Dlm.Orm.NH.csproj index 0ee3b41a3f..dc82b66e33 100644 --- a/Components/DLM/BExIS.Dlm.Orm.NH/BExIS.Dlm.Orm.NH.csproj +++ b/Components/DLM/BExIS.Dlm.Orm.NH/BExIS.Dlm.Orm.NH.csproj @@ -66,6 +66,14 @@ PreserveNewest + + Designer + PreserveNewest + + + Designer + PreserveNewest + PreserveNewest diff --git a/Components/DLM/BExIS.Dlm.Orm.NH/Mappings/Default/Curation/CurationEntry.hbm.xml b/Components/DLM/BExIS.Dlm.Orm.NH/Mappings/Default/Curation/CurationEntry.hbm.xml new file mode 100644 index 0000000000..a38af9dcd4 --- /dev/null +++ b/Components/DLM/BExIS.Dlm.Orm.NH/Mappings/Default/Curation/CurationEntry.hbm.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Components/DLM/BExIS.Dlm.Orm.NH/Mappings/Default/Curation/CurationNote.hbm.xml b/Components/DLM/BExIS.Dlm.Orm.NH/Mappings/Default/Curation/CurationNote.hbm.xml new file mode 100644 index 0000000000..50e0c29249 --- /dev/null +++ b/Components/DLM/BExIS.Dlm.Orm.NH/Mappings/Default/Curation/CurationNote.hbm.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Components/DLM/BExIS.Dlm.Services/BExIS.Dlm.Services.csproj b/Components/DLM/BExIS.Dlm.Services/BExIS.Dlm.Services.csproj index 03e628526f..f0aedfb538 100644 --- a/Components/DLM/BExIS.Dlm.Services/BExIS.Dlm.Services.csproj +++ b/Components/DLM/BExIS.Dlm.Services/BExIS.Dlm.Services.csproj @@ -136,6 +136,7 @@ + diff --git a/Components/DLM/BExIS.Dlm.Services/Curation/CurationManager.cs b/Components/DLM/BExIS.Dlm.Services/Curation/CurationManager.cs new file mode 100644 index 0000000000..23c24ed78e --- /dev/null +++ b/Components/DLM/BExIS.Dlm.Services/Curation/CurationManager.cs @@ -0,0 +1,253 @@ +using BExIS.Dlm.Entities.Curation; +using BExIS.Dlm.Entities.Data; +using BExIS.Security.Entities.Subjects; +using System; +using System.Collections.Generic; +using System.Linq; +using Vaiona.Persistence.Api; + +namespace BExIS.Dlm.Services.Curation +{ + public class CurationManager : IDisposable + { + private readonly IUnitOfWork _guow; + private bool _isDisposed; + + public CurationManager() + { + _guow = this.GetIsolatedUnitOfWork(); + + CurationEntryRepository = _guow.GetReadOnlyRepository(); + } + + ~CurationManager() + { + Dispose(true); + } + + public IQueryable CurationEntries => CurationEntryRepository.Query(); + + public IReadOnlyRepository CurationEntryRepository { get; } + + private static void FixPositions(IEnumerable entries, CurationEntry entry) + { + if (entry.Type == CurationEntryType.StatusEntryItem) return; + + if (entry.Position <= 0) throw new ArgumentException("Position must be greater than 0."); + + // take all entries of the same type + var entryList = entries.Where(e => e.Type == entry.Type) + .OrderBy(e => e.Topic + e.Name + e.Description).OrderBy(e => e.Position).ToList(); + + if (entry.Position <= entryList.Count) + { + // place entry at the correct place inside the list + entryList.Remove(entry); + entryList.Insert(entry.Position - 1, entry); + } + + for (var i = 0; i < entryList.Count; i++) + { + // override every position according to the new list + entryList[i].Position = i + 1; + } + } + + public CurationEntry Create(string topic, CurationEntryType type, long datasetId, string name, string description, string solution, int position, string source, IEnumerable notes, bool userIsDone, bool isApproved, User user, bool userIsCurator) + { + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name), "name is empty but is required."); + if (string.IsNullOrEmpty(source)) throw new ArgumentNullException(nameof(source), "source is empty but is required."); + + if (type == CurationEntryType.StatusEntryItem && position != 0) throw new ArgumentException("Position for StatusEntryItem must be 0."); + + using (var uow = this.GetUnitOfWork()) + { + var repoEntries = uow.GetRepository(); + var repoDataset = uow.GetRepository(); + var repoUser = uow.GetRepository(); + + // Re-fetch user from the current session and ensure Groups are loaded + var loadedUser = repoUser.Get(user.Id); + // Force loading of Groups collection + var _ = loadedUser.Groups.Count; + + // set last change date time + if (!userIsCurator) + throw new UnauthorizedAccessException("Only curators are allowed to create curation entries."); + + var dataset = repoDataset.Get(datasetId); + + // check if dataset exists + if (dataset == null) + throw new ArgumentException($"Dataset with id {datasetId} does not exist.", nameof(datasetId)); + + CurationEntry curationEntry = new CurationEntry( + topic, + type, + dataset, + name, + description, + solution, + position, + source, + notes, + DateTime.Now, + loadedUser, + userIsDone, + isApproved, + lastChangeDatetime_User: DateTime.MinValue, + lastChangeDatetime_Curator: DateTime.Now + ); + + repoEntries.Put(curationEntry); + + // check/fix positions + var datasetEntries = repoEntries.Get(e => e.Dataset == dataset); + if (type != CurationEntryType.StatusEntryItem) + { + FixPositions(datasetEntries, curationEntry); + } + + uow.Commit(); + + return curationEntry; + } + } + public CurationEntry Update(long id, string topic, CurationEntryType type, string name, string description, string solution, int position, string source, IEnumerable notes, bool userIsDone, bool isApproved, User user, bool userIsCurator) + { + if (id <= 0) throw new ArgumentException("id must be greater than 0."); + + if (type == CurationEntryType.StatusEntryItem && position != 0) throw new ArgumentException("Position for StatusEntryItem must be 0."); + if (type != CurationEntryType.StatusEntryItem && position <= 0) throw new ArgumentException("Position for none StatusEntryItem must be greater than 0."); + + using (var uow = this.GetUnitOfWork()) + { + var repoEntries = uow.GetRepository(); + var repoNotes = uow.GetRepository(); + var repoUser = uow.GetRepository(); + + // Re-fetch user from the current session and ensure Groups are loaded + var loadedUser = repoUser.Get(user.Id); + // Force loading of Groups collection + var _ = loadedUser.Groups.Count; + + var merged = repoEntries.Get(id); + + var currentNotes = merged.Notes.ToList(); + + var incomingIds = new HashSet(notes.Select(n => n.Id)); + incomingIds.Remove(0); + var deletedNotes = merged.Notes.Where(n => !incomingIds.Contains(n.Id)).ToList(); + + var userType = CurationUserType.User; + if (userIsCurator) + userType = CurationUserType.Curator; + + + foreach (var note in deletedNotes) + { + if (loadedUser.Id != note.User.Id) continue; // do not delete notes from other users + currentNotes.Remove(note); + repoNotes.Delete(note); + } + + foreach (var incomingNote in notes) + { + var existingNote = currentNotes.FirstOrDefault(n => n.Id == incomingNote.Id); + if (incomingNote.Id == 0 || existingNote == null) + { + var newNote = new CurationNote(loadedUser, incomingNote.Comment, userType); + currentNotes.Add(newNote); + } + else + { + if (loadedUser.Id != existingNote.User.Id) continue; // do not change notes from other users + existingNote.Comment = incomingNote.Comment; + } + } + + merged.Notes = currentNotes; + + var changed = false; + + if (userIsCurator) + { + changed = topic != merged.Topic || + type != merged.Type || + name != merged.Name || + description != merged.Description || + solution != merged.Solution || + source != merged.Source || + userIsDone != merged.UserIsDone || + isApproved != merged.IsApproved; + merged.Topic = topic; + merged.Type = type; + merged.Name = name; + merged.Description = description; + merged.Solution = solution; + merged.Source = source; + merged.UserIsDone = userIsDone; + merged.IsApproved = isApproved; + if (changed) + merged.LastChangeDatetime_Curator = DateTime.Now; + } + else + { + changed = userIsDone != merged.UserIsDone; + merged.UserIsDone = userIsDone; + if (!isApproved) + { + changed |= isApproved != merged.IsApproved; + merged.IsApproved = isApproved; + } + if (changed) + merged.LastChangeDatetime_User = DateTime.Now; + } + + + // check/fix positions + if (merged.Position != position) + { + merged.Position = position; + var datasetEntries = repoEntries.Get(e => e.Dataset == merged.Dataset); + FixPositions(datasetEntries, merged); + } + + //repoEntries.Put(merged); + uow.Commit(); + + return merged; + } + } + + + public void Delete(long id) + { + using (var uow = this.GetUnitOfWork()) + { + var repo = uow.GetRepository(); + var curationEntry = repo.Get(id); + repo.Delete(curationEntry); + uow.Commit(); + } + } + + + public void Dispose() + { + Dispose(true); + } + + protected void Dispose(bool disposing) + { + if (!_isDisposed) + { + if (disposing) + { + _guow?.Dispose(); + _isDisposed = true; + } + } + } + } +} diff --git a/Components/DLM/BExIS.Dlm.Services/Data/DatasetManager.cs b/Components/DLM/BExIS.Dlm.Services/Data/DatasetManager.cs index 49ab1a1554..a4335230b7 100644 --- a/Components/DLM/BExIS.Dlm.Services/Data/DatasetManager.cs +++ b/Components/DLM/BExIS.Dlm.Services/Data/DatasetManager.cs @@ -1254,7 +1254,7 @@ public DataTable GetLatestDatasetVersionTuples(long datasetId, int pageNumber, i // should use the fallback method, but DatasetConvertor class must be merged with OutputDataManager and SearchUIHelper claases first. var version = this.GetDatasetLatestVersion(datasetId); var tuples = getDatasetVersionEffectiveTuples(version, pageNumber, pageSize, false); // the false, causes the method to use a scoped sesssion and keep it alive further processings that aredone later on the tuples - if (version.Dataset.DataStructure.Self is StructuredDataStructure) + if (version.Dataset.DataStructure!=null) { DataTable table = convertDataTuplesToDataTable(tuples, version, (StructuredDataStructure)version.Dataset.DataStructure.Self); return table; @@ -1268,7 +1268,7 @@ public DataTable GetDatasetVersionTuples(long versionId, int pageNumber, int pag // should use the fallback method, but DatasetConvertor class must be merged with OutputDataManager and SearchUIHelper claases first. var version = this.GetDatasetVersion(versionId); var tuples = getDatasetVersionEffectiveTuples(version, pageNumber, pageSize, false); // the false, causes the method to use a scoped sesssion and keep it alive further processings that aredone later on the tuples - if (version.Dataset.DataStructure.Self is StructuredDataStructure) + if (version.Dataset.DataStructure != null) { DataTable table = convertDataTuplesToDataTable(tuples, version, (StructuredDataStructure)version.Dataset.DataStructure.Self); return table; @@ -1510,7 +1510,7 @@ public List GetDatasetLatestVersions(List datasetIds, boo /// /// The identifiers of the dataset whose their latest versions is requested /// The list of the latest versions of the deleted datasets - private DatasetVersion getDeletedDatasetLatestVersion(long datasetId) + public DatasetVersion GetDeletedDatasetLatestVersion(long datasetId) { using (IUnitOfWork uow = this.GetUnitOfWork()) { @@ -3361,7 +3361,7 @@ private void createMaterializedView(long datasetId) { var datasetRepo = uow.GetReadOnlyRepository(); Dataset ds = datasetRepo.Get(datasetId); - if (ds.DataStructure != null && ds.DataStructure.Self is StructuredDataStructure) + if (ds.DataStructure != null && ds.DataStructure!= null) { StructuredDataStructure sds = (StructuredDataStructure)ds.DataStructure.Self; if (sds.Variables != null && sds.Variables.Count() > 0) diff --git a/Components/DLM/BExIS.Dlm.Services/DataStructure/DataStructureManager.cs b/Components/DLM/BExIS.Dlm.Services/DataStructure/DataStructureManager.cs index 96d07f4add..52f68153d5 100644 --- a/Components/DLM/BExIS.Dlm.Services/DataStructure/DataStructureManager.cs +++ b/Components/DLM/BExIS.Dlm.Services/DataStructure/DataStructureManager.cs @@ -107,10 +107,8 @@ public StructuredDataStructure CreateStructuredDataStructure(string name, string using (IUnitOfWork uow = this.GetUnitOfWork()) { IRepository repo = uow.GetRepository(); - IRepository sRepo = uow.GetRepository(); if ( - (repo.Query(p => p.Name.ToLower() == name.ToLower()).Count() <= 0) && - (sRepo.Query(p => p.Name.ToLower() == name.ToLower()).Count() <= 0) + (repo.Query(p => p.Name.ToLower() == name.ToLower()).Count() <= 0) ) { StructuredDataStructure e = new StructuredDataStructure() diff --git a/Components/DLM/BExIS.Dlm.Services/MetadataStructure/MetadataPackageManager.cs b/Components/DLM/BExIS.Dlm.Services/MetadataStructure/MetadataPackageManager.cs index 4efff6b59b..1663427d25 100644 --- a/Components/DLM/BExIS.Dlm.Services/MetadataStructure/MetadataPackageManager.cs +++ b/Components/DLM/BExIS.Dlm.Services/MetadataStructure/MetadataPackageManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; +using System.Xml; using Vaiona.Persistence.Api; namespace BExIS.Dlm.Services.MetadataStructure @@ -128,7 +129,7 @@ public MetadataPackage Update(MetadataPackage entity) #region Associations - public MetadataAttributeUsage AddMetadataAtributeUsage(MetadataPackage package, MetadataAttribute attribute, string label, string description, int minCardinality, int maxCardinality, string defaultValue, string fixedValue) + public MetadataAttributeUsage AddMetadataAtributeUsage(MetadataPackage package, MetadataAttribute attribute, string label, string description, int minCardinality, int maxCardinality, string defaultValue, string fixedValue, XmlDocument extra = null ) { Contract.Requires(package != null && package.Id >= 0); Contract.Requires(attribute != null && attribute.Id >= 0); @@ -166,6 +167,10 @@ select v DefaultValue = defaultValue, FixedValue = fixedValue }; + + if (extra != null && !string.IsNullOrEmpty(extra.OuterXml)) + usage.Extra = extra; + package.MetadataAttributeUsages.Add(usage); attribute.UsedIn.Add(usage); diff --git a/Components/DLM/BExIS.Dlm.Tests/BExIS.Dlm.Tests.csproj b/Components/DLM/BExIS.Dlm.Tests/BExIS.Dlm.Tests.csproj index 531cbac7f2..204b99e14c 100644 --- a/Components/DLM/BExIS.Dlm.Tests/BExIS.Dlm.Tests.csproj +++ b/Components/DLM/BExIS.Dlm.Tests/BExIS.Dlm.Tests.csproj @@ -67,6 +67,7 @@ ..\..\..\packages\Iesi.Collections.4.0.4\lib\net461\Iesi.Collections.dll + ..\..\..\packages\Unity.4.0.1\lib\net45\Microsoft.Practices.Unity.dll @@ -204,6 +205,7 @@ + diff --git a/Components/DLM/BExIS.Dlm.Tests/Helpers/DatasetHelper.cs b/Components/DLM/BExIS.Dlm.Tests/Helpers/DatasetHelper.cs index e285308f64..752e5f5f15 100644 --- a/Components/DLM/BExIS.Dlm.Tests/Helpers/DatasetHelper.cs +++ b/Components/DLM/BExIS.Dlm.Tests/Helpers/DatasetHelper.cs @@ -392,7 +392,7 @@ public Dataset UpdateAnyTupleForDataset(Dataset dataset, StructuredDataStructure } } - public List GetUpdatedDatatuples(DatasetVersion datasetVersion, StructuredDataStructure dataStructure, DatasetManager datasetManager) + public List GetUpdatedDatatuples(DatasetVersion datasetVersion, StructuredDataStructure dataStructure, DatasetManager datasetManager, int count = 0) { datasetVersion.Should().NotBeNull(); var dataset = datasetVersion.Dataset; @@ -401,11 +401,17 @@ public List GetUpdatedDatatuples(DatasetVersion datasetVersion, Struc try { - var datatuples = datasetManager.GetDataTuples(datasetVersion.Id); + List datatuples = new List(); + if(count == 0) + datatuples = datasetManager.GetDataTuples(datasetVersion.Id); + else + datatuples = datasetManager.GetDataTuples(datasetVersion.Id).Take(count).ToList(); + List editedTuples = new List(); foreach (var dataTuple in datatuples) { + dataTuple.Materialize(); var vv = dataTuple.VariableValues.Where(v => v.VariableId.Equals(dataStructure.Variables.Skip(4).First().Id)).FirstOrDefault(); @@ -416,6 +422,7 @@ public List GetUpdatedDatatuples(DatasetVersion datasetVersion, Struc //dataTuple.XmlVariableValues.Should().NotBeNull(); editedTuples.Add((DataTuple)dataTuple); + } return editedTuples; diff --git a/Components/DLM/BExIS.Dlm.Tests/Services/Curation/CurationEntryTests.cs b/Components/DLM/BExIS.Dlm.Tests/Services/Curation/CurationEntryTests.cs new file mode 100644 index 0000000000..f13d9955c3 --- /dev/null +++ b/Components/DLM/BExIS.Dlm.Tests/Services/Curation/CurationEntryTests.cs @@ -0,0 +1,184 @@ +using BExIS.App.Testing; +using BExIS.Dlm.Entities.Curation; +using BExIS.Dlm.Entities.Data; +using BExIS.Dlm.Entities.DataStructure; +using BExIS.Dlm.Services.Administration; +using BExIS.Dlm.Services.Curation; +using BExIS.Dlm.Services.Data; +using BExIS.Dlm.Services.MetadataStructure; +using BExIS.Dlm.Tests.Helpers; +using BExIS.Security.Entities.Subjects; +using BExIS.Security.Services.Subjects; +using BExIS.Utils.Config; +using BExIS.Utils.NH.Querying; +using FluentAssertions; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using Vaiona.Persistence.Api; + +namespace BExIS.Dlm.Tests.Services.Curation +{ + public class CurationEntryTests + { + private TestSetupHelper helper = null; + + [OneTimeSetUp] + public void OneTimeSetUp() + { + helper = new TestSetupHelper(WebApiConfig.Register, false); + + using (UserManager userManager = new UserManager()) + using (GroupManager groupManager = new GroupManager()) + { + Group group = new Group(); + group.Name = "curator"; + group.Description = "curators group"; + groupManager.CreateAsync(group); + + + + User admin = new User() + { + Name = "Admin", + DisplayName = "Admin", + Email = "bexis2-support@uni-jena.de" + }; + + userManager.CreateAsync(admin).Wait(); + + admin = userManager.FindByNameAsync("Admin").Result; + group = groupManager.FindByNameAsync("curator").Result; + + userManager.AddToRoleAsync(admin, group.Name).Wait(); + + + + } + } + + [SetUp] + public void SetUp() + { + var dsHelper = new DatasetHelper(); + + } + + [TearDown] + public void TearDown() + { + + + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + using (var uow = this.GetUnitOfWork()) + { + var repo = uow.GetRepository(); + + var l = repo.Query().ToList(); + foreach (var item in l) + { + repo.Delete(item); + } + uow.Commit(); + + var usersRepo = uow.GetRepository(); + + var u = usersRepo.Query().ToList(); + foreach (var item in u) + { + usersRepo.Delete(item); + } + + var groupsRepo = uow.GetRepository(); + + var g = groupsRepo.Query().ToList(); + foreach (var item in g) + { + groupsRepo.Delete(item); + } + uow.Commit(); + } + + var dsHelper = new DatasetHelper(); + dsHelper.PurgeAllDatasets(); + dsHelper.PurgeAllDataStructures(); + dsHelper.PurgeAllResearchPlans(); + + } + + [Test()] + public void Create_valid_CurationEntry() + { + + //act + using (var curationEntryManager = new CurationManager()) + using (var userManager = new UserManager()) + { + + //Arrange + var dsHelper = new DatasetHelper(); + var ds = dsHelper.CreateDataset(); + var user = userManager.Users.FirstOrDefault(); + + var curationEntry = curationEntryManager.Create( + "Test Topic", + CurationEntryType.None, + ds.Id, "Test Name", "Test Description", "Test Solution", 1, "Test Source", new List(), false, false, user, true); + + + //Assert + curationEntry.Should().NotBeNull(); + curationEntry.Id.Should().BeGreaterThan(0); + curationEntry.Topic.Should().Be("Test Topic"); + curationEntry.Type.Should().Be(CurationEntryType.None); + curationEntry.Dataset.Should().Be(ds); + curationEntry.Name.Should().Be("Test Name"); + curationEntry.Description.Should().Be("Test Description"); + curationEntry.Solution.Should().Be("Test Solution"); + curationEntry.Position.Should().Be(1); + curationEntry.Source.Should().Be("Test Source"); + curationEntry.Notes.Should().NotBeNull(); + + curationEntry.UserIsDone.Should().BeFalse(); + curationEntry.IsApproved.Should().BeFalse(); + } + } + + [Test()] + public void Delete_valid_CurationEntry() + { + + //act + using (var curationEntryManager = new CurationManager()) + using (var userManager = new UserManager()) + { + + //Arrange + var dsHelper = new DatasetHelper(); + var ds = dsHelper.CreateDataset(); + var user = userManager.Users.FirstOrDefault(); + + var curationEntry = curationEntryManager.Create( + "Test Topic", + CurationEntryType.None, + ds.Id, "Test Name", "Test Description", "Test Solution", 1, "Test Source", new List(), false, false, user, true); + + + //Assert + curationEntry.Should().NotBeNull(); + + curationEntryManager.Delete(curationEntry.Id); + + var curationEntryDeleted = curationEntryManager.CurationEntries.Where(c=> c.Id == curationEntry.Id); + + curationEntryDeleted.Should().BeEmpty(); + + } + } + } +} \ No newline at end of file diff --git a/Components/DLM/BExIS.Dlm.Tests/Services/Data/DatasetManagerTests.cs b/Components/DLM/BExIS.Dlm.Tests/Services/Data/DatasetManagerTests.cs index addf0e4b37..9f922ae22a 100644 --- a/Components/DLM/BExIS.Dlm.Tests/Services/Data/DatasetManagerTests.cs +++ b/Components/DLM/BExIS.Dlm.Tests/Services/Data/DatasetManagerTests.cs @@ -804,7 +804,7 @@ public void UpdateSingleValueInMetadata_valid_updatedValue() var version = dm.GetDatasetLatestVersion(dataset.Id); // Act //string xpath = "Metadata/Basic/BasicType/alternateIdentifier/alternateIdentifierType"; - string xpath = "Metadata/Basic/BasicType/DatasetGUID/DatasetGUIDType"; + string xpath = "Metadata/Basic/BasicType/DatasetGUID/String"; string value = "new doi"+DateTime.Now.ToString(); dm.UpdateSingleValueInMetadata(version.Id, xpath, value); @@ -840,7 +840,7 @@ public void UpdateValueInMetadata_valid_updatedValue() var version = dm.GetDatasetLatestVersion(dataset.Id); // Act //string xpath = "Metadata/Basic/BasicType/alternateIdentifier/alternateIdentifierType"; - string xpath = "Metadata/Basic/BasicType/DatasetGUID/DatasetGUIDType"; + string xpath = "Metadata/Basic/BasicType/DatasetGUID/String"; string value = "new doi" + DateTime.Now.ToString(); string value2 = "new doi new"; @@ -882,7 +882,7 @@ public void UpdateValueInMetadata_lastValueEmpty_updatedValueFromLast() var version = dm.GetDatasetLatestVersion(dataset.Id); // Act //string xpath = "Metadata/Basic/BasicType/alternateIdentifier/alternateIdentifierType"; - string xpath = "Metadata/Basic/BasicType/DatasetGUID/DatasetGUIDType"; + string xpath = "Metadata/Basic/BasicType/DatasetGUID/String"; string value = ""; string value2 = "new doi new"; diff --git a/Components/DLM/BExIS.Dlm.Tests/Services/Data/DatasetManager_EditDatasetVersionTests.cs b/Components/DLM/BExIS.Dlm.Tests/Services/Data/DatasetManager_EditDatasetVersionTests.cs index de85940cc1..fe84678e69 100644 --- a/Components/DLM/BExIS.Dlm.Tests/Services/Data/DatasetManager_EditDatasetVersionTests.cs +++ b/Components/DLM/BExIS.Dlm.Tests/Services/Data/DatasetManager_EditDatasetVersionTests.cs @@ -196,7 +196,7 @@ public void EditDatasetVersion_UpdateAllDataTuples_SameNumberOfDatatuples(int pr datatupleFromDatabaseIds = datasetManager.GetDatasetVersionEffectiveTupleIds(latest); //get updated tuples as incoming datatuples - incoming = dsHelper.GetUpdatedDatatuples(latest, dataset.DataStructure as StructuredDataStructure, datasetManager); + incoming = dsHelper.GetUpdatedDatatuples(latest, dataset.DataStructure as StructuredDataStructure, datasetManager, 10); //because of updateing all datatuples the incoming number is should be equal then the existing one expectedCount = incoming.Count; diff --git a/Components/IO/BExIS.IO.DataType.DisplayPattern/DataTypeDisplayPattern.cs b/Components/IO/BExIS.IO.DataType.DisplayPattern/DataTypeDisplayPattern.cs index b18f6a3ecf..2c96b95f36 100644 --- a/Components/IO/BExIS.IO.DataType.DisplayPattern/DataTypeDisplayPattern.cs +++ b/Components/IO/BExIS.IO.DataType.DisplayPattern/DataTypeDisplayPattern.cs @@ -35,11 +35,14 @@ public class DataTypeDisplayPattern new DataTypeDisplayPattern() {Id=21, Systemtype = DataTypeCode.DateTime, Name = "Month", ExcelPattern="MM", DisplayPattern="MM", StringPattern = "MM", RegexPattern = null}, new DataTypeDisplayPattern() {Id=22, Systemtype = DataTypeCode.DateTime, Name = "d/M/yyyy h:mm:ss tt", ExcelPattern=@"d\/M\/yyyy hh:mm:ss tt", DisplayPattern="d/M/yyyy h:mm:ss tt", StringPattern = "d/M/yyyy h:mm:ss tt", RegexPattern = null}, new DataTypeDisplayPattern() {Id=23, Systemtype = DataTypeCode.DateTime, Name = "M/d/yyyy h:mm:ss tt", ExcelPattern=@"M\/d\/yyyy hh:mm:ss tt", DisplayPattern="M/d/yyyy h:mm:ss tt", StringPattern = "M/d/yyyy h:mm:ss tt", RegexPattern = null}, - new DataTypeDisplayPattern() {Id=24, Systemtype = DataTypeCode.DateTime, Name = "DateTimeIso without T", ExcelPattern=@"yyyy-MM-dd hh:mm:ss", DisplayPattern = "yyyy-MM-dd hh:mm:ss", StringPattern = "yyyy-MM-dd HH:mm:ss", RegexPattern = null}, - new DataTypeDisplayPattern() {Id=25,Systemtype = DataTypeCode.DateTime, Name = "DateEu with time", ExcelPattern=@"dd\.MM\.yyyy hh:mm:ss", DisplayPattern="dd.MM.yyyy hh:mm:ss", StringPattern = "dd.MM.yyyy hh:mm:ss", RegexPattern = null}, + new DataTypeDisplayPattern() {Id=24, Systemtype = DataTypeCode.DateTime, Name = "DateTimeIso without T", ExcelPattern=@"yyyy-MM-dd hh:mm:ss tt", DisplayPattern = "yyyy-MM-dd hh:mm:ss tt", StringPattern = "yyyy-MM-dd HH:mm:ss tt", RegexPattern = null}, + new DataTypeDisplayPattern() {Id=25,Systemtype = DataTypeCode.DateTime, Name = "DateEu with time", ExcelPattern=@"dd\.MM\.yyyy hh:mm:ss tt", DisplayPattern="dd.MM.yyyy hh:mm:ss tt", StringPattern = "dd.MM.yyyy hh:mm:ss tt", RegexPattern = null}, new DataTypeDisplayPattern() {Id=26,Systemtype = DataTypeCode.DateTime, Name = "Hour", ExcelPattern=@"HH", DisplayPattern="HH", StringPattern = "HH", RegexPattern = null}, new DataTypeDisplayPattern() {Id=27,Systemtype = DataTypeCode.DateTime, Name = "Minute", ExcelPattern=@"mm", DisplayPattern="mm", StringPattern = "mm", RegexPattern = null}, - new DataTypeDisplayPattern() {Id=28,Systemtype = DataTypeCode.DateTime, Name = "Secound", ExcelPattern=@"ss", DisplayPattern="ss", StringPattern = "ss", RegexPattern = null} + new DataTypeDisplayPattern() {Id=28,Systemtype = DataTypeCode.DateTime, Name = "Secound", ExcelPattern=@"ss", DisplayPattern="ss", StringPattern = "ss", RegexPattern = null}, + new DataTypeDisplayPattern() {Id=29,Systemtype = DataTypeCode.DateTime, Name = "DateEu with time24", ExcelPattern=@"dd\.MM\.yyyy HH:mm:ss", DisplayPattern="dd.MM.yyyy HH:mm:ss", StringPattern = "dd.MM.yyyy HH:mm:ss", RegexPattern = null}, + new DataTypeDisplayPattern() {Id=30,Systemtype = DataTypeCode.DateTime, Name = "DateTimeIso without sec", ExcelPattern=@"yyyy-MM-dd\Thh:mm", DisplayPattern="yyyy-MM-ddThh:mm", StringPattern = "yyyy-MM-ddTHH:mm", RegexPattern = null} + }; public DataTypeCode Systemtype { get; set; } diff --git a/Components/IO/BExIS.IO.Tests/BExIS.IO.Tests.csproj b/Components/IO/BExIS.IO.Tests/BExIS.IO.Tests.csproj index e7e2354df5..e784b1aad4 100644 --- a/Components/IO/BExIS.IO.Tests/BExIS.IO.Tests.csproj +++ b/Components/IO/BExIS.IO.Tests/BExIS.IO.Tests.csproj @@ -141,10 +141,6 @@ {0FCF7DE1-E8F2-484C-8638-EF759B11D8A2} BExIS.App.Testing - - {6EAD7D02-02F7-42FF-85E4-90BB892D3846} - BExIS.Utils.Config - {B4E7B1BF-01B4-40AF-8D19-B8F362167261} BExIS.Dlm.Entities @@ -188,6 +184,10 @@ {B4E7B1BF-01B4-40AF-8D19-B8F362167261} BExIS.Dlm.Entities + + {6EAD7D02-02F7-42FF-85E4-90BB892D3846} + BExIS.Utils.Config + {a7fbcc13-7e29-4710-82a1-bd6d6f811fda} BExIS.Utils.Data diff --git a/Components/IO/BExIS.IO.Tests/IOHelperTests.cs b/Components/IO/BExIS.IO.Tests/IOHelperTests.cs index bec6ce798b..1ea89f63b8 100644 --- a/Components/IO/BExIS.IO.Tests/IOHelperTests.cs +++ b/Components/IO/BExIS.IO.Tests/IOHelperTests.cs @@ -31,7 +31,7 @@ public void OneTimeTearDown() public void GetDynamicStorePathValuesTest( [Range(1, 2)] long datasetId, [Range(1, 2)] long datasetVersionOrderNr) { - string path = IoHelper.GetDynamicStorePath(datasetId, datasetVersionOrderNr, "title", ".txt"); + string path = IOHelper.GetDynamicStorePath(datasetId, datasetVersionOrderNr, "title", ".txt"); path.Should().NotBeNull("Because path is not null."); } @@ -39,7 +39,7 @@ public void GetDynamicStorePathValuesTest( [TestCase(1, 2, "test", ".txt", ExpectedResult = @"Datasets\1\DatasetVersions\1_2_test.txt")] public string GetDynamicStorePathResultTest(long datasetId, long datasetVersionOrderNr, string title, string extention) { - return IoHelper.GetDynamicStorePath(datasetId, datasetVersionOrderNr, title, extention); + return IOHelper.GetDynamicStorePath(datasetId, datasetVersionOrderNr, title, extention); } [TestCase(-1, 1, "title", ".txt")] @@ -48,7 +48,7 @@ public string GetDynamicStorePathResultTest(long datasetId, long datasetVersionO [TestCase(1, 1, "title", "")] public void GetDynamicStorePathWidthExceptionTest(long datasetId, long datasetVersionOrderNr, string title, string extention) { - Assert.Throws(() => IoHelper.GetDynamicStorePath(datasetId, datasetVersionOrderNr, title, extention)); + Assert.Throws(() => IOHelper.GetDynamicStorePath(datasetId, datasetVersionOrderNr, title, extention)); } } } \ No newline at end of file diff --git a/Components/IO/BExIS.IO.Tests/IOUtilityTests.cs b/Components/IO/BExIS.IO.Tests/IOUtilityTests.cs index 1a017ad487..fdad61b143 100644 --- a/Components/IO/BExIS.IO.Tests/IOUtilityTests.cs +++ b/Components/IO/BExIS.IO.Tests/IOUtilityTests.cs @@ -83,10 +83,10 @@ public void OneTimeTearDown() [TestCase("13:00 AM", "hh:mm tt", "1/1/0001 11:00:00 PM", false)] [TestCase("2017", "yyyy", "1/1/2017 12:00:00 AM", true)] [TestCase("1", "MM", "1/1/0001 12:00:00 AM", true)] - [TestCase("jan", "MMM", "1/1/2025 12:00:00 AM", true)] + [TestCase("jan", "MMM", "1/1/2026 12:00:00 AM", true)] [TestCase("01", "MM", "1/1/0001 12:00:00 AM", true)] - [TestCase("january", "MMMM", "1/1/2025 12:00:00 AM", true)] - [TestCase("Januar", "MMMM", "1/1/2025 12:00:00 AM", true, "de-de")] + [TestCase("january", "MMMM", "1/1/2026 12:00:00 AM", true)] + [TestCase("Januar", "MMMM", "1/1/2026 12:00:00 AM", true, "de-de")] [TestCase("24/10/2017", "MM/dd/yyyy", "10/24/2017 12:00:00 AM", false)] [TestCase("2006-2-2", "yyyy-M-d", "2/2/2006 12:00:00 AM", true)] [TestCase("2006-02-02", "yyyy-MM-dd", "2/2/2006 12:00:00 AM", true)] @@ -167,10 +167,10 @@ public void ConvertStringToDateTimeWithpatternTest(string input, string pattern, [TestCase("13:00 AM", "hh:mm tt", "1/1/0001 11:00:00 PM", false)] [TestCase("2017", "yyyy", "1/1/2017 12:00:00 AM", true)] [TestCase("1", "MM", "1/1/0001 12:00:00 AM", true)] - [TestCase("jan", "MMM", "1/1/2025 12:00:00 AM", true)] + [TestCase("jan", "MMM", "1/1/2026 12:00:00 AM", true)] [TestCase("01", "MM", "1/1/0001 12:00:00 AM", true)] - [TestCase("january", "MMMM", "1/1/2025 12:00:00 AM", true)] - [TestCase("Januar", "MMMM", "1/1/2025 12:00:00 AM", true, "de-de")] + [TestCase("january", "MMMM", "1/1/2026 12:00:00 AM", true)] + [TestCase("Januar", "MMMM", "1/1/2026 12:00:00 AM", true, "de-de")] [TestCase("24/10/2017", "MM/dd/yyyy", "10/24/2017 12:00:00 AM", false)] [TestCase("2006-2-2", "yyyy-M-d", "2/2/2006 12:00:00 AM", true)] [TestCase("2006-02-02", "yyyy-MM-dd", "2/2/2006 12:00:00 AM", true)] diff --git a/Components/IO/BExIS.IO.Tests/Transform/Input/AsciiReaderTest.cs b/Components/IO/BExIS.IO.Tests/Transform/Input/AsciiReaderTest.cs index bce9038480..d9e61076a7 100644 --- a/Components/IO/BExIS.IO.Tests/Transform/Input/AsciiReaderTest.cs +++ b/Components/IO/BExIS.IO.Tests/Transform/Input/AsciiReaderTest.cs @@ -121,6 +121,29 @@ public void rowToList_RowAsQuotesAndSeperatorInQuotes_ReturnExpectedListOfString Assert.That(values, Is.EquivalentTo(expectedOutcome)); } + [Test] + public void rowToList_RowAsOneVariablewithQuotesAndSeperatorInQuotes_ReturnExpectedListOfStrings() + { + //Arrange + string row = "V1,V2,'V3,V4'"; + List expectedOutcome = new List { "V1", "V2", "V3,V4" }; + + AsciiFileReaderInfo info = new AsciiFileReaderInfo(); + info.Seperator = TextSeperator.comma; + + AsciiReader reader = new AsciiReader(new StructuredDataStructure(), info); + + //Act + + List values = reader.rowToList(row, + AsciiFileReaderInfo.GetSeperator(TextSeperator.comma)); + + //Assert + Assert.That(values.Count, Is.EqualTo(expectedOutcome.Count)); + Assert.That(values, Is.EquivalentTo(expectedOutcome)); + } + + [Test] public void ValidateRow_runNotValid_LimitErrors() { diff --git a/Components/IO/BExIS.IO.Tests/Transform/Input/StructureAnalyzerTest.cs b/Components/IO/BExIS.IO.Tests/Transform/Input/StructureAnalyzerTest.cs index a1fab480f0..e9e1c3bd1a 100644 --- a/Components/IO/BExIS.IO.Tests/Transform/Input/StructureAnalyzerTest.cs +++ b/Components/IO/BExIS.IO.Tests/Transform/Input/StructureAnalyzerTest.cs @@ -249,7 +249,7 @@ public void SuggestSystemTypes_Valid_ResultWithCorrectTypes(int n) StructureAnalyser structureAnalyser = new StructureAnalyser(); //Act - var result = structureAnalyser.SuggestSystemTypes(rows.GetRange(0, n), TextSeperator.semicolon, DecimalCharacter.comma, new List()); + var result = structureAnalyser.SuggestSystemTypes(rows.GetRange(0, n),TextMarker.doubleQuotes, TextSeperator.semicolon, DecimalCharacter.comma, new List()); //Assert Assert.NotNull(result); @@ -294,7 +294,7 @@ public void SuggestSystemTypes_WithTestData_ResultWithCorrectTypes() StructureAnalyser structureAnalyser = new StructureAnalyser(); //Act - var result = structureAnalyser.SuggestSystemTypes(rows, TextSeperator.semicolon, DecimalCharacter.comma, new List()); + var result = structureAnalyser.SuggestSystemTypes(rows, TextMarker.doubleQuotes, TextSeperator.semicolon, DecimalCharacter.comma, new List()); //Assert Assert.NotNull(result); @@ -321,7 +321,7 @@ public void SuggestSystemTypes_ValidDateTypes_ResultWithCorrectTypes(int n) dateValues.Add("2022-12-24"); // yyyy-MM-dd //Act - var result = structureAnalyser.SuggestSystemTypes(rows.GetRange(0, n), TextSeperator.semicolon, DecimalCharacter.comma, new List()); + var result = structureAnalyser.SuggestSystemTypes(rows.GetRange(0, n), TextMarker.doubleQuotes, TextSeperator.semicolon, DecimalCharacter.comma, new List()); //Assert Assert.NotNull(result); @@ -334,7 +334,7 @@ public void SuggestSystemTypes_ValidWithMissingValues_ResultWithCorrectTypes() StructureAnalyser structureAnalyser = new StructureAnalyser(); //Act - var result = structureAnalyser.SuggestSystemTypes(rowWithMissingValues, TextSeperator.semicolon, DecimalCharacter.comma, missingValueList); + var result = structureAnalyser.SuggestSystemTypes(rowWithMissingValues, TextMarker.doubleQuotes, TextSeperator.semicolon, DecimalCharacter.comma, missingValueList); //Assert Assert.NotNull(result); diff --git a/Components/IO/BExIS.IO.Tests/Transform/Input/StructureAnalyzer_SuggestUnitTests.cs b/Components/IO/BExIS.IO.Tests/Transform/Input/StructureAnalyzer_SuggestUnitTests.cs index 464441236e..b131494fc1 100644 --- a/Components/IO/BExIS.IO.Tests/Transform/Input/StructureAnalyzer_SuggestUnitTests.cs +++ b/Components/IO/BExIS.IO.Tests/Transform/Input/StructureAnalyzer_SuggestUnitTests.cs @@ -53,7 +53,7 @@ public void SuggestUnit_AbbrOrNameAsInputDatatypeEmpty_ReturnUnit(string input, Assert.IsTrue(exist,"not exist"); } - [TestCase("Floating Point Number", 1, 105)] // datatype, id, count + [TestCase("Floating Point Number", 1, 104)] // datatype, id, count [TestCase("Text", 1, 1)] // datatype, id, count public void SuggestUnit_InputIsEmptyButDatatypeExist_ReturnUnit(string dataType, long firstId, int count) { diff --git a/Components/IO/BExIS.IO.Transform.Input/AsciiReader.cs b/Components/IO/BExIS.IO.Transform.Input/AsciiReader.cs index ec0c0384ce..e2db48b9a9 100644 --- a/Components/IO/BExIS.IO.Transform.Input/AsciiReader.cs +++ b/Components/IO/BExIS.IO.Transform.Input/AsciiReader.cs @@ -988,6 +988,28 @@ public List rowToList(string line, char seperator) return line.Split(seperator).ToList(); } + /// + /// Convert a row as a string to a list of strings based on offset,seperator and textmarker + /// + /// + /// + /// Row as a string + /// Character used as TextSeparator + /// Character used as TextMarker + /// offset to skip values from begin + /// List of values + public List RowToList(string line, char seperator, char textMarker, int offset = 0) + { + List tempRow = new List(); + + tempRow = new List(); + tempRow = TextMarkerHandling(line, seperator,textMarker); + + //if a offset is marked in the file reader information the offset needs to skip from the complete string array + return tempRow.Skip(offset).ToList(); + //return line.Split(seperator).ToList(); + } + private List values; private List temp; diff --git a/Components/IO/BExIS.IO.Transform.Input/StructureAnalyser.cs b/Components/IO/BExIS.IO.Transform.Input/StructureAnalyser.cs index f0e94123f4..390b197c0f 100644 --- a/Components/IO/BExIS.IO.Transform.Input/StructureAnalyser.cs +++ b/Components/IO/BExIS.IO.Transform.Input/StructureAnalyser.cs @@ -457,7 +457,7 @@ public List GetRandowRows(List rows, int numberOfRows) throw new NotImplementedException(); } - public Dictionary SuggestSystemTypes(List rows, TextSeperator delimeter, DecimalCharacter decimalCharacter, List missingValues) + public Dictionary SuggestSystemTypes(List rows,TextMarker textMarker, TextSeperator delimeter, DecimalCharacter decimalCharacter, List missingValues) { Dictionary> source = new Dictionary>(); Dictionary result = new Dictionary(); @@ -465,6 +465,12 @@ public Dictionary SuggestSystemTypes(List rows, TextSeperator // get type checks checks = getDataTypeChecks(decimalCharacter); + // validate input + AsciiFileReaderInfo info = new AsciiFileReaderInfo(); + info.Seperator = delimeter; + info.TextMarker = textMarker; + AsciiReader reader = new AsciiReader(new StructuredDataStructure(), info); + // create dictionary with types per column char seperator = AsciiFileReaderInfo.GetSeperator(delimeter); int numberOfRow = rows.First().Split(seperator).Count(); @@ -477,8 +483,11 @@ public Dictionary SuggestSystemTypes(List rows, TextSeperator // go throw each row foreach (var row in rows) { + // + var cells = reader.rowToList(row, seperator); + //split row based on sepeartor - foreach (var cell in row.Split(seperator).Select((x, i) => new { Value = x, Index = i })) + foreach (var cell in cells.Select((x, i) => new { Value = x, Index = i })) { //update possible list of types by check the value against the existing list source[cell.Index] = checkValue(cell.Value, source[cell.Index], missingValues); diff --git a/Components/IO/BExIS.IO/BExIS.IO.csproj b/Components/IO/BExIS.IO/BExIS.IO.csproj index 69d9e8c657..9372e6ba86 100644 --- a/Components/IO/BExIS.IO/BExIS.IO.csproj +++ b/Components/IO/BExIS.IO/BExIS.IO.csproj @@ -67,6 +67,16 @@ + + + {6EAD7D02-02F7-42FF-85E4-90BB892D3846} + BExIS.Utils.Config + + + {782b71c1-707f-4ab1-80e9-90d2880635b4} + BExIS.Utils + + diff --git a/Components/IO/BExIS.Io.Transform.Output/OutputDataStructureManager.cs b/Components/IO/BExIS.Io.Transform.Output/OutputDataStructureManager.cs index f48051cb6c..f9a0e641bc 100644 --- a/Components/IO/BExIS.Io.Transform.Output/OutputDataStructureManager.cs +++ b/Components/IO/BExIS.Io.Transform.Output/OutputDataStructureManager.cs @@ -5,11 +5,18 @@ using BExIS.Dlm.Services.DataStructure; using BExIS.Dlm.Services.Meanings; using BExIS.IO.DataType.DisplayPattern; +using DocumentFormat.OpenXml.Drawing.Charts; +using DocumentFormat.OpenXml.Math; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Data; +using System.IO; using System.Linq; +using System.Text; +using System.Web.Configuration; +using Vaiona.Utils.Cfg; +using DataTable = System.Data.DataTable; namespace BExIS.IO.Transform.Output { @@ -426,16 +433,36 @@ public static string GetDataStructureAsJson(long id) return JsonConvert.SerializeObject(new DataStructureDataTable(id)); } - //public static string GetVariableListAsJson(long id) - //{ - // return JsonConvert.SerializeObject(new DataStructureDataList(id), Newtonsoft.Json.Formatting.Indented); - //} public static DataStructureDataList GetVariableList(long id) { return new DataStructureDataList(id); } + public static string GenerateDataStructureAsText(long datastructureId) + { + StringBuilder stringBuilder = new StringBuilder(); + + using (var dataStructureManager = new DataStructureManager()) + { + StructuredDataStructure dataStructure = new StructuredDataStructure(); + dataStructure = dataStructureManager.StructuredDataStructureRepo.Get(datastructureId); + + if (dataStructure != null) + { + stringBuilder.AppendLine(String.Join(",", dataStructure.Variables.Select(v => v.Label))); + stringBuilder.AppendLine(String.Join(",", dataStructure.Variables.Select(v => v.Unit?.Name))); + stringBuilder.AppendLine(String.Join(",", dataStructure.Variables.Select(v => v.Description))); + stringBuilder.AppendLine(String.Join(",", dataStructure.Variables.Select(v => v.DataType?.Name))); + stringBuilder.AppendLine(String.Join(",", dataStructure.Variables.Select(v => v.IsValueOptional? "optional" : "mandatory"))); + stringBuilder.AppendLine(String.Join(",", dataStructure.Variables.Select(v => v.IsKey?"primary key":""))); + } + } + + return stringBuilder.ToString(); + } + + public static string GenerateDataStructure(long datasetId) { string path = ""; diff --git a/Components/IO/BExIS.Io.Transform.Output/OutputDatasetManager.cs b/Components/IO/BExIS.Io.Transform.Output/OutputDatasetManager.cs index 906205b66b..a1bf214578 100644 --- a/Components/IO/BExIS.Io.Transform.Output/OutputDatasetManager.cs +++ b/Components/IO/BExIS.Io.Transform.Output/OutputDatasetManager.cs @@ -74,7 +74,7 @@ public static GFBIOPangaeaFormularObject GetGFBIOPangaeaFormularObject(long data public static string GetDynamicDatasetStorePath(long datasetId, long datasetVersionOrderNr, string title, string extention) { - return IoHelper.GetDynamicStorePath(datasetId, datasetVersionOrderNr, title, extention); + return IOHelper.GetDynamicStorePath(datasetId, datasetVersionOrderNr, title, extention); } public static XmlDocument GenerateManifest(long datasetId, long versionId) diff --git a/Components/IO/BExIS.Io/BExIS.Io.csproj b/Components/IO/BExIS.Io/BExIS.Io.csproj index 69d9e8c657..9372e6ba86 100644 --- a/Components/IO/BExIS.Io/BExIS.Io.csproj +++ b/Components/IO/BExIS.Io/BExIS.Io.csproj @@ -67,6 +67,16 @@ + + + {6EAD7D02-02F7-42FF-85E4-90BB892D3846} + BExIS.Utils.Config + + + {782b71c1-707f-4ab1-80e9-90d2880635b4} + BExIS.Utils + + diff --git a/Components/IO/BExIS.Io/IoHelper.cs b/Components/IO/BExIS.Io/IoHelper.cs index eca10ad259..942599e19b 100644 --- a/Components/IO/BExIS.Io/IoHelper.cs +++ b/Components/IO/BExIS.Io/IoHelper.cs @@ -1,9 +1,11 @@ -using System; +using BExIS.Utils.Config; +using BExIS.Utils.Files; +using System; using System.IO; namespace BExIS.IO { - public class IoHelper + public class IOHelper { public static string GetDynamicStorePath(long datasetId, long datasetVersionOrderNr, string title, string extention) { @@ -14,9 +16,90 @@ public static string GetDynamicStorePath(long datasetId, long datasetVersionOrde if (extention.IndexOf('.') == -1) throw new Exception("Extention should start with '.' ."); string storePath = Path.Combine("Datasets", datasetId.ToString(), "DatasetVersions"); + string fileName = datasetId + "_" + datasetVersionOrderNr + "_" + title + extention;//GetFileName(FileType.None, datasetId, (int)datasetVersionOrderNr, 0, title); - return Path.Combine(storePath, datasetId + "_" + datasetVersionOrderNr + "_" + title + extention); + return Path.Combine(storePath, fileName); } + + public static string GetDynamicStoreFilePath(long datasetId, long datasetVersionOrderNr, string name, string extention) + { + if (datasetId < 1) throw new Exception("Dataset id can not be less then 1."); + if (datasetVersionOrderNr < 1) throw new Exception("Dataset version number can not be less then 1."); + if (string.IsNullOrEmpty(name)) throw new Exception("Title should not be Empty."); + if (string.IsNullOrEmpty(extention)) throw new Exception("Extention should not be Empty."); + if (extention.IndexOf('.') == -1) throw new Exception("Extention should start with '.' ."); + + string storePath = Path.Combine("Datasets", datasetId.ToString(), "DatasetVersions"); + string fileName = GetFileName(FileType.None, datasetId, (int)datasetVersionOrderNr, 0, name); + + return Path.Combine(storePath, fileName + extention); + } + + public static string GetFileName(FileType type, long datasetId, int versionNr, long datastructureId, string title = "") + { + string appName = GeneralSettings.ApplicationName; + if (string.IsNullOrEmpty(appName)) appName = "BEXIS2"; + + string downloadName = "no title available"; + string downloadDate = DateTime.Now.ToString("yyyyMMdd"); + + string downloadTitle = FileNameUtility.SanitizeFileName(title,'-'); + + + + // tag vs version? + + switch (type) + { + case FileType.Metadata: + // filename should contain: application name, dataset ID, and version ID + downloadName = string.Format("{0}_{1}_v{2}_metadata", appName, datasetId, versionNr); + break; + case FileType.MetadataExport: + // filename should contain: application name, dataset ID, and version ID + downloadName = string.Format("{0}_{1}_v{2}_metadata_{3}", appName, datasetId, versionNr, title); + break; + case FileType.DataStructure: + downloadName = string.Format("{0}_{1}_data_structure_{2}", appName, datasetId, datastructureId); + break; + case FileType.PrimaryData: + downloadName = string.Format("{0}_{1}_v{2}_data", appName, datasetId, versionNr); + + break; + case FileType.PrimaryDataFiles: + downloadName = string.Format("{0}_{1}_v{2}_data_{3}", appName, datasetId, versionNr, title); + break; + case FileType.Attachments: + downloadName = string.Format("{0}_{1}_v{2}_attachment_{3}", appName, datasetId, versionNr, title); + break; + case FileType.Bundle: + downloadName = string.Format("{0}_{1}_v{2}_{3}_{4}", appName, datasetId, versionNr, downloadTitle, downloadDate); + break; + case FileType.Manifest: + downloadName = string.Format("{0}_{1}_v{2}_general_metadata", appName, datasetId, versionNr); + break; + default: + downloadName = string.Format("{0}_{1}_v{2}_{3}", appName, datasetId, versionNr, title); + break; + } + + + return downloadName; + } + + } + + public enum FileType + { + Metadata, + MetadataExport, + PrimaryData, + PrimaryDataFiles, + DataStructure, + Attachments, + Bundle, + Manifest, + None } public enum DecimalCharacter diff --git a/Components/JSON/BEXIS.JSON.Helpers/MetadataStructureConverter.cs b/Components/JSON/BEXIS.JSON.Helpers/MetadataStructureConverter.cs index 356d2cc319..f495e7c3cb 100644 --- a/Components/JSON/BEXIS.JSON.Helpers/MetadataStructureConverter.cs +++ b/Components/JSON/BEXIS.JSON.Helpers/MetadataStructureConverter.cs @@ -179,11 +179,21 @@ private JSchema addMetadataAttrUsage(BaseUsage usage, JSchema schema, out bool r // set json schema type based on datatype input currentText.Type = convertToJSchemaType(type.DataType); - //if Datatype is datetime, jsosn schema use type string - // but need to set a format - if (type.DataType.SystemType == "datetime") + ////if Datatype is datetime, jsosn schema use type string + //// but need to set a format + //if (type.DataType.SystemType.ToLower() == "datetime") + //{ + // currentText.Format = "date"; + //} + + if (type.DataType.SystemType.ToLower() == "datetime") { - currentText.Format = "date"; + currentText.Format = type.DataType.Name.ToLower(); // set datetime, date or time in format + } + + if (type.DataType.SystemType.ToLower() == "string" && type.DataType.Name.ToLower() == "text") + { + currentText.Format = type.DataType.Name.ToLower(); } //Contraints @@ -378,7 +388,10 @@ private bool IsChoice(XmlNode xmlNode) private JSchemaType GetJSchemaType(BaseUsage usage) { - if(IsChoice(usage.Extra)) + // if choice and max cardinality >1 then array + if (IsChoice(usage.Extra) && getMaxCardinality(usage) > 1) + return JSchemaType.Array; + else if (IsChoice(usage.Extra)) return JSchemaType.Object; else if (getMaxCardinality(usage) > 1) return JSchemaType.Array; diff --git a/Components/UI/BExIS.UI.Tests/BExIS.UI.Tests.csproj b/Components/UI/BExIS.UI.Tests/BExIS.UI.Tests.csproj index 1b8a096eca..7ad7a67c66 100644 --- a/Components/UI/BExIS.UI.Tests/BExIS.UI.Tests.csproj +++ b/Components/UI/BExIS.UI.Tests/BExIS.UI.Tests.csproj @@ -144,10 +144,6 @@ {5C4C8570-A53B-4191-A414-DE028AAAF36A} BExIS.IO.Transform.Input - - {6EAD7D02-02F7-42FF-85E4-90BB892D3846} - BExIS.Utils.Config - {b446c14f-9df1-4c3c-a634-e3addd05c48a} Vaiona.Core diff --git a/Components/UI/BExIS.UI.Tests/HookManagerTests.cs b/Components/UI/BExIS.UI.Tests/HookManagerTests.cs index e887fe440a..91f7222f78 100644 --- a/Components/UI/BExIS.UI.Tests/HookManagerTests.cs +++ b/Components/UI/BExIS.UI.Tests/HookManagerTests.cs @@ -2,7 +2,6 @@ using BExIS.IO.Transform.Input; using BExIS.UI.Hooks; using BExIS.UI.Hooks.Caches; -using BExIS.Utils.Config; using NUnit.Framework; using System; using System.Collections.Generic; diff --git a/Components/UI/BExIS.UI/BExIS.UI.csproj b/Components/UI/BExIS.UI/BExIS.UI.csproj index efdf488fdf..1b5719d2f8 100644 --- a/Components/UI/BExIS.UI/BExIS.UI.csproj +++ b/Components/UI/BExIS.UI/BExIS.UI.csproj @@ -164,7 +164,7 @@ BExIS.IO - {6ead7d02-02f7-42ff-85e4-90bb892d3846} + {6EAD7D02-02F7-42FF-85E4-90BB892D3846} BExIS.Utils.Config diff --git a/Components/Utils/BExIS.Utils.Config/BExIS.Utils.Config.csproj b/Components/Utils/BExIS.Utils.Config/BExIS.Utils.Config.csproj index a98f53abf2..977ccdc3af 100644 --- a/Components/Utils/BExIS.Utils.Config/BExIS.Utils.Config.csproj +++ b/Components/Utils/BExIS.Utils.Config/BExIS.Utils.Config.csproj @@ -68,6 +68,7 @@ + diff --git a/Components/Utils/BExIS.Utils.Config/Configurations/CurationConfiguration.cs b/Components/Utils/BExIS.Utils.Config/Configurations/CurationConfiguration.cs new file mode 100644 index 0000000000..90326ac122 --- /dev/null +++ b/Components/Utils/BExIS.Utils.Config/Configurations/CurationConfiguration.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace BExIS.Utils.Config.Configurations +{ + public class CurationLabel + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("color")] + public string Color { get; set; } + + public CurationLabel(string name, string color) + { + this.Name = name; + this.Color = color; + } + } + + public class CurationConfiguration + { + [JsonProperty("curationLabels")] + public List CurationLabels { get; set; } + } +} diff --git a/Components/Utils/BExIS.Utils.Data/Helpers/ShellSeedDataGenerator.cs b/Components/Utils/BExIS.Utils.Data/Helpers/ShellSeedDataGenerator.cs index 9e2852a90c..019254f8f1 100644 --- a/Components/Utils/BExIS.Utils.Data/Helpers/ShellSeedDataGenerator.cs +++ b/Components/Utils/BExIS.Utils.Data/Helpers/ShellSeedDataGenerator.cs @@ -40,9 +40,9 @@ public void GenerateSeedData() var o12 = operationManager.Find("Shell", "Settings", "*") ?? operationManager.Create("Shell", "Settings", "*", settings); - if (!versionManager.Exists("Shell", "4.1.0")) + if (!versionManager.Exists("Shell", "4.2.1")) { - versionManager.Create("Shell", "4.1.0"); + versionManager.Create("Shell", "4.2.1"); } } } diff --git a/Components/Utils/BExIS.Utils.Tests/BExIS.Utils.Tests.csproj b/Components/Utils/BExIS.Utils.Tests/BExIS.Utils.Tests.csproj index e972bfbdea..1495ffc90a 100644 --- a/Components/Utils/BExIS.Utils.Tests/BExIS.Utils.Tests.csproj +++ b/Components/Utils/BExIS.Utils.Tests/BExIS.Utils.Tests.csproj @@ -126,6 +126,7 @@ + diff --git a/Components/Utils/BExIS.Utils.Tests/Files/FileNameUtilityTests.cs b/Components/Utils/BExIS.Utils.Tests/Files/FileNameUtilityTests.cs new file mode 100644 index 0000000000..1fe1eaee91 --- /dev/null +++ b/Components/Utils/BExIS.Utils.Tests/Files/FileNameUtilityTests.cs @@ -0,0 +1,37 @@ +using BExIS.Utils.Files; +using BExIS.Utils.Helpers; +using NUnit.Framework; + +namespace BExIS.Utils.Tests +{ + [TestFixture()] + public class FileNameUtilityTests + { + [OneTimeSetUp] + public void OneTimeSetUp() + { + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + } + + [Test] + public void SanitizeFileName_invalidCharacters_ReturnValid() + { + //Arrange + string input = "filewith:forbidden\"chars/\\|?*.txt"; + string expected = "file-name-with-forbidden-chars-.txt"; + + //Act + string result = FileNameUtility.SanitizeFileName(input); + + //Assert + Assert.NotNull(result); + Assert.AreEqual(expected, result); + } + + + } +} \ No newline at end of file diff --git a/Components/Utils/BExIS.Utils/BExIS.Utils.csproj b/Components/Utils/BExIS.Utils/BExIS.Utils.csproj index e0457b17a7..a453805a79 100644 --- a/Components/Utils/BExIS.Utils/BExIS.Utils.csproj +++ b/Components/Utils/BExIS.Utils/BExIS.Utils.csproj @@ -101,6 +101,7 @@ + diff --git a/Components/Utils/BExIS.Utils/Files/FileNameUtility.cs b/Components/Utils/BExIS.Utils/Files/FileNameUtility.cs new file mode 100644 index 0000000000..cea12d49b3 --- /dev/null +++ b/Components/Utils/BExIS.Utils/Files/FileNameUtility.cs @@ -0,0 +1,85 @@ +using System.IO; +using System.Linq; +using System.Text; + + +namespace BExIS.Utils.Files +{ + /// + /// Provides utility methods for handling and sanitizing file names based on operating system rules. + /// + public static class FileNameUtility + { + /// + /// Replaces all invalid file name characters with a specified replacement character. + /// It ensures the resulting string is safe to use as a file name segment on Windows/Unix-like systems. + /// + /// The original file name string to sanitize. + /// The character to use for replacing invalid characters (default is '-'). + /// A sanitized string suitable for use as a file name. + public static string SanitizeFileName(string fileName, char replacementChar = '-') + { + // 1. Check for null or empty input + if (string.IsNullOrEmpty(fileName)) + { + return string.Empty; + } + + // 2. Get the array of characters forbidden in file names by the operating system. + // This is the most reliable way to handle OS-specific invalid characters. + char[] invalidChars = Path.GetInvalidFileNameChars(); + + // 3. Use a StringBuilder for efficient string manipulation + StringBuilder sb = new StringBuilder(fileName.Length); + + // 4. Iterate through the input string and replace invalid characters + foreach (char c in fileName) + { + if (invalidChars.Contains(c)) + { + // Replace the invalid character with the specified replacement char + sb.Append(replacementChar); + } + else + { + // Keep valid characters as they are + sb.Append(c); + } + } + + string sanitizedName = sb.ToString(); + + // 5. Post-sanitization cleanup for specific Windows constraints (ends with '.' or ' ') + // While replacement should handle most issues, it's good practice to ensure + // the name doesn't end in characters that Windows implicitly trims or handles poorly. + + // Trim spaces from the end + sanitizedName = sanitizedName.TrimEnd(' '); + + // If the name ends with the replacement char (e.g., if the original ended with a '.'), + // ensure it doesn't leave an ending period (Windows specific) unless it's part of an extension. + // We'll use a simple check to ensure it doesn't end with a period. + if (sanitizedName.EndsWith(".")) + { + // Remove trailing period if it exists + sanitizedName = sanitizedName.TrimEnd('.'); + } + + // Optional: Replace multiple consecutive replacement characters with a single one. + // This prevents "file---name--.txt" from becoming "file-name.txt". + string doubleReplacement = new string(new[] { replacementChar, replacementChar }); + while (sanitizedName.Contains(doubleReplacement)) + { + sanitizedName = sanitizedName.Replace(doubleReplacement, replacementChar.ToString()); + } + + // length + if (sanitizedName.Length > 40) + { + sanitizedName = sanitizedName.Substring(0, 40)+"..."; + } + + return sanitizedName; + } + } +} diff --git a/Components/Utils/BExIS.Utils/Helpers/ManualHelper.cs b/Components/Utils/BExIS.Utils/Helpers/ManualHelper.cs index df20f063cc..7f06929e46 100644 --- a/Components/Utils/BExIS.Utils/Helpers/ManualHelper.cs +++ b/Components/Utils/BExIS.Utils/Helpers/ManualHelper.cs @@ -10,7 +10,6 @@ public static string GetUrl(string url) if (string.IsNullOrEmpty(url)) return docsUrl+"general"; //2. URL is set and no url -> generate link to internal documentation - if (url.Contains("http")) return url; diff --git a/Components/Vaiona/Vaiona.Logging/Vaiona.Logging.csproj b/Components/Vaiona/Vaiona.Logging/Vaiona.Logging.csproj index fcc9faa73a..e678b94e5d 100644 --- a/Components/Vaiona/Vaiona.Logging/Vaiona.Logging.csproj +++ b/Components/Vaiona/Vaiona.Logging/Vaiona.Logging.csproj @@ -58,9 +58,6 @@ - - - {0815d220-3625-4e23-bbbc-8152345637fe} @@ -79,6 +76,9 @@ Vaiona.Utils + + + diff --git a/Components/XML/BExIS.Xml.Helpers.UnitTests/ConvertTo_JsonToXmlArray.xml b/Components/XML/BExIS.Xml.Helpers.UnitTests/ConvertTo_JsonToXmlArray.xml index be73e05b32..2547a8c9dd 100644 --- a/Components/XML/BExIS.Xml.Helpers.UnitTests/ConvertTo_JsonToXmlArray.xml +++ b/Components/XML/BExIS.Xml.Helpers.UnitTests/ConvertTo_JsonToXmlArray.xml @@ -1,483 +1,227 @@ - - - - - NA - - - <TitleType type="MetadataAttribute" name="TitleType" roleId="64" id="126" number="1">Extreme drought impacts have been underestimated in grasslands and shrublands globally</TitleType> - - - Climate change is increasing the frequency and severity of short-term (~1 y) drought events—the most common duration of drought—globally. Yet the impact of this intensification of drought on ecosystem functioning remains poorly resolved. This is due in part to the widely disparate approaches ecologists have employed to study drought, variation in the severity and duration of drought studied, and differences among ecosystems in vegetation, edaphic and climatic attributes that can mediate drought impacts. To overcome these problems and better identify the factors that modulate drought responses, we used a coordinated distributed experiment to quantify the impact of short-term drought on grassland and shrubland ecosystems. With a standardized approach, we imposed ~a single year of drought at 100 sites on six continents. Here we show that loss of a foundational ecosystem function—aboveground net primary production (ANPP)—was 60% greater at sites that experienced statistically extreme drought (1-in-100-y event) vs. those sites where drought was nominal (historically more common) in magnitude (35% vs. 21%, respectively). This reduction in a key carbon cycle process with a single year of extreme drought greatly exceeds previously reported losses for grasslands and shrublands. Our global experiment also revealed high variability in drought response but that relative reductions in ANPP were greater in drier ecosystems and those with fewer plant species. Overall, our results demonstrate with unprecedented rigor that the global impacts of projected increases in drought severity have been significantly underestimated and that drier and less diverse sites are likely to be most vulnerable to extreme drought. - - - Smith, Melinda D. - Wilkins, Kate D. - Holdrege, Martin C. - Wilfahrt, Peter - Collins, Scott L. - Knapp, Alan K. - Sala, Osvaldo E. - Dukes, Jeffrey S. - Phillips, Richard P. - Yahdjian, Laura - Gherardi, Laureano A. - Ohlert, Timothy - Beier, Claus - Fraser, Lauchlan H. - Jentsch, Anke - Loik, Michael E. - Maestre, Fernando T. - Power, Sally A. - Yu, Qiang - Felton, Andrew J. - Munson, Seth M. - Luo, Yiqi - Abdoli, Hamed - Abedi, Mehdi - Alados, Concepción L. - Alberti, Juan - Alon, Moshe - An, Hui - Anacker, Brian - Anderson, Maggie - Auge, Harald - Bachle, Seton - Bahalkeh, Khadijeh - Bahn, Michael - Batbaatar, Amgaa - Bauerle, Taryn - Beard, Karen H. - Behn, Kai - Beil, Ilka - Biancari, Lucio - Blindow, Irmgard - Bondaruk, Viviana Florencia - Borer, Elizabeth T. - Bork, Edward W. - Bruschetti, Carlos Martin - Byrne, Kerry M. - Cahill Jr., James F. - Calvo, Dianela A. - Carbognani, Michele - Cardoni, Augusto - Carlyle, Cameron N. - Castillo-Garcia, Miguel - Chang, Scott X. - Chieppa, Jeff - Cianciaruso, Marcus V. - Cohen, Ofer - Cordeiro, Amanda L. - Cusack, Daniela F. - Dahlke, Sven - Daleo, Pedro - D'Antonio, Carla M. - Dietterich, Lee H. - S. Doherty, Tim - Dubbert, Maren - Ebeling, Anne - Eisenhauer, Nico - Fischer, Felícia M. - Forte, T'ai G. W. - Gebauer, Tobias - Gozalo, Beatriz - Greenville, Aaron C. - Guidoni-Martins, Karlo G. - Hannusch, Heather J. - Vatsø Haugum, Siri - Hautier, Yann - Hefting, Mariet - Henry, Hugh A. L. - Hoss, Daniela - Ingrisch, Johannes - Iribarne, Oscar - Isbell, Forest - Johnson, Yari - Jordan, Samuel - Kelly, Eugene F. - Kimmel, Kaitlin - Kreyling, Juergen - Kröel-Dulay, György - Kröpfl, Alicia - Kübert, Angelika - Kulmatiski, Andrew - Lamb, Eric G. - Larsen, Klaus Steenberg - Larson, Julie - Lawson, Jason - Leder, Cintia V. - Linstädter, Anja - Liu, Jielin - Liu, Shirong - Lodge, Alexandra G. - Longo, Grisel - Loydi, Alejandro - Luan, Junwei - Curtis Lubbe, Frederick - Macfarlane, Craig - Mackie-Haas, Kathleen - Malyshev, Andrey V. - Maturano-Ruiz, Adrián - Merchant, Thomas - Metcalfe, Daniel B. - Mori, Akira S. - Mudongo, Edwin - Newman, Gregory S. - Nielsen, Uffe N. - Nimmo, Dale - Niu, Yujie - Nobre, Paola - O'Connor, Rory C. - Ogaya, Romà - Oñatibia, Gastón R. - Orbán, Ildikó - Osborne, Brooke - Otfinowski, Rafael - Pärtel, Meelis - Penuelas, Josep - Peri, Pablo L. - Peter, Guadalupe - Petraglia, Alessandro - Picon-Cochard, Catherine - Pillar, Valério D. - Piñeiro-Guerra, Juan Manuel - Ploughe, Laura W. - Plowes, Robert M. - Portales-Reyes, Cristy - Prober, Suzanne M. - Pueyo, Yolanda - Reed, Sasha C. - Ritchie, Euan G. - Rodríguez, Dana Aylén - Rogers, William E. - Roscher, Christiane - Sánchez, Ana M. - Santos, Bráulio A. - Cecilia Scarfó, María - Seabloom, Eric W. - Shi, Baoku - Souza, Lara - Stampfli, Andreas - Standish, Rachel J. - Sternberg, Marcelo - Sun, Wei - Sünnemann, Marie - Tedder, Michelle - Thorvaldsen, Pål - Tian, Dashuan - Tielbörger, Katja - Valdecantos, Alejandro - van den Brink, Liesbeth - Vandvik, Vigdis - Vankoughnett, Mathew R. - Guri Velle, Liv - Wang, Changhui - Wang, Yi - Wardle, Glenda M. - Werner, Christiane - Wei, Cunzheng - Wiehl, Georg - Williams, Jennifer L. - Wolf, Amelia A. - Zeiter, Michaela - Zhang, Fawei - Zhu, Juntao - Zong, Ning - Zuo, Xiaoan - - - - - - - - - - - Ludmilla Figueiredo - - - NA - - - 07.05.2024 00:00 - - - 07.05.2024 00:00 - - - - - - - NA - - - NA - - - 10.1073/pnas.2309881120 - - - https://www.pnas.org/doi/abs/10.1073/pnas.2309881120 - - - none - - - - - journalArticle - - - - - - - - - - - - e2309881120 - - - 4 - - - 121 - - - States that data and code are shared, but only data is. In Dryad, asks future users to contact authors to ensure use according to best practices. - - - NA - - - - - - - - - - - - - - none - - - none - - - - - - - - - Dataset - - - - - - - - - - - https://datadryad.org/stash/dataset/doi:10.5061/dryad.3j9kd51rb - - - yes - - - https://doi.org/10.5061/dryad.3j9kd51rb - - - yes - - - - - - cc0 1.0 - - - - - dryad - - - - - - - - - - - - - - - - - csv - - - - - - - - - - - - - NA - - - - - - - - - - Test1 - - - - - - - - hi - - - maybe - - - - - - - - - - - - - - - - - Felix - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - hier steht der name - - - - - - - - link - - - verfügbar - - - IOD - - - test - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + <TitleDatatype_string type="MetadataAttribute" name="TitleDatatype_string" roleId="64" id="87" number="1">my publication</TitleDatatype_string> + + + asbjdh gajhsdg jahgfsd aghsdas + + + David Schöne + Sven Thiel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + yes + + + + + + + + + + + none + yes + + + none + a + + + + + + + + + Software + + + Franziska + + + + + + + + + + + + + + + + + asd + + + + + + + 1234567890 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Components/XML/BExIS.Xml.Helpers.UnitTests/ConvertTo_XmlToJson.xml b/Components/XML/BExIS.Xml.Helpers.UnitTests/ConvertTo_XmlToJson.xml index 59412a905f..b0a2134948 100644 --- a/Components/XML/BExIS.Xml.Helpers.UnitTests/ConvertTo_XmlToJson.xml +++ b/Components/XML/BExIS.Xml.Helpers.UnitTests/ConvertTo_XmlToJson.xml @@ -2,393 +2,393 @@ - 123456 + 123456 - + - + - David Schöne + David Schöne - + - 01608037788 + 01608037788
- Von der Gabelentz Straße 9 erfurt 99089 Deutschland + Von der Gabelentz Straße 9 erfurt 99089 Deutschland
- + - Sven Thiel + Sven Thiel - + - +
- +
- + - Franziska + Franziska - + - +
- +
-
+
- + - + - David Schöne + David Schöne - + - 01608037788 + 01608037788
- Von der Gabelentz Straße 9 erfurt 99089 Deutschland + Von der Gabelentz Straße 9 erfurt 99089 Deutschland
-
+
- + - + - + - + - + - + - <TitleType type="MetadataAttribute" name="TitleType" roleId="5" id="8" number="1">Test</TitleType> + <String255 type="MetadataAttribute" name="String255" roleId="5" id="2" number="1">Test</String255>
- +
- + - +
-
+
- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - 2022-07-06 + 2022-07-06 - + - + - + - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
- +
-
+
- + - + - + - + - + - + - + - + - + - + - + - + - +
-
+
- + - + - + - +
- +
- +
-
+
- + - + - +
- +
- +
-
+
- + - + - +
- +
- +
-
+
- + - + - +
- +
- +
-
+
- + - + - +
- +
- +
-
+
- + - + - +
- +
- +
-
+
- + - + - +
- +
- +
-
+
-
+
\ No newline at end of file diff --git a/Components/XML/BExIS.Xml.Helpers/BExIS.Xml.Helpers.csproj b/Components/XML/BExIS.Xml.Helpers/BExIS.Xml.Helpers.csproj index 149d36bc51..cf26493387 100644 --- a/Components/XML/BExIS.Xml.Helpers/BExIS.Xml.Helpers.csproj +++ b/Components/XML/BExIS.Xml.Helpers/BExIS.Xml.Helpers.csproj @@ -71,7 +71,7 @@ - + diff --git a/Components/XML/BExIS.Xml.Helpers/Extensions/XmlExtensions.cs b/Components/XML/BExIS.Xml.Helpers/Extensions/XmlExtensions.cs new file mode 100644 index 0000000000..ab00ce6c3d --- /dev/null +++ b/Components/XML/BExIS.Xml.Helpers/Extensions/XmlExtensions.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Xml; + +namespace BExIS.Xml.Helpers.Extensions +{ + public static class XmlExtensions + { + public static void TransformXmlToMatchClassTypes(XmlNode node, Type classType) + { + foreach (XmlNode childNode in node.ChildNodes) + { + string propertyName = childNode.Name; + PropertyInfo propertyInfo = classType.GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); + + if (propertyInfo != null) + { + // If it's a list or array type, ensure the XML node appropriately reflects that + if (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(List<>)) + { + Type itemType = propertyInfo.PropertyType.GetGenericArguments()[0]; + + foreach (XmlNode listItem in childNode.ChildNodes) + { + // Recursively transform the XML node to match the item type of the collection + TransformXmlToMatchClassTypes(listItem, itemType); + } + } + else if (childNode.HasChildNodes) + { + TransformXmlToMatchClassTypes(childNode, propertyInfo.PropertyType); + } + } + else + { + // Handle cases where the property is not found in the class type + Console.WriteLine($"Property '{propertyName}' not found in '{classType.Name}'."); + } + } + } + } +} diff --git a/Components/XML/BExIS.Xml.Helpers/IOHelper.cs b/Components/XML/BExIS.Xml.Helpers/IOHelper.cs deleted file mode 100644 index f5abb419d2..0000000000 --- a/Components/XML/BExIS.Xml.Helpers/IOHelper.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.IO; - -namespace BExIS.Xml.Helpers -{ - public class IOHelper - { - public static string GetDynamicStorePath(long datasetId, long datasetVersionOrderNr, string title, string extention) - { - string storePath = Path.Combine("Datasets", datasetId.ToString(), "DatasetVersions"); - - return Path.Combine(storePath, datasetId + "_" + datasetVersionOrderNr + "_" + title + extention); - } - } -} \ No newline at end of file diff --git a/Components/XML/BExIS.Xml.Helpers/Mapping/XmlSchemaManager.cs b/Components/XML/BExIS.Xml.Helpers/Mapping/XmlSchemaManager.cs index 7dede0faa1..828b436516 100644 --- a/Components/XML/BExIS.Xml.Helpers/Mapping/XmlSchemaManager.cs +++ b/Components/XML/BExIS.Xml.Helpers/Mapping/XmlSchemaManager.cs @@ -716,7 +716,7 @@ public long GenerateMetadataStructure(string nameOfStartNode, string schemaName) //Debug.Writeline("package : " + element.Name); //Debug.Writeline("--------------------------"); - string typeName = GetTypeOfName(element.Name); + string typeName = GetTypeOfName(element); string rootName = ((XmlSchemaElement)root).Name; string xpathInternal = "Metadata/" + element.Name + "/" + typeName; @@ -965,7 +965,7 @@ private MetadataCompoundAttribute get(XmlSchemaElement element, List par if (ct.Name != null) nameOfType = ct.Name; else - nameOfType = GetTypeOfName(element.Name); + nameOfType = GetTypeOfName(element); MetadataCompoundAttribute metadataCompountAttr = getExistingMetadataCompoundAttribute(nameOfType); string currentInternalXPath = internalXPath + "/" + element.Name + "/" + nameOfType; @@ -1218,12 +1218,38 @@ private void addUsageFromMetadataCompoundAttributeToPackage(MetadataPackage pack * */ + //if element is a choise + XmlDocument extra = null; + //check if element is a choice if (XmlSchemaUtility.IsChoiceType(element)) { min = 0; + Dictionary additionalAttributes = new Dictionary(); + + XmlSchemaComplexType ct = element.ElementSchemaType as XmlSchemaComplexType; + + if (ct != null) + { + #region choice + + // check if it is e choice + XmlSchemaChoice choice = ct.ContentTypeParticle as XmlSchemaChoice; + if (choice != null) + { + additionalAttributes.Add("min", choice.MinOccurs.ToString()); + if (choice.MaxOccurs > 10) + additionalAttributes.Add("max", "10"); + else + additionalAttributes.Add("max", choice.MaxOccurs.ToString()); + } + + #endregion choice + } + + extra = xmlDatasetHelper.AddReferenceToXml(new XmlDocument(), "choice", "true", "elementType", @"extra/type", additionalAttributes); } - metadataPackageManager.AddMetadataAtributeUsage(package, compoundAttribute, element.Name, GetDescription(element.Annotation), min, max, element.DefaultValue, element.FixedValue); + metadataPackageManager.AddMetadataAtributeUsage(package, compoundAttribute, element.Name, GetDescription(element.Annotation), min, max, element.DefaultValue, element.FixedValue, extra); } } finally @@ -1328,9 +1354,9 @@ private MetadataCompoundAttribute addMetadataAttributeToMetadataCompoundAttribut MetadataAttribute attribute; if (metadataAttributeManager.MetadataAttributeRepo != null && - getExistingMetadataAttribute(GetTypeOfName(element.Name)) != null) + getExistingMetadataAttribute(GetTypeOfName(element)) != null) { - attribute = getExistingMetadataAttribute(GetTypeOfName(element.Name)); + attribute = getExistingMetadataAttribute(GetTypeOfName(element)); } else { @@ -1443,7 +1469,7 @@ private MetadataAttribute createMetadataAttribute(XmlSchemaElement element, stri { XmlSchemaSimpleType type = (XmlSchemaSimpleType)element.ElementSchemaType; - string name = GetTypeOfName(element.Name); + string name = GetTypeOfName(element); string description = ""; if (element.Annotation != null) @@ -1507,7 +1533,7 @@ private MetadataAttribute createMetadataAttribute(XmlSchemaElement element, stri var t = ComplexTypes.FirstOrDefault(c => c.Name.Equals(type.Name)); if (t != null) type = t; - string name = GetTypeOfName(element.Name); + string name = GetTypeOfName(element); string description = ""; if (element.Annotation != null) @@ -1628,7 +1654,8 @@ private MetadataCompoundAttribute createMetadataCompoundAttribute(XmlSchemaEleme { // create a compoundAttribute int i = 0; - MetadataCompoundAttribute mca = getExistingMetadataCompoundAttribute(element.Name + "Type"); ;// = metadataAttributeManager.MetadataCompoundAttributeRepo.Get(p => p.Name == element.Name+"Type").FirstOrDefault(); + string typeName = GetTypeOfName(element); + MetadataCompoundAttribute mca = getExistingMetadataCompoundAttribute(typeName); ;// = metadataAttributeManager.MetadataCompoundAttributeRepo.Get(p => p.Name == element.Name+"Type").FirstOrDefault(); //Debug.WriteLine("createMetadataCompoundAttribute" + i++); DataType dt1 = dataTypeManager.Repo.Get(p => p.Name.ToLower().Equals("text")).FirstOrDefault(); if (dt1 == null) @@ -1640,8 +1667,8 @@ private MetadataCompoundAttribute createMetadataCompoundAttribute(XmlSchemaEleme { mca = new MetadataCompoundAttribute { - ShortName = GetTypeOfName(element.Name), - Name = GetTypeOfName(element.Name), + ShortName = typeName, + Name = typeName, Description = "", DataType = dt1 }; @@ -1909,9 +1936,9 @@ private void addMetadataAttributeToMappingFile(MetadataCompoundAttribute compoun MetadataAttribute attribute; if (metadataAttributeManager.MetadataAttributeRepo != null && - metadataAttributeManager.MetadataAttributeRepo.Query().Where(m => m.Name.Equals(GetTypeOfName(element.Name))).Count() > 0) + metadataAttributeManager.MetadataAttributeRepo.Query().Where(m => m.Name.Equals(GetTypeOfName(element))).Count() > 0) { - attribute = metadataAttributeManager.MetadataAttributeRepo.Query().Where(m => m.Name.Equals(GetTypeOfName(element.Name))).First(); + attribute = metadataAttributeManager.MetadataAttributeRepo.Query().Where(m => m.Name.Equals(GetTypeOfName(element))).First(); } else { @@ -2108,14 +2135,27 @@ private DataType GetDataType(string dataTypeAsString, string typeCodeName) { if (!dataTypeAsString.ToLower().Equals("Object")) { + TypeCode typeCode = ConvertStringToSystemType(dataTypeAsString); DataType dataType = null; // if datatime - need to check typeCodeName for date, time , datetime - + TypeCode c = DataTypeHelper.GetMaxTypeCode(typeCode); string label = DataTypeHelper.GetLabel(typeCode); + // in case of time or date, there is no system type for it so we need to set it + if (typeCodeName.ToLower().Equals("time")) + { + c = TypeCode.DateTime; + label = "Time"; + } + else if(typeCodeName.ToLower().Equals("date")) + { + c = TypeCode.DateTime; + label = "Date"; + } + if (dataTypeAsString.Equals(TypeCode.DateTime.ToString())) { @@ -2131,7 +2171,7 @@ private DataType GetDataType(string dataTypeAsString, string typeCodeName) dataType = dataTypeManager.Repo.Query() .Where( - d => + d => d.Name.ToLower().Equals(label.ToString().ToLower())) .FirstOrDefault(); @@ -2203,9 +2243,35 @@ private void checkDirectory(string filePath) } } - private string GetTypeOfName(string name) + private string GetTypeOfName(XmlSchemaElement element) { - return name + "Type"; + string nameOfType = ""; + + // check if type is complex + XmlSchemaComplexType ct = XmlSchemaUtility.GetComplextType(element); + + if (ct != null && ct.Name != null) + { + return ct.Name; + } + + //check if simple type + XmlSchemaSimpleType st = XmlSchemaUtility.GetSimpleType(element); + + if(st != null && st.Name != null) + { + return st.Name; + } + + // nothing found so create a new name + XSDType + if(element.ElementType!=null && element.ElementType.GetType()!=null) + return element.Name + element.ElementType.GetType().Name; // bexis2InternType + + if (element.ElementSchemaType != null && element.ElementSchemaType.GetType() != null) + return element.Name + element.ElementSchemaType.GetType().Name; // bexis2InternType + + // if nothing exist return name only + return element.Name; } #endregion helper functions diff --git a/Components/XML/BExIS.Xml.Helpers/XmlMetadataWriter.cs b/Components/XML/BExIS.Xml.Helpers/XmlMetadataWriter.cs index ac01e1afb1..7b1eac5af7 100644 --- a/Components/XML/BExIS.Xml.Helpers/XmlMetadataWriter.cs +++ b/Components/XML/BExIS.Xml.Helpers/XmlMetadataWriter.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Xml; using System.Xml.Linq; @@ -777,6 +778,11 @@ public XDocument AddAttribute(XDocument metadataXml, BaseUsage attributeUsage, i { _tempXDoc = metadataXml; + if (!XmlUtility.IsSafeXPath(parentXPath)) + { + throw new ArgumentException("Potentially unsafe xpath expression."); + } + /* * In the xml the structure is everytime usage/type * diff --git a/Components/XML/BExIS.Xml.Helpers/XmlSchemaUtility.cs b/Components/XML/BExIS.Xml.Helpers/XmlSchemaUtility.cs index 23d67e24a3..0cd09a9b7f 100644 --- a/Components/XML/BExIS.Xml.Helpers/XmlSchemaUtility.cs +++ b/Components/XML/BExIS.Xml.Helpers/XmlSchemaUtility.cs @@ -524,6 +524,14 @@ public static XmlSchemaComplexType GetComplextType(XmlSchemaElement element) return null; } + public static XmlSchemaSimpleType GetSimpleType(XmlSchemaElement element) + { + if (element.ElementSchemaType is XmlSchemaSimpleType) + return element.ElementSchemaType as XmlSchemaSimpleType; + + return null; + } + public static List GetAllSimpleElements(List elements) { List simpleElementList = new List(); diff --git a/Components/XML/BExIS.Xml.Helpers/XmlUtility.cs b/Components/XML/BExIS.Xml.Helpers/XmlUtility.cs index 6f33fcddf2..354a75bab2 100644 --- a/Components/XML/BExIS.Xml.Helpers/XmlUtility.cs +++ b/Components/XML/BExIS.Xml.Helpers/XmlUtility.cs @@ -13,6 +13,19 @@ namespace BExIS.Xml.Helpers { public class XmlUtility { + // Validate that xpath is a simple, safe path: only allows slashes and node names (alphanumeric/underscore) + public static bool IsSafeXPath(string xpath) + { + // Prevent empty, null XPaths + if (string.IsNullOrEmpty(xpath)) return false; + + // Only allow XPaths like /a/b/c or /a[1]/b matches, no function calls, no quotes, etc. + // You can adjust the pattern according to the actual requirements. + // This only allows: /node1/node2/... + var safePattern = @"^(/?/?[a-zA-Z_][\w\-]*(\[\d+\])?)*$"; + return System.Text.RegularExpressions.Regex.IsMatch(xpath, safePattern); + } + public static string GetXPathToNode(XmlNode node) { string xPath = ""; diff --git a/Console/BExIS.Web.Shell.Svelte/package-lock.json b/Console/BExIS.Web.Shell.Svelte/package-lock.json index 8186a81eba..0971161e8c 100644 --- a/Console/BExIS.Web.Shell.Svelte/package-lock.json +++ b/Console/BExIS.Web.Shell.Svelte/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.1", "hasInstallScript": true, "dependencies": { - "@bexis2/bexis2-core-ui": "0.4.47", + "@bexis2/bexis2-core-ui": "0.4.64", "@sveltejs/adapter-static": "3.0.2", "buffer": "6.0.3", "gray-matter": "4.0.3", @@ -76,9 +76,9 @@ } }, "node_modules/@bexis2/bexis2-core-ui": { - "version": "0.4.47", - "resolved": "https://registry.npmjs.org/@bexis2/bexis2-core-ui/-/bexis2-core-ui-0.4.47.tgz", - "integrity": "sha512-PL9g1AvXaNj2Srw0xDAmBjrQiI9qoBGMvdJPj9hydRWmod+uKnbWlMrqDilnrWh2EWy2FzFiqE81FZ2giWO/6Q==", + "version": "0.4.63", + "resolved": "https://registry.npmjs.org/@bexis2/bexis2-core-ui/-/bexis2-core-ui-0.4.63.tgz", + "integrity": "sha512-tLqUzCsCslNSA+YoMd6+FRA6SXY2SOet9RVdQbe6khVXEc33lvFwu3ZrNfNBT3Ku5ca1dtngzA4dhSNeCAg99A==", "license": "ISC", "dependencies": { "@codemirror/lang-html": "^6.4.9", diff --git a/Console/BExIS.Web.Shell.Svelte/package.json b/Console/BExIS.Web.Shell.Svelte/package.json index 4444330de3..4f43fac141 100644 --- a/Console/BExIS.Web.Shell.Svelte/package.json +++ b/Console/BExIS.Web.Shell.Svelte/package.json @@ -52,7 +52,7 @@ }, "type": "module", "dependencies": { - "@bexis2/bexis2-core-ui": "0.4.47", + "@bexis2/bexis2-core-ui": "0.4.65", "@sveltejs/adapter-static": "3.0.2", "buffer": "6.0.3", "gray-matter": "4.0.3", diff --git a/Console/BExIS.Web.Shell.Svelte/src/routes/home/docs/[...slug]/[category]/+page.svelte b/Console/BExIS.Web.Shell.Svelte/src/routes/home/docs/[...slug]/[category]/+page.svelte index 7562fae4fc..1646f8ddc5 100644 --- a/Console/BExIS.Web.Shell.Svelte/src/routes/home/docs/[...slug]/[category]/+page.svelte +++ b/Console/BExIS.Web.Shell.Svelte/src/routes/home/docs/[...slug]/[category]/+page.svelte @@ -18,8 +18,7 @@ import sanitizeHtml from 'sanitize-html'; import { marked } from 'marked'; - import { time } from 'console'; - import { TIMEOUT } from 'dns'; + export let data; let content_complete = ''; @@ -210,7 +209,7 @@ const svg_add = ''; const svg_edit = - ''; + ''; const svg_view = ''; const svg_delete = @@ -219,26 +218,31 @@ ''; const svg_configure = ''; - - if (text.startsWith('[!LINK_VIEW]')) { - return ``; - } - if (text.startsWith('[!LINK_EDIT]')) { - return ``; - } - if (text.startsWith('[!LINK_CREATE]')) { - return ``; - } - if (text.startsWith('[!LINK_DELETE]')) { - return ``; + const svg_save = + ''; + + const icons = {}; + icons['[!LINK_VIEW]'] = svg_view; + icons['[!LINK_EDIT]'] = svg_edit; + icons['[!LINK_CREATE]'] = svg_add; + icons['[!LINK_DELETE]'] = svg_delete; + icons['[!LINK_MANAGE]'] = svg_manage; + icons['[!LINK_CONFIGURE]'] = svg_configure; + icons['[!LINK_SAVE]'] = svg_save; + + // use regex to extract the link type and the rest of the text + const text_type = text.match(/(\[!LINK_[A-Z]+\])/); + if (!text_type) { + return `${text}`; } - if (text.startsWith('[!LINK_MANAGE]')) { - return ``; + const text_rest = text.replace(text_type[0], '').trim(); + if (text_type[0] in icons) { + return `${icons[text_type[0]]}${text_rest}`; } - if (text.startsWith('[!LINK_CONFIGURE]')) { - return ``; + else + { + return `${text}`; } - return `${text}`; }; async function toggleVisibility(index: number, init = false) { @@ -340,7 +344,7 @@ // Find the last non-empty element (with a height > 0) for (let i = elementsAbove.length - 1; i >= 0; i--) { - if (elementsAbove[i].offsetHeight > 0) { + if ((elementsAbove[i] as HTMLElement).offsetHeight > 0) { lastElement = elementsAbove[i]; break; } diff --git a/Console/BExIS.Web.Shell/App_Data/BExIS.Modules.Dim.UI.xml b/Console/BExIS.Web.Shell/App_Data/BExIS.Modules.Dim.UI.xml index d747be1752..f6f5bf1d5b 100644 --- a/Console/BExIS.Web.Shell/App_Data/BExIS.Modules.Dim.UI.xml +++ b/Console/BExIS.Web.Shell/App_Data/BExIS.Modules.Dim.UI.xml @@ -941,7 +941,7 @@ - + Map a system key to xml node @@ -954,7 +954,7 @@ - + Map a node from xml to system key @@ -967,7 +967,7 @@ - + Map a node from xml to concept key diff --git a/Console/BExIS.Web.Shell/Areas/BAM/Controllers/HelpController.cs b/Console/BExIS.Web.Shell/Areas/BAM/Controllers/HelpController.cs index f17277464e..f350fd0356 100644 --- a/Console/BExIS.Web.Shell/Areas/BAM/Controllers/HelpController.cs +++ b/Console/BExIS.Web.Shell/Areas/BAM/Controllers/HelpController.cs @@ -1,5 +1,4 @@ -using BExIS.Utils.Config; -using BExIS.Utils.Helpers; +using BExIS.Utils.Helpers; using System; using System.Web.Mvc; using Vaiona.Web.Mvc.Modularity; diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/package-lock.json b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/package-lock.json index 5b80daedd7..0e1aca7615 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/package-lock.json +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/package-lock.json @@ -10,12 +10,13 @@ "hasInstallScript": true, "license": "ISC", "dependencies": { - "@bexis2/bexis2-core-ui": "0.4.47", - "@bexis2/bexis2-rpm-ui": "0.2.11", + "@bexis2/bexis2-core-ui": "0.4.64", + "@bexis2/bexis2-rpm-ui": "0.2.15", "@floating-ui/dom": "1.6.8", "@fortawesome/free-solid-svg-icons": "6.6.0", "@sveltejs/adapter-static": "3.0.2", "@sveltejs/package": "2.3.2", + "@xyflow/svelte": "^0.1.36", "patch-package": "8.0.0", "svelte-adapter-github": "1.0.0-next.0", "svelte-fa": "4.0.2", @@ -85,9 +86,10 @@ } }, "node_modules/@bexis2/bexis2-core-ui": { - "version": "0.4.47", - "resolved": "https://registry.npmjs.org/@bexis2/bexis2-core-ui/-/bexis2-core-ui-0.4.47.tgz", - "integrity": "sha512-PL9g1AvXaNj2Srw0xDAmBjrQiI9qoBGMvdJPj9hydRWmod+uKnbWlMrqDilnrWh2EWy2FzFiqE81FZ2giWO/6Q==", + "version": "0.4.64", + "resolved": "https://registry.npmjs.org/@bexis2/bexis2-core-ui/-/bexis2-core-ui-0.4.64.tgz", + "integrity": "sha512-YK6tKBISQY9DYXsGqG8523kIAKFPETRGEozGhditIify+mQgUoMEcnB4E4vHJHjhX+VAM73jOdCMuv9MF8ev8Q==", + "license": "ISC", "dependencies": { "@codemirror/lang-html": "^6.4.9", "@codemirror/lang-javascript": "^6.2.2", @@ -119,13 +121,13 @@ } }, "node_modules/@bexis2/bexis2-rpm-ui": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@bexis2/bexis2-rpm-ui/-/bexis2-rpm-ui-0.2.11.tgz", - "integrity": "sha512-8NyBkD2bwFU/Q/zMozUQvqo+WQFfGJtxoij/3VSqze4LWHbwVBCseW3RaoZxLuuSIWZq6Ll9txR1SfoBWtYVnA==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/@bexis2/bexis2-rpm-ui/-/bexis2-rpm-ui-0.2.15.tgz", + "integrity": "sha512-vCQK1M2pd7KdGzIP9QpNclFx1Y37ckNjgZbwcHQOmxOtMA+CRe6MEHintKDNlO8lXEWSYv29KDAIVqcdPNsDfA==", "hasInstallScript": true, "license": "ISC", "dependencies": { - "@bexis2/bexis2-core-ui": "0.4.47", + "@bexis2/bexis2-core-ui": "0.4.60", "@floating-ui/dom": "1.6.8", "@sveltejs/adapter-static": "3.0.2", "@sveltejs/package": "2.3.2", @@ -134,6 +136,41 @@ "svelte-fa": "4.0.2" } }, + "node_modules/@bexis2/bexis2-rpm-ui/node_modules/@bexis2/bexis2-core-ui": { + "version": "0.4.60", + "resolved": "https://registry.npmjs.org/@bexis2/bexis2-core-ui/-/bexis2-core-ui-0.4.60.tgz", + "integrity": "sha512-D/Ytgpzj51uxCu+TEROF4PxmfHE7ONaGDeQINj083dtaalS0l/u3w7kWUIeTn5D8u6oLUg/FywVzqyd0bCXkpA==", + "license": "ISC", + "dependencies": { + "@codemirror/lang-html": "^6.4.9", + "@codemirror/lang-javascript": "^6.2.2", + "@codemirror/lang-json": "^6.0.1", + "@codemirror/lint": "^6.8.1", + "@codemirror/theme-one-dark": "^6.1.2", + "@floating-ui/dom": "^1.6.8", + "@fortawesome/fontawesome-free": "^6.6.0", + "@fortawesome/fontawesome-svg-core": "^6.6.0", + "@fortawesome/free-regular-svg-icons": "^6.6.0", + "@fortawesome/free-solid-svg-icons": "^6.6.0", + "@sveltejs/kit": "^2.5.19", + "@sveltejs/vite-plugin-svelte": "^3.1.1", + "axios": "^1.7.3", + "codemirror": "^6.0.1", + "dateformat": "^5.0.3", + "delay": "^6.0.0", + "dotenv": "^16.4.5", + "eslint4b-prebuilt": "^6.7.2", + "highlight.js": "^11.10.0", + "highlightjs-svelte": "^1.0.6", + "svelte": "^4.2.18", + "svelte-codemirror-editor": "^1.4.0", + "svelte-fa": "^4.0.2", + "svelte-file-dropzone": "^2.0.7", + "svelte-headless-table": "^0.18.2", + "svelte-select": "5.8.3", + "vest": "^5.4.0" + } + }, "node_modules/@codemirror/autocomplete": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.0.tgz", @@ -1238,6 +1275,15 @@ "tailwindcss": ">=3.0.0" } }, + "node_modules/@svelte-put/shortcut": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@svelte-put/shortcut/-/shortcut-3.1.1.tgz", + "integrity": "sha512-2L5EYTZXiaKvbEelVkg5znxqvfZGZai3m97+cAiUBhLZwXnGtviTDpHxOoZBsqz41szlfRMcamW/8o0+fbW3ZQ==", + "license": "MIT", + "peerDependencies": { + "svelte": "^3.55.0 || ^4.0.0 || ^5.0.0" + } + }, "node_modules/@sveltejs/adapter-auto": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.2.2.tgz", @@ -1404,6 +1450,55 @@ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -1712,6 +1807,35 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@xyflow/svelte": { + "version": "0.1.39", + "resolved": "https://registry.npmjs.org/@xyflow/svelte/-/svelte-0.1.39.tgz", + "integrity": "sha512-QZ5mzNysvJeJW7DxmqI4Urhhef9tclqtPr7WAS5zQF5Gk6k9INwzey4CYNtEZo8XMj9H8lzgoJRmgMPnJEc1kw==", + "license": "MIT", + "dependencies": { + "@svelte-put/shortcut": "3.1.1", + "@xyflow/system": "0.0.59", + "classcat": "^5.0.4" + }, + "peerDependencies": { + "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.59", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.59.tgz", + "integrity": "sha512-+xgqYhoBv5F10TQx0SiKZR/DcWtuxFYR+e/LluHb7DMtX4SsMDutZWEJ4da4fDco25jZxw5G9fOlmk7MWvYd5Q==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -2130,6 +2254,12 @@ "node": ">=8" } }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, "node_modules/code-red": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", @@ -2260,6 +2390,111 @@ "node": ">=4" } }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/dateformat": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-5.0.3.tgz", @@ -4892,9 +5127,9 @@ } }, "node_modules/svelty-picker": { - "version": "5.2.11", - "resolved": "https://registry.npmjs.org/svelty-picker/-/svelty-picker-5.2.11.tgz", - "integrity": "sha512-0WrAWqj0kNDhq18rm8wujH+Fy45/jCdDq2r4sWEpgdNSKNc6IxjzH5v1CqRR0Xq09lUnMmBY9izqTKpmLKc5hQ==", + "version": "5.2.12", + "resolved": "https://registry.npmjs.org/svelty-picker/-/svelty-picker-5.2.12.tgz", + "integrity": "sha512-w9mxlWEwMtypiDUZDp04HxmfOb9ujmJLQnyjBh8CQL+p6KiE1O7Kctffzo8BsbjR8TdwZGe4uDk/pXn1fe8jXQ==", "dependencies": { "@floating-ui/dom": "^1.4" }, @@ -5473,9 +5708,9 @@ } }, "@bexis2/bexis2-core-ui": { - "version": "0.4.47", - "resolved": "https://registry.npmjs.org/@bexis2/bexis2-core-ui/-/bexis2-core-ui-0.4.47.tgz", - "integrity": "sha512-PL9g1AvXaNj2Srw0xDAmBjrQiI9qoBGMvdJPj9hydRWmod+uKnbWlMrqDilnrWh2EWy2FzFiqE81FZ2giWO/6Q==", + "version": "0.4.64", + "resolved": "https://registry.npmjs.org/@bexis2/bexis2-core-ui/-/bexis2-core-ui-0.4.64.tgz", + "integrity": "sha512-YK6tKBISQY9DYXsGqG8523kIAKFPETRGEozGhditIify+mQgUoMEcnB4E4vHJHjhX+VAM73jOdCMuv9MF8ev8Q==", "requires": { "@codemirror/lang-html": "^6.4.9", "@codemirror/lang-javascript": "^6.2.2", @@ -5507,17 +5742,53 @@ } }, "@bexis2/bexis2-rpm-ui": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@bexis2/bexis2-rpm-ui/-/bexis2-rpm-ui-0.2.11.tgz", - "integrity": "sha512-8NyBkD2bwFU/Q/zMozUQvqo+WQFfGJtxoij/3VSqze4LWHbwVBCseW3RaoZxLuuSIWZq6Ll9txR1SfoBWtYVnA==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/@bexis2/bexis2-rpm-ui/-/bexis2-rpm-ui-0.2.15.tgz", + "integrity": "sha512-vCQK1M2pd7KdGzIP9QpNclFx1Y37ckNjgZbwcHQOmxOtMA+CRe6MEHintKDNlO8lXEWSYv29KDAIVqcdPNsDfA==", "requires": { - "@bexis2/bexis2-core-ui": "0.4.47", + "@bexis2/bexis2-core-ui": "0.4.60", "@floating-ui/dom": "1.6.8", "@sveltejs/adapter-static": "3.0.2", "@sveltejs/package": "2.3.2", "papaparse": "5.4.1", "patch-package": "8.0.0", "svelte-fa": "4.0.2" + }, + "dependencies": { + "@bexis2/bexis2-core-ui": { + "version": "0.4.60", + "resolved": "https://registry.npmjs.org/@bexis2/bexis2-core-ui/-/bexis2-core-ui-0.4.60.tgz", + "integrity": "sha512-D/Ytgpzj51uxCu+TEROF4PxmfHE7ONaGDeQINj083dtaalS0l/u3w7kWUIeTn5D8u6oLUg/FywVzqyd0bCXkpA==", + "requires": { + "@codemirror/lang-html": "^6.4.9", + "@codemirror/lang-javascript": "^6.2.2", + "@codemirror/lang-json": "^6.0.1", + "@codemirror/lint": "^6.8.1", + "@codemirror/theme-one-dark": "^6.1.2", + "@floating-ui/dom": "^1.6.8", + "@fortawesome/fontawesome-free": "^6.6.0", + "@fortawesome/fontawesome-svg-core": "^6.6.0", + "@fortawesome/free-regular-svg-icons": "^6.6.0", + "@fortawesome/free-solid-svg-icons": "^6.6.0", + "@sveltejs/kit": "^2.5.19", + "@sveltejs/vite-plugin-svelte": "^3.1.1", + "axios": "^1.7.3", + "codemirror": "^6.0.1", + "dateformat": "^5.0.3", + "delay": "^6.0.0", + "dotenv": "^16.4.5", + "eslint4b-prebuilt": "^6.7.2", + "highlight.js": "^11.10.0", + "highlightjs-svelte": "^1.0.6", + "svelte": "^4.2.18", + "svelte-codemirror-editor": "^1.4.0", + "svelte-fa": "^4.0.2", + "svelte-file-dropzone": "^2.0.7", + "svelte-headless-table": "^0.18.2", + "svelte-select": "5.8.3", + "vest": "^5.4.0" + } + } } }, "@codemirror/autocomplete": { @@ -6177,6 +6448,12 @@ "dev": true, "requires": {} }, + "@svelte-put/shortcut": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@svelte-put/shortcut/-/shortcut-3.1.1.tgz", + "integrity": "sha512-2L5EYTZXiaKvbEelVkg5znxqvfZGZai3m97+cAiUBhLZwXnGtviTDpHxOoZBsqz41szlfRMcamW/8o0+fbW3ZQ==", + "requires": {} + }, "@sveltejs/adapter-auto": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.2.2.tgz", @@ -6292,6 +6569,49 @@ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" }, + "@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" + }, + "@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "requires": { + "@types/d3-color": "*" + } + }, + "@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==" + }, + "@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "requires": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -6491,6 +6811,30 @@ "tinyrainbow": "^1.2.0" } }, + "@xyflow/svelte": { + "version": "0.1.39", + "resolved": "https://registry.npmjs.org/@xyflow/svelte/-/svelte-0.1.39.tgz", + "integrity": "sha512-QZ5mzNysvJeJW7DxmqI4Urhhef9tclqtPr7WAS5zQF5Gk6k9INwzey4CYNtEZo8XMj9H8lzgoJRmgMPnJEc1kw==", + "requires": { + "@svelte-put/shortcut": "3.1.1", + "@xyflow/system": "0.0.59", + "classcat": "^5.0.4" + } + }, + "@xyflow/system": { + "version": "0.0.59", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.59.tgz", + "integrity": "sha512-+xgqYhoBv5F10TQx0SiKZR/DcWtuxFYR+e/LluHb7DMtX4SsMDutZWEJ4da4fDco25jZxw5G9fOlmk7MWvYd5Q==", + "requires": { + "@types/d3-drag": "^3.0.7", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, "@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -6762,6 +7106,11 @@ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==" }, + "classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==" + }, "code-red": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", @@ -6863,6 +7212,72 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, + "d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==" + }, + "d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==" + }, + "d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + } + }, + "d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==" + }, + "d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "requires": { + "d3-color": "1 - 3" + } + }, + "d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==" + }, + "d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==" + }, + "d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "requires": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + } + }, + "d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + } + }, "dateformat": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-5.0.3.tgz", @@ -8683,9 +9098,9 @@ } }, "svelty-picker": { - "version": "5.2.11", - "resolved": "https://registry.npmjs.org/svelty-picker/-/svelty-picker-5.2.11.tgz", - "integrity": "sha512-0WrAWqj0kNDhq18rm8wujH+Fy45/jCdDq2r4sWEpgdNSKNc6IxjzH5v1CqRR0Xq09lUnMmBY9izqTKpmLKc5hQ==", + "version": "5.2.12", + "resolved": "https://registry.npmjs.org/svelty-picker/-/svelty-picker-5.2.12.tgz", + "integrity": "sha512-w9mxlWEwMtypiDUZDp04HxmfOb9ujmJLQnyjBh8CQL+p6KiE1O7Kctffzo8BsbjR8TdwZGe4uDk/pXn1fe8jXQ==", "requires": { "@floating-ui/dom": "^1.4" } diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/package.json b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/package.json index f58b857a4b..cdd9212694 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/package.json +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/package.json @@ -34,6 +34,7 @@ "@types/node": "22.0.2", "@typescript-eslint/eslint-plugin": "8.0.0", "@typescript-eslint/parser": "8.0.0", + "@ts4nfdi/terminology-service-suite-js": "^6.0.0", "autoprefixer": "10.4.19", "eslint": "9.8.0", "eslint-config-prettier": "9.1.0", @@ -50,8 +51,8 @@ }, "type": "module", "dependencies": { - "@bexis2/bexis2-core-ui": "0.4.47", - "@bexis2/bexis2-rpm-ui": "0.2.11", + "@bexis2/bexis2-core-ui": "0.4.65", + "@bexis2/bexis2-rpm-ui": "0.2.15", "@floating-ui/dom": "1.6.8", "@fortawesome/free-solid-svg-icons": "6.6.0", "@sveltejs/adapter-static": "3.0.2", diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/lib/components/datadescription/Generate.svelte b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/lib/components/datadescription/Generate.svelte index 29eeaaa972..0f0149a499 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/lib/components/datadescription/Generate.svelte +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/lib/components/datadescription/Generate.svelte @@ -2,7 +2,7 @@ import { goTo } from '../../../services/BaseCaller'; import { MultiSelect, Spinner } from '@bexis2/bexis2-core-ui'; import type { listItemType } from '@bexis2/bexis2-core-ui'; - import { onMount, createEventDispatcher } from 'svelte'; + import { onMount, createEventDispatcher} from 'svelte'; import { availableStructues, setStructure } from '$services/DataDescriptionCaller'; import { getHookStart } from '$services/HookCaller'; @@ -10,7 +10,7 @@ import type { DataDescriptionModel } from '$models/DataDescription'; import { latestFileUploadDate, latestDataDescriptionDate } from '../../../routes/edit/stores'; - + const dispatch = createEventDispatcher(); function goToGenerate(file, id) { @@ -38,6 +38,9 @@ let structures = []; $: structures; + let selected; + $: selected; + onMount(async () => { reload(); setList(model.readableFiles, structures); @@ -63,18 +66,24 @@ } // after select a value from the dropdown - // it will go to the generator or selet a exiting structure + // it will go to the generator or select a exiting structure async function change(e) { let item = e.detail; //console.log("select item",item) - if (item.group === 'options') { + if (item.group === 'other options') { //console.log("go to create a datastructure") goToCreate(); - } else if (item.group === 'file') { + } else if (item.group === 'create new based on file') { + if (item.text === 'upload first a file to create a new data structure based on it') { + // do nothing + // trigger clear selection + selected = null; + return; + } loading = true; goToGenerate(e.detail.text, model.id); - } else if (item.group === 'structure') { + } else if (item.group === 'data structures') { console.log('select a structure', id, item.id); loading = true; await setStructure(model.id, item.id); @@ -82,22 +91,44 @@ } } - // list is a comibnation of options, already existing datastructures and files + // list is a combination of options, already existing data structures and files function setList(files, structureList) { list = []; - if (model.isRestricted == false) { - // if user is not restricted by selection of the structures, then add option to vcreate a new one - list.push({ id: 0, text: 'create new', group: 'options' }); - } - if (files && model.isRestricted == false) { // if user is not restricted by selection of the structures, then add option to create from file - files.forEach((i) => list.push({ id: i.name, text: i.name, group: 'file' })); + if (files.length > 0){ + files.forEach((i) => list.push({ + id: i.name, text: i.name, group: 'create new based on file', + description: '' + })); + } + else{ + // if no files are available, we can not create from file + list.push({ + id: 0, text: 'upload first a file to create a new data structure based on it', group: 'create new based on file', + description: '' + }); + } + } + + if (model.isRestricted == false) { + // if user is not restricted by selection of the structures, then add option to vcreate a new one + // get max id to avoid id conflict + let maxId = 0; + list.forEach((i) => { + if (i.id > maxId) maxId = i.id; + }); + list.push({ + id: maxId + 1, text: 'create a new data structure manually', group: 'other options', + description: '', + }); } if (structureList) { //structureList!== null && structureList != undefined) + // change group from structure to existing structure + structureList.forEach((s) => (s.group = 'data structures')); list = [...list, ...structureList]; } } @@ -107,7 +138,8 @@
{/if} diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/lib/components/datadescription/ShowHeader.svelte b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/lib/components/datadescription/ShowHeader.svelte index 123f876f07..7daea8cfce 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/lib/components/datadescription/ShowHeader.svelte +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/lib/components/datadescription/ShowHeader.svelte @@ -3,7 +3,7 @@ import type { fileInfoType } from '@bexis2/bexis2-core-ui'; import Fa from 'svelte-fa'; - import { faTrash, faEdit, faDownload } from '@fortawesome/free-solid-svg-icons'; + import { faTrash, faPencil, faDownload } from '@fortawesome/free-solid-svg-icons'; import { removeStructure } from '$services/DataDescriptionCaller'; import { latestDataDescriptionDate } from '../../../routes/edit/stores'; @@ -28,8 +28,8 @@ const modal: ModalSettings = { type: 'confirm', - title: 'Copy', - body: 'Are you sure you wish to remove the structure?', + title: 'Remove Data Structure', + body: 'Are you sure you want to remove this data structure?', // TRUE if confirm pressed, FALSE if cancel pressed response: (r: boolean) => { if (r === true) { @@ -87,20 +87,20 @@
- Download {#if enableEdit} - Edit {/if} {#if hasData === false} modalStore.trigger(modal)}>Remove {/if}
diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/lib/components/fileupload/FileOverviewItem.svelte b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/lib/components/fileupload/FileOverviewItem.svelte index 1b31159461..b39049096e 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/lib/components/fileupload/FileOverviewItem.svelte +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/lib/components/fileupload/FileOverviewItem.svelte @@ -76,7 +76,7 @@ {#if loading} {:else} - + {/if}
diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/lib/hooks/Metadata.svelte b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/lib/hooks/Metadata.svelte index cf31824e1c..a2babda494 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/lib/hooks/Metadata.svelte +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/lib/hooks/Metadata.svelte @@ -61,12 +61,12 @@
- Edit
{#if open} (open = false)} > -{/if} +{/if} \ No newline at end of file diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/routes/create/Form.svelte b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/routes/create/Form.svelte index 53decb691e..9cafe8625a 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/routes/create/Form.svelte +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/routes/create/Form.svelte @@ -159,14 +159,14 @@ {/if} Cancel - Create and Continue diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/routes/entitytemplates/Edit.svelte b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/routes/entitytemplates/Edit.svelte index 388d7cfdbe..85d925b0c5 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/routes/entitytemplates/Edit.svelte +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI.Svelte/src/routes/entitytemplates/Edit.svelte @@ -6,7 +6,7 @@ // ui Components import Fa from 'svelte-fa'; import { DropdownKVP, MultiSelect, TextArea, TextInput, Spinner } from '@bexis2/bexis2-core-ui'; - import { faSave, faTrash } from '@fortawesome/free-solid-svg-icons/index'; + import { faSave, faXmark } from '@fortawesome/free-solid-svg-icons/index'; import { SlideToggle } from '@skeletonlabs/skeleton'; import ContentContainer from '../../lib/components/ContentContainer.svelte'; import { Modal, getModalStore } from '@skeletonlabs/skeleton'; @@ -400,7 +400,7 @@ title="cancel" type="button" class="btn variant-filled-warning" - on:click={onCancel}> 4
+ @@ -72,8 +73,6 @@ - - @@ -115,6 +114,7 @@ + diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/API/CurationEntryController.cs b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/API/CurationEntryController.cs new file mode 100644 index 0000000000..1cef70d76f --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/API/CurationEntryController.cs @@ -0,0 +1,459 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using System.Web.Http; +using BExIS.App.Bootstrap.Attributes; +using BExIS.Dlm.Entities.Curation; +using BExIS.Dlm.Entities.Data; +using BExIS.Dlm.Services.Curation; +using BExIS.Dlm.Services.Data; +using BExIS.Modules.Dcm.UI.Models.Curation; +using BExIS.Security.Entities.Subjects; +using BExIS.Security.Services.Authorization; +using BExIS.Security.Services.Subjects; +using BExIS.Utils.Config.Configurations; +using BExIS.Utils.Route; +using BExIS.Xml.Helpers; +using NHibernate.Linq; +using NHibernate.Util; +using Vaiona.Web.Mvc.Modularity; + +namespace BExIS.Modules.Dcm.UI.Controllers +{ + public class CurationEntryController : ApiController + { + private XmlDatasetHelper xmlDatasetHelper = new XmlDatasetHelper(); + + private class AnonymizedCuration + { + public List AnonymizedCurationEntries { get; } + public List AnonymizedCurationUsers { get; } + + public AnonymizedCuration(IEnumerable curationEntries, User user) + { + bool userIsCurator = CurationEntry.GetCurationUserType(user, GetCurationGroupName()).Equals(CurationUserType.Curator); + + // requesting user gets added to the set of seen users + var userSet = new HashSet() { user.Id }; + var userList = new List(); + + AnonymizedCurationEntries = new List(); + + // extract all users out of the curationEntries + // and create the list of curationEntryModels + foreach (var curationEntry in curationEntries) + { + // add creator of each entry + if (!userSet.Contains(curationEntry.Creator.Id)) + { + userSet.Add(curationEntry.Creator.Id); + userList.Add(curationEntry.Creator); + } + // The users (who are not curators) will not receive the description of the status entry item + if (!userIsCurator && curationEntry.Type.Equals(CurationEntryType.StatusEntryItem)) + { + AnonymizedCurationEntries.Add(new CurationEntryModel(curationEntry) + { + Description = string.Empty, + Notes = new List() + }); + } + else + { + // add the curationEntryModel + AnonymizedCurationEntries.Add(new CurationEntryModel(curationEntry)); + } + // add the users from the notes to the seen users + foreach (var note in curationEntry.Notes) + { + if (!userSet.Contains(note.User.Id)) + { + userSet.Add(note.User.Id); + userList.Add(note.User); + } + } + } + + // remove requesting user because hes not in the user list + // (unnecessary for now but should be done for other future checks) + userSet.Remove(user.Id); + + // order the users by name for consistency + // always put the requesting user on the first place + var sortedUsers = userList.OrderBy(u => u.DisplayName).ToList(); + sortedUsers = sortedUsers.Prepend(user).ToList(); + + // convert the user list to new userIds from 1 to n + var userMap = new Dictionary(); + for (var i = 0; i < sortedUsers.Count; i++) userMap.Add(sortedUsers[i].Id, new CurationUserModel(sortedUsers[i], i + 1, GetCurationUserType(sortedUsers[i]))); + + // replace original userIds with the anonymized ones + foreach (var curationEntry in AnonymizedCurationEntries) + { + curationEntry.CreatorId = userMap[curationEntry.CreatorId].Id; + foreach (var note in curationEntry.Notes) + { + note.UserId = userMap[note.UserId].Id; + } + } + + AnonymizedCurationUsers = userMap.Values.ToList(); + } + } + + private static CurationEntryModel AnonymizeCurationEntryModel(CurationEntryModel knownCurationEntry, CurationEntry curationEntry, User user) + { + // create the CurationEntryModel from the CurationModel and replace the userId + var newCurationEntry = new CurationEntryModel(curationEntry) + { + CreatorId = knownCurationEntry.CreatorId + }; + + // map each note to its user + var noteUserMap = new Dictionary(); + foreach (var knownNote in knownCurationEntry.Notes) + { + if (!noteUserMap.ContainsKey(knownNote.Id) && !knownNote.UserId.Equals(1)) + noteUserMap.Add(knownNote.Id, knownNote.UserId); + } + + // replace original userIds with the anonymized ones from the knownCurationEntry + foreach (var newNote in newCurationEntry.Notes) + { + if (newNote.UserId == user.Id) + newNote.UserId = 1; + else if (noteUserMap.TryGetValue(newNote.Id, out long value)) + newNote.UserId = value; + else + newNote.UserId = 0; + } + return newCurationEntry; + } + + private static List GetCurationLabels() + { + var curationLabels = ModuleManager.GetModuleSettings("DDM").GetValueByKey("curation")?.CurationLabels; + return curationLabels == null ? new List() : curationLabels; + } + + private static String GetCurationGroupName() + { + var groupName = ModuleManager.GetModuleSettings("DDM").GetValueByKey("curatorsGroupName").ToString(); + if (string.IsNullOrEmpty(groupName)) + { + return "curator"; + } + return groupName; + } + + public static CurationUserType GetCurationUserType(User user) + { + var curationGroupName = GetCurationGroupName(); + if (user.Groups.Any(g => g.Name.Equals(curationGroupName, StringComparison.CurrentCultureIgnoreCase))) + { + return CurationUserType.Curator; + } + return CurationUserType.User; + } + + private static IEnumerable GetGreetingTemplates() + { + // this should be replaced by a configuration or derived from other source in the future + var templateList = new List() { + new CurationTemplateModel("Example Greeting Name 1", "Example greeting content 1"), + new CurationTemplateModel("Example Greeting Name 2", "Example greeting content 2") + }; + return templateList; + } + + private static IEnumerable GetTaskListTemplates() + { + // this should be replaced by a configuration or derived from other source in the future + var templateList = new List() { + new CurationTemplateModel("Example TaskList Name 1", "Example tasklist content 1"), + new CurationTemplateModel("Example TaskList Name 2", "Example tasklist content 2") + }; + return templateList; + } + + private static List GetCurationEntryTypes() + { + // this should be replaced by a configuration or derived from other source in the future + return Enum.GetValues(typeof(CurationEntryType)).Cast().ToList(); + } + + private static int userRights(long userId, long datasetId) + { + int rights = 0; + using (EntityPermissionManager entityPermissionManager = new EntityPermissionManager()) + { + rights = entityPermissionManager.GetEffectiveRightsAsync(userId, datasetId).Result; + } + return rights; + } + + [BExISApiAuthorize] + [JsonNetFilter] + [GetRoute("api/datasets/{datasetid}/curation")] + public async Task Get(long datasetid) + { + if (datasetid == 0) return Request.CreateErrorResponse(HttpStatusCode.PreconditionFailed, "id should be greater than 0"); + + // get user or respond with error + if (!ActionContext.ControllerContext.RouteData.Values.TryGetValue("user", out object userObject) || !(userObject is User user)) + return Request.CreateErrorResponse(HttpStatusCode.NotFound, "User not found"); + + // check if user has rights to access the dataset + //if (!(userRights(user.Id, datasetid) > 0)) + // return Request.CreateErrorResponse(HttpStatusCode.PreconditionFailed, "User has no rights to access the dataset"); + + CurationModel curationModel; + + using (var curationManager = new CurationManager()) + using (var datasetManager = new DatasetManager()) + using (var userManager = new UserManager()) + { + if (datasetManager.GetDataset(datasetid) == null) + return Request.CreateErrorResponse(HttpStatusCode.NotFound, "Dataset not found"); + + var userWithGroups = userManager.Users + .Where(u => u.Id == user.Id) + .Fetch(u => u.Groups) + .SingleOrDefault(); + + var curationEntries = curationManager.CurationEntryRepository.Get().Where(c => c.Dataset != null && c.Dataset.Id == datasetid).ToList(); + var datasetVersion = datasetManager.GetDatasetLatestVersion(datasetid); + + var curationLabels = GetCurationLabels(); + + var anonymizedCurationEntries = new AnonymizedCuration(curationEntries, userWithGroups); + + var curationEntryTypes = GetCurationEntryTypes(); + + var greetingTemplates = GetGreetingTemplates(); + var taskListTemplates = GetTaskListTemplates(); + + bool userIsCurator = CurationEntry.GetCurationUserType(userWithGroups, GetCurationGroupName()).Equals(CurationUserType.Curator); + + curationModel = new CurationModel( + datasetid, + datasetVersion != null ? datasetVersion.Title : string.Empty, + datasetVersion != null ? datasetVersion.Timestamp : DateTime.MinValue, + anonymizedCurationEntries.AnonymizedCurationEntries, + anonymizedCurationEntries.AnonymizedCurationUsers, + userIsCurator ? curationLabels : new List(), + curationEntryTypes, + userIsCurator ? greetingTemplates : new List(), + userIsCurator ? taskListTemplates : new List() + ); + } + + var response = Request.CreateResponse(HttpStatusCode.OK, curationModel); + return response; + } + + [BExISApiAuthorize] + [JsonNetFilter] + [GetRoute("api/curationentries")] + public async Task Get() + { + // get user or respond with error + if (!ActionContext.ControllerContext.RouteData.Values.TryGetValue("user", out object userObject) || !(userObject is User user)) + return Request.CreateErrorResponse(HttpStatusCode.PreconditionFailed, "User not found"); + + // get CurationEntries + using (var curationManager = new CurationManager()) + using (var datasetManager = new DatasetManager()) + { + var datasets = datasetManager.DatasetRepo.Get(); + var versions = datasetManager.DatasetVersionRepo.Get(); + var curationEntries = curationManager.CurationEntryRepository.Get().Where(ce => ce.Dataset != null); + + var query = from d in datasets + join v in versions + on d.Id equals v.Dataset.Id into versionGroup + join ce in curationEntries + on d.Id equals ce.Dataset.Id into curationGroup + select new + { + DatasetId = d.Id, + DatasetName = versionGroup + .Where(v => v.Status == DatasetVersionStatus.CheckedIn) + .OrderByDescending(v => v.Timestamp) + .Select(v => v.Title) + .FirstOrDefault() ?? string.Empty, + StatusEntry = curationGroup + .FirstOrDefault(x => x.Type == CurationEntryType.StatusEntryItem), + AllEntries = curationGroup, + FilteredEntries = curationGroup + .Where(e => e.Type != CurationEntryType.StatusEntryItem && e.Type != CurationEntryType.None) + } into temp + select new + { + temp.DatasetId, + temp.DatasetName, + notesComments = temp.StatusEntry != null && temp.StatusEntry.Notes != null + ? temp.StatusEntry.Notes.Select(n => n.Comment).ToList() + : new List(), + CurationStarted = temp.StatusEntry != null, + UserIsDone = temp.StatusEntry != null ? temp.StatusEntry.UserIsDone : false, + IsApproved = temp.StatusEntry != null ? temp.StatusEntry.IsApproved : false, + LastChangeDatetime_Curator = temp.AllEntries.Any() + ? temp.AllEntries.Max(e => e.LastChangeDatetime_Curator) + : (DateTime?)null, + LastChangeDatetime_User = temp.AllEntries.Any() + ? temp.AllEntries.Max(e => e.LastChangeDatetime_User) + : (DateTime?)null, + Count_UserIsDone_True_IsApproved_True = temp.FilteredEntries + .Count(e => e.UserIsDone && e.IsApproved), + Count_UserIsDone_True_IsApproved_False = temp.FilteredEntries + .Count(e => e.UserIsDone && !e.IsApproved), + Count_UserIsDone_False_IsApproved_True = temp.FilteredEntries + .Count(e => !e.UserIsDone && e.IsApproved), + Count_UserIsDone_False_IsApproved_False = temp.FilteredEntries + .Count(e => !e.UserIsDone && !e.IsApproved) + }; + + var curationLabels = GetCurationLabels(); + + return Request.CreateResponse(HttpStatusCode.OK, new { datasets = query.ToList(), curationLabels }); + } + } + + // PUT: api/data/5 + [BExISApiAuthorize] + [PutRoute("api/curationentries")] + [JsonNetFilter] + [HttpPut] + public async Task Put(CurationEntryModel curationEntryModel) + { + if (curationEntryModel == null) return Request.CreateErrorResponse(HttpStatusCode.PreconditionFailed, "curationEntry is null"); + if (curationEntryModel.Id == 0) return Request.CreateErrorResponse(HttpStatusCode.PreconditionFailed,"id should be greater than 0"); + + // get user or respond with error + if (!ActionContext.ControllerContext.RouteData.Values.TryGetValue("user", out object userObject) || !(userObject is User user)) + return Request.CreateErrorResponse(HttpStatusCode.PreconditionFailed, "User not found"); + + List notes; + if (curationEntryModel.Notes == null || !curationEntryModel.Notes.Any()) + notes = new List(); + else + notes = curationEntryModel.Notes.Select(n => new CurationNote() { Id = n.Id, Comment = n.Comment }).ToList(); + + using (var curationManager = new CurationManager()) + using (var userManager = new UserManager()) + { + var c = curationManager.CurationEntries.FirstOrDefault(ce => ce.Id == curationEntryModel.Id); + if (c == null) return Request.CreateErrorResponse(HttpStatusCode.NotFound, "curationEntry not found"); + + var userWithGroups = userManager.Users + .Where(u => u.Id == user.Id) + .Fetch(u => u.Groups) + .SingleOrDefault(); + + // check if user has rights to access the dataset + //if (!(userRights(user.Id, c.Dataset.Id) > 0)) + // return Request.CreateErrorResponse(HttpStatusCode.PreconditionFailed, "User has no rights to access the dataset"); + bool userIsCurator = CurationEntry.GetCurationUserType(userWithGroups, GetCurationGroupName()).Equals(CurationUserType.Curator); + + var newCurationEntry = curationManager.Update( + curationEntryModel.Id, + curationEntryModel.Topic, + curationEntryModel.Type, + curationEntryModel.Name, + curationEntryModel.Description, + curationEntryModel.Solution, + curationEntryModel.Position, + curationEntryModel.Source, + notes, + curationEntryModel.UserIsDone, + curationEntryModel.IsApproved, + userWithGroups, + userIsCurator + ); + + var response = AnonymizeCurationEntryModel(curationEntryModel, newCurationEntry, userWithGroups); + + return Request.CreateResponse(HttpStatusCode.OK, response); + } + + } + + [BExISApiAuthorize] + [PostRoute("api/curationentries")] + [JsonNetFilter] + [HttpPost] + public async Task Post(CurationEntryModel curationEntryModel) + { + if (curationEntryModel == null) return Request.CreateErrorResponse(HttpStatusCode.PreconditionFailed, "curationEntry must not be null"); + if (curationEntryModel.Id > 0) return Request.CreateErrorResponse(HttpStatusCode.PreconditionFailed, "curationEntry id must be 0"); + if (curationEntryModel.DatasetId == 0) return Request.CreateErrorResponse(HttpStatusCode.PreconditionFailed, "datasetId should be greater than 0"); + + // get user or respond with error + if (!ActionContext.ControllerContext.RouteData.Values.TryGetValue("user", out object userObject) || !(userObject is User user)) + return Request.CreateErrorResponse(HttpStatusCode.PreconditionFailed, "User not found"); + + // check if user has rights to access the dataset + //if (!(userRights(user.Id, curationEntryModel.DatasetId) > 0)) + // return Request.CreateErrorResponse(HttpStatusCode.PreconditionFailed, "User has no rights to access the dataset"); + + using (var curationManager = new CurationManager()) + using (var userManager = new UserManager()) + { + var userWithGroups = userManager.Users + .Where(u => u.Id == user.Id) + .Fetch(u => u.Groups) + .SingleOrDefault(); + + var userIsCurator = CurationEntry.GetCurationUserType(userWithGroups, GetCurationGroupName()).Equals(CurationUserType.Curator); + + if (!CurationEntry.GetCurationUserType(userWithGroups, GetCurationGroupName()).Equals(CurationUserType.Curator)) + return Request.CreateErrorResponse(HttpStatusCode.PreconditionFailed, "User not permitted to create curation entries"); + + List notes; + if (curationEntryModel.Notes == null || !curationEntryModel.Notes.Any()) + notes = new List(); + else + + notes = curationEntryModel.Notes.Select(n => new CurationNote(userWithGroups, n.Comment, GetCurationUserType(userWithGroups))).ToList(); // all notes will be created for the current user + + if (curationEntryModel.Type == CurationEntryType.StatusEntryItem) + { + if (curationManager.CurationEntries.Any(ce => ce.Dataset.Id == curationEntryModel.DatasetId && ce.Type == CurationEntryType.StatusEntryItem)) + { + return Request.CreateErrorResponse(HttpStatusCode.Conflict, "A status entry item already exists for this dataset. Only one status entry item is allowed per dataset."); + } + } + else if (!curationManager.CurationEntries.Any(ce => ce.Dataset.Id == curationEntryModel.DatasetId)) + { + return Request.CreateErrorResponse(HttpStatusCode.Conflict, "No curation entry exists for this dataset. A status entry item is required before creating other curation entries."); + } + + var newCurationEntry = curationManager.Create( + curationEntryModel.Topic, + curationEntryModel.Type, + curationEntryModel.DatasetId, + curationEntryModel.Name, + curationEntryModel.Description, + curationEntryModel.Solution, + curationEntryModel.Position, + curationEntryModel.Source, + notes, + curationEntryModel.UserIsDone, + curationEntryModel.IsApproved, + userWithGroups, + userIsCurator + ); + + var response = AnonymizeCurationEntryModel(curationEntryModel, newCurationEntry, userWithGroups); + + return Request.CreateResponse(HttpStatusCode.OK, response); + } + + } + + } +} \ No newline at end of file diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/API/DataInController.cs b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/API/DataInController.cs index b72eaabbd1..d147ef1fd3 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/API/DataInController.cs +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/API/DataInController.cs @@ -13,7 +13,6 @@ using BExIS.Security.Services.Authorization; using BExIS.Security.Services.Subjects; using BExIS.Security.Services.Utilities; -using BExIS.Utils.Config; using BExIS.Utils.Data.Upload; using BExIS.Utils.Route; using BExIS.Utils.Upload; @@ -34,6 +33,7 @@ using System.Xml; using BExIS.Dim.Entities.Mappings; using Remotion.Linq.Parsing.Structure.NodeTypeProviders; +using BExIS.Utils.Config; namespace BExIS.Modules.Dcm.UI.Controllers { diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/API/DatasetInController.cs b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/API/DatasetInController.cs index fa40691785..299db46b14 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/API/DatasetInController.cs +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/API/DatasetInController.cs @@ -30,6 +30,7 @@ namespace BExIS.Modules.Dcm.UI.Controllers.API { + [RoutePrefix("api/dataset")] public class DatasetInController : ApiController { // POST api/ DatasetIn @@ -47,7 +48,8 @@ public class DatasetInController : ApiController /// /// HttpResponseMessage [BExISApiAuthorize] - [PostRoute("api/Dataset")] + [PostRoute("")] + [HttpPost] public HttpResponseMessage Post([FromBody] PostApiDatasetModel dataset) { @@ -133,7 +135,7 @@ public HttpResponseMessage Post([FromBody] PostApiDatasetModel dataset) datasetId = newDataset.Id; // add security - entityPermissionManager.CreateAsync(user.UserName, "Dataset", typeof(Dataset), newDataset.Id, Enum.GetValues(typeof(RightType)).Cast().ToList()); + entityPermissionManager.CreateAsync(user.UserName, entityTemplate.EntityType.Name, typeof(Dataset), newDataset.Id, Enum.GetValues(typeof(RightType)).Cast().ToList()); //add title and description to the metadata @@ -184,21 +186,21 @@ public HttpResponseMessage Post([FromBody] PostApiDatasetModel dataset) } } - // PUT api//5 - [ApiExplorerSettings(IgnoreApi = true)] - [BExISApiAuthorize] - [PutRoute("api/Dataset")] - public void Put(int id, [FromBody] string value) - { - } - - // DELETE api//5 - [ApiExplorerSettings(IgnoreApi = true)] - [BExISApiAuthorize] - [DeleteRoute("api/Dataset")] - public void Delete(int id) - { - } + //// PUT api//5 + //[ApiExplorerSettings(IgnoreApi = true)] + //[BExISApiAuthorize] + //[PutRoute("api/Dataset")] + //public void Put(int id, [FromBody] string value) + //{ + //} + + //// DELETE api//5 + //[ApiExplorerSettings(IgnoreApi = true)] + //[BExISApiAuthorize] + //[DeleteRoute("api/Dataset")] + //public void Delete(int id) + //{ + //} private XmlDocument setSystemValuesToMetadata(long datasetid, long version, long metadataStructureId, XmlDocument metadata, bool newDataset) { diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/API/MetadataInController.cs b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/API/MetadataInController.cs index f1fcfcf3da..252e6949b7 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/API/MetadataInController.cs +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/API/MetadataInController.cs @@ -73,6 +73,7 @@ public async Task Put(int id) var request = Request.CreateResponse(); User user = null; string error = ""; + string comment = "Update via API"; DatasetManager datasetManager = new DatasetManager(); UserManager userManager = new UserManager(); @@ -207,6 +208,12 @@ public async Task Put(int id) { return Request.CreateErrorResponse(HttpStatusCode.ExpectationFailed, "the json does not have the expected structure"); } + + var commentProperty = metadataJson.Property("@comment"); + if (commentProperty != null && commentProperty.Value != null && commentProperty.Value.ToString().Length > 0) + { + comment = commentProperty.Value.ToString(); + } } else { @@ -254,12 +261,12 @@ public async Task Put(int id) workingCopy.ModificationInfo = new EntityAuditInfo() { Performer = user.UserName, - Comment = "via API", - ActionType = AuditActionType.Create + Comment = "Metadata", + ActionType = AuditActionType.Create, }; datasetManager.EditDatasetVersion(workingCopy, null, null, null); - datasetManager.CheckInDataset(id, "via API", user.Name, ViewCreationBehavior.None); + datasetManager.CheckInDataset(id, comment, user.Name, ViewCreationBehavior.None); } LoggerFactory.LogData(id.ToString(), typeof(Dataset).Name, Vaiona.Entities.Logging.CrudState.Created); diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/CreateController.cs b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/CreateController.cs index 10b7280bdc..0823b5a8db 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/CreateController.cs +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/CreateController.cs @@ -18,7 +18,6 @@ using BExIS.Security.Services.Subjects; using BExIS.Security.Services.Utilities; using BExIS.UI.Helpers; -using BExIS.Utils.Config; using BExIS.Xml.Helpers; using System; using System.Collections.Generic; @@ -29,6 +28,7 @@ using System.Xml; using System.Xml.XPath; using Vaiona.Entities.Common; +using BExIS.Utils.Config; namespace BExIS.Modules.Dcm.UI.Controllers { @@ -244,6 +244,7 @@ public JsonResult Get(long id) [JsonNetFilter] [HttpPost] + [CustomValidateAntiForgeryToken] public JsonResult Create(CreateModel data) { if (data == null) return Json(false); @@ -369,7 +370,7 @@ public JsonResult Create(CreateModel data) dm.EditDatasetVersion(workingCopy, null, null, null); // close check out - dm.CheckInDataset(datasetId, "Init creation a " + entityTemplate.EntityType.Name.ToLower() + " based on " + entityTemplate.Name.ToLower(), GetUsernameOrDefault(), ViewCreationBehavior.None, TagType.Copy); + dm.CheckInDataset(datasetId, entityTemplate.EntityType.Name + " created based on category: " + entityTemplate.Name, GetUsernameOrDefault(), ViewCreationBehavior.None, TagType.Copy); } #endregion update version diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/EntityTemplatesController.cs b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/EntityTemplatesController.cs index 5de8da8996..a2123248fc 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/EntityTemplatesController.cs +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/EntityTemplatesController.cs @@ -77,6 +77,7 @@ public JsonResult Delete(long id) [JsonNetFilter] [HttpPost] + [CustomValidateAntiForgeryToken] public JsonResult Update(EntityTemplateModel entityTemplate) { using (var entityTemplateManager = new EntityTemplateManager()) diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/FormController.cs b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/FormController.cs index b3267bd911..2a0d6ab475 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/FormController.cs +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/FormController.cs @@ -177,10 +177,15 @@ public ActionResult LoadMetadataByVersion(long entityId, int version=-1, bool lo DatasetVersion dsv = null; - if (version == -1) + if (version == -1) // latest { dsv = datasetManager.GetDatasetLatestVersion(entityId); } + else if (version == 0 || dataset.Status == DatasetStatus.Deleted) // check may deleted + { + if(dataset.Status == DatasetStatus.Deleted) + dsv = datasetManager.GetDeletedDatasetLatestVersion(entityId); + } else { dsv = datasetManager.GetDatasetVersion(entityId, version); @@ -1555,6 +1560,11 @@ public JsonResult UpdateSimpleUsageWithParty(string xpath, long partyId,long ent { try { + if (!XmlUtility.IsSafeXPath(xpath)) + { + return Json(false, JsonRequestBehavior.AllowGet); + } + AddXmlAttribute(xpath, "partyid", partyId.ToString(), entityId); return Json(true, JsonRequestBehavior.AllowGet); @@ -2059,13 +2069,11 @@ private StepInfo LoadStepsBasedOnUsage(BaseUsage usage, StepInfo current, string if (usage is MetadataPackageUsage) { keyValueDic.Add("type", BExIS.Xml.Helpers.XmlNodeType.MetadataPackageUsage.ToString()); - //elements = XmlUtility.GetXElementsByAttribute(usage.Label, keyValueDic, xMetadata).ToList(); parentXElement = XmlUtility.GetXElementByXPath(parent.XPath, xMetadata); } else { keyValueDic.Add("type", BExIS.Xml.Helpers.XmlNodeType.MetadataAttributeUsage.ToString()); - //elements = XmlUtility.GetXElementsByAttribute(usage.Label, keyValueDic, xMetadata, parentXpath).ToList(); parentXElement = XmlUtility.GetXElementByXPath(parent.XPath, xMetadata); } @@ -3100,6 +3108,11 @@ private void UpdateAttribute(BaseUsage parentUsage, int packageNumber, BaseUsage //ToDo really said function, but cant find a other solution for now private void AddXmlAttribute(string xpath, string attrName, string attrValue,long entityId) { + if(XmlUtility.IsSafeXPath(xpath) == false) + { + throw new Exception("The provided xpath is not safe."); + } + TaskManager = FormHelper.GetTaskManager(entityId); XDocument metadataXml = getMetadata(TaskManager); diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/AttachmentUploadController.cs b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/AttachmentUploadController.cs index 23c60ef0c6..51cb1c789b 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/AttachmentUploadController.cs +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/AttachmentUploadController.cs @@ -89,6 +89,7 @@ public JsonResult Load(long id, int version) } [HttpPost] + [CustomValidateAntiForgeryToken] public JsonResult Upload(long id) { // load edit dataset cache @@ -160,6 +161,7 @@ public JsonResult Upload(long id) } [HttpPost] + [CustomValidateAntiForgeryToken] public JsonResult RemoveFile(long id, BExIS.UI.Hooks.Caches.FileInfo file) { // load edit dataset cache @@ -224,6 +226,7 @@ public JsonResult RemoveFile(long id, BExIS.UI.Hooks.Caches.FileInfo file) } [HttpPost] + [CustomValidateAntiForgeryToken] public JsonResult SaveFileDescription(long id, BExIS.UI.Hooks.Caches.FileInfo file, string description) { HookManager hookManager = new HookManager(); diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/DataController.cs b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/DataController.cs index cd6cb9dd90..ebcefb2df8 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/DataController.cs +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/DataController.cs @@ -106,6 +106,7 @@ public JsonResult Load(long id, int version) } [HttpPost] + [CustomValidateAntiForgeryToken] public JsonResult RemoveFile(long id, FileInfo file) { // remove file from server @@ -141,6 +142,7 @@ public JsonResult RemoveFile(long id, FileInfo file) /// /// [HttpPost] + [CustomValidateAntiForgeryToken] public JsonResult RevertFile(long id, FileInfo file) { // remove file from server @@ -184,6 +186,7 @@ public JsonResult RevertFile(long id, FileInfo file) /// /// [HttpPost] + [CustomValidateAntiForgeryToken] public JsonResult SaveFileDescription(long id, BExIS.UI.Hooks.Caches.FileInfo file) { // remove file from cache diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/EntityReferenceController.cs b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/EntityReferenceController.cs index eaf690c1fb..9d50c85025 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/EntityReferenceController.cs +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/EntityReferenceController.cs @@ -102,6 +102,7 @@ public ActionResult Create(long sourceId, long sourceTypeId) } [HttpPost] + [CustomValidateAntiForgeryToken] public ActionResult Create(CreateSimpleReferenceModel model) { EntityReferenceHelper helper = new EntityReferenceHelper(); diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/FileUploadController.cs b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/FileUploadController.cs index 76f77b3732..78723e68e3 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/FileUploadController.cs +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/FileUploadController.cs @@ -200,6 +200,7 @@ public JsonResult Upload(long id) } [HttpPost] + [CustomValidateAntiForgeryToken] public JsonResult RemoveFile(long id, BExIS.UI.Hooks.Caches.FileInfo file) { // remove file from server @@ -227,6 +228,7 @@ public JsonResult RemoveFile(long id, BExIS.UI.Hooks.Caches.FileInfo file) } [HttpPost] + [CustomValidateAntiForgeryToken] public JsonResult SaveFileDescription(long id, BExIS.UI.Hooks.Caches.FileInfo file, string description) { // remove file from cache diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/SubmitController.cs b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/SubmitController.cs index 39c9629e5c..b2515a7093 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/SubmitController.cs +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Hooks/SubmitController.cs @@ -80,7 +80,7 @@ public JsonResult Submit(long id, int version = 0) DataASyncUploadHelper asyncUploadHelper = new DataASyncUploadHelper(cache, log, "dataset"); asyncUploadHelper.User = BExISAuthorizeHelper.GetUserFromAuthorization(HttpContext); - asyncUploadHelper.RunningASync = true; + asyncUploadHelper.RunningASync = false; if (asyncUploadHelper.RunningASync && model.HasStructrue) //async { diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Legacy/CreateDatasetController.cs b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Legacy/CreateDatasetController.cs index b328f326fa..10bc1ae15f 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Legacy/CreateDatasetController.cs +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Legacy/CreateDatasetController.cs @@ -24,7 +24,6 @@ using BExIS.Security.Services.Utilities; using BExIS.UI.Helpers; using BExIS.UI.Models; -using BExIS.Utils.Config; using BExIS.Utils.Data.Upload; using BExIS.Utils.Extensions; using BExIS.Xml.Helpers; @@ -46,6 +45,7 @@ using Vaiona.Web.Mvc.Models; using Vaiona.Web.Mvc.Modularity; using BExIS.Utils.Data.Helpers; +using BExIS.Utils.Config; namespace BExIS.Modules.Dcm.UI.Controllers { diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Legacy/HelpController.cs b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Legacy/HelpController.cs index e4f9090f40..68e5bcd462 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Legacy/HelpController.cs +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Legacy/HelpController.cs @@ -1,5 +1,4 @@ -using BExIS.Utils.Config; -using BExIS.Utils.Helpers; +using BExIS.Utils.Helpers; using System; using System.Web.Mvc; using Vaiona.Web.Mvc.Modularity; diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Legacy/OldSubmitController.cs b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Legacy/OldSubmitController.cs deleted file mode 100644 index 2dc5af3ab0..0000000000 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Legacy/OldSubmitController.cs +++ /dev/null @@ -1,369 +0,0 @@ -using BExIS.Dcm.UploadWizard; -using BExIS.Dlm.Entities.Administration; -using BExIS.Dlm.Entities.Data; -using BExIS.Dlm.Entities.DataStructure; -using BExIS.Dlm.Services.Administration; -using BExIS.Dlm.Services.Data; -using BExIS.Dlm.Services.DataStructure; -using BExIS.Dlm.Services.MetadataStructure; -using BExIS.Modules.Dcm.UI.Models; -using BExIS.Security.Entities.Authorization; -using BExIS.Security.Services.Authorization; -using BExIS.Security.Services.Objects; -using BExIS.UI.Helpers; -using BExIS.Utils.Data.Upload; -using BExIS.Xml.Helpers; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Web.Mvc; -using System.Web.Routing; -using System.Xml; -using Vaiona.Persistence.Api; -using Vaiona.Utils.Cfg; -using Vaiona.Web.Extensions; -using Vaiona.Web.Mvc; -using Vaiona.Web.Mvc.Models; - -namespace BExIS.Modules.Dcm.UI.Controllers -{ - public class OldSubmitController : BaseController - { - // - // GET: /Collect/Home/ - - private List ids = new List(); - private FileStream Stream; - private TaskManager TaskManager; - - private XmlDatasetHelper xmlDatasetHelper = new XmlDatasetHelper(); - - public ActionResult Index() - { - ViewBag.Title = PresentationModel.GetViewTitleForTenant("Upload Data", Session.GetTenant()); - return View(); - } - - #region Upload Wizard - - public ActionResult UploadWizard(DataStructureType type, long datasetid = 0) - { - // if dataset id is set it possible to check the entity - //get the researchobject (cuurently called dataset) to get the id of a metadata structure - Dataset researcobject = this.GetUnitOfWork().GetReadOnlyRepository().Get(datasetid); - string defaultAction = "Upload"; - long entityId = datasetid; - if (researcobject != null) - { - long metadataStrutcureId = researcobject.MetadataStructure.Id; - - using (MetadataStructureManager metadataStructureManager = new MetadataStructureManager()) - { - string entityName = xmlDatasetHelper.GetEntityNameFromMetadatStructure(metadataStrutcureId, metadataStructureManager); - string entityType = xmlDatasetHelper.GetEntityTypeFromMetadatStructure(metadataStrutcureId, metadataStructureManager); - - //ToDo in the entity table there must be the information - using (EntityManager entityManager = new EntityManager()) - { - var entity = entityManager.Entities.Where(e => e.Name.Equals(entityName)).FirstOrDefault(); - - string moduleId = ""; - Tuple action = null; - - if (entity != null && entity.Extra != null) - { - var node = entity.Extra.SelectSingleNode("extra/modules/module"); - - if (node != null) moduleId = node.Attributes["value"].Value; - - string modus = "upload"; - - action = EntityViewerHelper.GetEntityViewAction(entityName, moduleId, modus); - } - if (action == null) RedirectToAction(defaultAction, new { type, entityId }); - - try - { - return RedirectToAction(action.Item3, action.Item2, new { area = action.Item1, type, entityId }); - } - catch - { - return RedirectToAction(defaultAction, new { type, entityId }); - } - } - } - } - - return RedirectToAction(defaultAction, new { type, entityId }); - } - - public ActionResult Upload(DataStructureType type, long entityId = 0) - { - ViewBag.Title = PresentationModel.GetViewTitleForTenant("Upload Data", Session.GetTenant()); - - Session["TaskManager"] = null; - - if (TaskManager == null) TaskManager = (TaskManager)Session["TaskManager"]; - - if (TaskManager == null) - { - try - { - string path = ""; - - if (type == DataStructureType.Unstructured) - path = Path.Combine(AppConfiguration.GetModuleWorkspacePath("DCM"), "SubmitUnstructuredDataTaskInfo.xml"); - - if (type == DataStructureType.Structured) - path = Path.Combine(AppConfiguration.GetModuleWorkspacePath("DCM"), "SubmitTaskInfo.xml"); - - XmlDocument xmlTaskInfo = new XmlDocument(); - xmlTaskInfo.Load(path); - - Session["TaskManager"] = TaskManager.Bind(xmlTaskInfo); - - TaskManager = (TaskManager)Session["TaskManager"]; - TaskManager.AddToBus(TaskManager.DATASTRUCTURE_TYPE, type); - - if (entityId > 0) TaskManager.AddToBus(TaskManager.DATASET_ID, entityId); - - Session["TaskManager"] = TaskManager; - } - catch (Exception e) - { - ModelState.AddModelError(String.Empty, e.Message); - } - - Session["Filestream"] = Stream; - - TaskManager = (TaskManager)Session["TaskManager"]; - - // get Lists of Dataset and Datastructure - Session["DatasetVersionViewList"] = LoadDatasetVersionViewList(type); - Session["DataStructureViewList"] = LoadDataStructureViewList(type); - Session["ResearchPlanViewList"] = LoadResearchPlanViewList(); - - // setparameters - SetParametersToTaskmanager(entityId); - } - - return View("UploadWizard", (TaskManager)Session["TaskManager"]); - } - - #region UploadNavigation - - [HttpPost] - public ActionResult RefreshNavigation() - { - TaskManager = (TaskManager)Session["TaskManager"]; - - return PartialView("_uploadWizardNav", TaskManager); - } - - [HttpPost] - public ActionResult RefreshTaskList() - { - TaskManager = (TaskManager)Session["TaskManager"]; - - return PartialView("_taskListView", TaskManager.GetStatusOfStepInfos()); - } - - #endregion UploadNavigation - - #region Finish - - [HttpGet] - public ActionResult FinishUpload() - { - TaskManager = (TaskManager)Session["TaskManager"]; - - long datasetId = (long)TaskManager.Bus[TaskManager.DATASET_ID]; - Session["TaskManager"] = null; - - return ShowData(datasetId); - } - - #endregion Finish - - #region Navigation options - - public ActionResult CancelUpload() - { - TaskManager = (TaskManager)Session["Taskmanager"]; - - DataStructureType type = new DataStructureType(); - - if (TaskManager.Bus.ContainsKey(TaskManager.DATASTRUCTURE_TYPE)) - { - type = (DataStructureType)TaskManager.Bus[TaskManager.DATASTRUCTURE_TYPE]; - } - - Session["Taskmanager"] = null; - TaskManager = null; - - return RedirectToAction("UploadWizard", "Submit", new RouteValueDictionary { { "area", "DCM" }, { "type", type } }); - } - - public ActionResult ShowData(long id) - { - return RedirectToAction("ShowData", "Data", new RouteValueDictionary { { "area", "DDM" }, { "id", id } }); - } - - public ActionResult ShowDashboard() - { - return RedirectToAction("Index", "Dashboard", new RouteValueDictionary { { "area", "DDM" } }); - } - - #endregion Navigation options - - #region Helper functions - - // chekc if user exist - // if true return usernamem otherwise "DEFAULT" - public string GetUsernameOrDefault() - { - string username = string.Empty; - try - { - username = HttpContext.User.Identity.Name; - } - catch { } - - return !string.IsNullOrWhiteSpace(username) ? username : "DEFAULT"; - } - - public List LoadDatasetVersionViewList(DataStructureType dataStructureType) - { - EntityPermissionManager entityPermissionManager = new EntityPermissionManager(); - DataStructureManager dataStructureManager = new DataStructureManager(); - DatasetManager dm = new DatasetManager(); - - try - { - List datasetIds = entityPermissionManager.GetKeys(GetUsernameOrDefault(), "Dataset", typeof(Dataset), RightType.Write).Result.ToList(); - - List tempStructured = new List(); - List tempUnStructured = new List(); - - var DatasetVersions = dm.GetDatasetLatestVersions(datasetIds, false); - - foreach (var dsv in DatasetVersions) - { - if (dsv.Dataset.DataStructure.Self.GetType().Equals(typeof(StructuredDataStructure))) - { - tempStructured.Add(new ListViewItem(dsv.Dataset.Id, dsv.Title)); - } - else - { - tempUnStructured.Add(new ListViewItem(dsv.Dataset.Id, dsv.Title)); - } - } - - if (dataStructureType.Equals(DataStructureType.Structured)) - { - return tempStructured.OrderBy(p => p.Title).ToList(); - } - else - { - return tempUnStructured.OrderBy(p => p.Title).ToList(); - } - } - finally - { - entityPermissionManager.Dispose(); - dataStructureManager.Dispose(); - dm.Dispose(); - } - } - - public List LoadDataStructureViewList(DataStructureType dataStructureType) - { - DataStructureManager dsm = new DataStructureManager(); - - try - { - List temp = new List(); - - foreach (var datasStructure in dsm.GetStructuredDataStructuresAsKVP()) - { - string title = datasStructure.Value; - - temp.Add(new ListViewItem(datasStructure.Key, title)); - } - - return temp.OrderBy(p => p.Title).ToList(); - } - finally - { - dsm.Dispose(); - } - } - - public List LoadResearchPlanViewList() - { - using (ResearchPlanManager rpm = new ResearchPlanManager()) - { - List temp = new List(); - - foreach (ResearchPlan researchPlan in rpm.Repo.Get()) - { - string title = researchPlan.Title; - - temp.Add(new ListViewItem(researchPlan.Id, title)); - } - - return temp.OrderBy(p => p.Title).ToList(); - } - } - - private void SetParametersToTaskmanager(long datasetId) - { - if (TaskManager == null) - { - TaskManager = (TaskManager)Session["TaskManager"]; - } - - #region set dataset id & dataset title - - if (datasetId > 0) - { - try - { - long datasetid = Convert.ToInt64(datasetId); - TaskManager.AddToBus(TaskManager.DATASET_ID, datasetid); - - // get title - using (DatasetManager dm = new DatasetManager()) - { - string title = ""; - // is checkedIn? - if (dm.IsDatasetCheckedIn(datasetid)) - { - var dsv = dm.GetDatasetLatestVersion(datasetid); - title = dsv.Title; - } - - TaskManager.AddToBus(TaskManager.DATASET_TITLE, title); - } - } - catch (Exception ex) - { - throw ex; - } - } - - #endregion set dataset id & dataset title - } - - #endregion Helper functions - - #endregion Upload Wizard - } - - public class UpdateNameModel - { - public string Name { get; set; } - public IEnumerable Numbers { get; set; } - } -} \ No newline at end of file diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Legacy/PushController.cs b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Legacy/PushController.cs deleted file mode 100644 index 8866fa877a..0000000000 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Controllers/Legacy/PushController.cs +++ /dev/null @@ -1,147 +0,0 @@ -using BExIS.IO; -using BExIS.Modules.Dcm.UI.Models.Push; -using BExIS.Utils.Data.Upload; -using BExIS.Utils.Upload; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Web; -using System.Web.Mvc; -using Vaiona.Utils.Cfg; -using Vaiona.Web.Extensions; -using Vaiona.Web.Mvc.Models; - -namespace BExIS.Modules.Dcm.UI.Controllers -{ - public class PushController : Controller - { - public ActionResult Index() - { - ViewBag.Title = PresentationModel.GetViewTitleForTenant("Push Big File", this.Session.GetTenant()); - - Session["Files"] = null; - - // get max file name length - // get max file lenght - var dataPath = AppConfiguration.DataPath; //Path.Combine(AppConfiguration.WorkspaceRootPath, "Data"); - var storepath = Path.Combine(dataPath, "Temp", GetUsernameOrDefault()); - - ViewBag.maxFileNameLength = 260 - storepath.Length - 2; - - return View(LoadDefaultModel()); - } - - public ActionResult Delete(string path) - { - ViewBag.Title = PresentationModel.GetViewTitleForTenant("Push Big File", this.Session.GetTenant()); - FileHelper.Delete(path); - - return View("Index", LoadDefaultModel()); - } - - public ActionResult Reload() - { - return View("_fileListView", GetServerFileList()); - } - - public ActionResult Remove() - { - return Content(""); - } - - [HttpPost] - public ActionResult ProcessSubmit(IEnumerable attachments) - { - ViewBag.Title = PresentationModel.GetViewTitleForTenant("Push Big File", this.Session.GetTenant()); - // The Name of the Upload component is "attachments" - if (attachments != null) - { - Session["FileInfos"] = attachments; - uploadFiles(attachments); - } - - var dataPath = AppConfiguration.DataPath; //Path.Combine(AppConfiguration.WorkspaceRootPath, "Data"); - var storepath = Path.Combine(dataPath, "Temp", GetUsernameOrDefault()); - - ViewBag.maxFileNameLength = 260 - storepath.Length - 2; - // Redirect to a view showing the result of the form submission. - return View("Index", LoadDefaultModel()); - } - - /// - /// read filenames from datapath/Temp/UserName - /// - /// return a list with all names from FileStream in the folder - private List GetServerFileList() - { - var fileList = new List(); - - var userDataPath = Path.Combine(AppConfiguration.DataPath, "Temp", GetUsernameOrDefault()); - - // if folder not exist - if (!Directory.Exists(userDataPath)) - { - Directory.CreateDirectory(userDataPath); - } - - var dirInfo = new DirectoryInfo(userDataPath); - - foreach (var info in dirInfo.GetFiles()) - { - fileList.Add(new BasicFileInfo(info.Name, info.FullName, "", info.Extension, info.Length)); - } - - return fileList; - } - - #region helper - - // chekc if user exist - // if true return usernamem otherwise "DEFAULT" - private string GetUsernameOrDefault() - { - var username = string.Empty; - try - { - username = HttpContext.User.Identity.Name; - } - catch { } - - return !string.IsNullOrWhiteSpace(username) ? username : "DEFAULT"; - } - - public void uploadFiles(IEnumerable attachments) - { - var filemNames = ""; - - Debug.WriteLine("save starts"); - - foreach (var file in attachments) - { - var fileName = Path.GetFileName(file.FileName); - filemNames += fileName.ToString() + ","; - - var dataPath = AppConfiguration.DataPath; - var destinationPath = Path.Combine(dataPath, "Temp", GetUsernameOrDefault(), fileName); - - Debug.WriteLine("contentlength :" + file.ContentLength); - - file.SaveAs(destinationPath); - } - } - - private SendBigFilesToServerModel LoadDefaultModel() - { - var model = new SendBigFilesToServerModel - { - ServerFileList = GetServerFileList(), - SupportedFileExtentions = UploadHelper.GetExtentionList(DataStructureType.Unstructured, this.Session.GetTenant()), - // FileSize = this.Session.GetTenant().MaximumUploadSize - }; - - return model; - } - - #endregion helper - } -} \ No newline at end of file diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Helpers/DCMSeedDataGenerator.cs b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Helpers/DCMSeedDataGenerator.cs index 4ce6330ec9..5892e28566 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Helpers/DCMSeedDataGenerator.cs +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Helpers/DCMSeedDataGenerator.cs @@ -198,6 +198,7 @@ public void GenerateSeedData() operationManager.Create("Api", "Dataset", "*", DatasetCreationFeature); operationManager.Create("Api", "MetadataIn", "*", DatasetCreationFeature); operationManager.Create("Api", "Metadata", "*", DatasetCreationFeature); + operationManager.Create("Api", "CurationEntry", "*", null); operationManager.Create("DCM", "Edit", "*"); operationManager.Create("DCM", "View", "*"); @@ -285,9 +286,9 @@ public void GenerateSeedData() if (!metadataStructureManager.Repo.Get().Any(m => m.Name.Equals("Basic ABCD"))) { string titleXPath = - "Metadata/Metadata/MetadataType/Description/DescriptionType/Representation/MetadataDescriptionRepr/Title/TitleType"; + "Metadata/Metadata/ContentMetadata/Description/DescriptionXmlSchemaComplexType/Representation/MetadataDescriptionRepr/Title/String255"; string descriptionXpath = - "Metadata/Metadata/MetadataType/Description/DescriptionType/Representation/MetadataDescriptionRepr/Details/DetailsType"; + "Metadata/Metadata/ContentMetadata/Description/DescriptionXmlSchemaComplexType/Representation/MetadataDescriptionRepr/Details/String"; ImportSchema("Basic ABCD", "ABCD_2.06.XSD", "DataSet", entity.Name, entity.EntityType.FullName, titleXPath, descriptionXpath); @@ -297,8 +298,8 @@ public void GenerateSeedData() if (!metadataStructureManager.Repo.Get().Any(m => m.Name.Equals("GBIF"))) { - string titleXPath = "Metadata/Basic/BasicType/title/titleType"; - string descriptionXpath = "Metadata/abstract/abstractType/para/paraType"; + string titleXPath = "Metadata/Basic/BasicType/title/i18nString"; + string descriptionXpath = "Metadata/abstract/abstractXmlSchemaComplexType/para/paraDatatype_string"; ImportSchema("GBIF", "eml.xsd", "Dataset", entity.Name, entity.EntityType.FullName, titleXPath, descriptionXpath); @@ -306,8 +307,8 @@ public void GenerateSeedData() if (!metadataStructureManager.Repo.Get().Any(m => m.Name.Equals("Publication"))) { - string titleXPath = "Metadata/publication/publicationType/Title/TitleType"; - string descriptionXpath = "Metadata/publication/publicationType/Abstract/AbstractType"; + string titleXPath = "Metadata/publication/publication/Title/TitleDatatype_string"; + string descriptionXpath = "Metadata/publication/publication/Abstract/AbstractDatatype_string"; ImportSchema("Publication", "BEXIS2-Publication-Schema-draft.xsd", "Metadata", publication.Name, publication.EntityType.FullName, titleXPath, descriptionXpath); } diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Models/Curation/Curation.cs b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Models/Curation/Curation.cs new file mode 100644 index 0000000000..4a6bb03cca --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Models/Curation/Curation.cs @@ -0,0 +1,191 @@ +using BExIS.Dim.Entities.Publications; +using BExIS.Dlm.Entities.Curation; +using BExIS.Security.Entities.Subjects; +using BExIS.Utils.Config.Configurations; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BExIS.Modules.Dcm.UI.Models.Curation +{ + public class CurationEntryModel + { + public long Id { get; set; } + public string Topic { get; set; } + public CurationEntryType Type { get; set; } + public long DatasetId { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public string Solution { get; set; } + public int Position { get; set; } + public string Source { get; set; } + public IEnumerable Notes { get; set; } + public DateTime CreationDate { get; set; } + public long CreatorId { get; set; } + public bool UserIsDone { get; set; } + public bool IsApproved { get; set; } + public DateTime LastChangeDatetime_User { get; set; } + public DateTime LastChangeDatetime_Curator { get; set; } + + public CurationEntryModel() { + + Id = 0; + Topic = string.Empty; + Type = CurationEntryType.None; + DatasetId = 0; + Name = string.Empty; + Description = string.Empty; + Solution = string.Empty; + Position = 0; + Source = string.Empty; + Notes = new List(); + CreationDate = DateTime.MinValue; + CreatorId = 0; + UserIsDone = false; + IsApproved = false; + LastChangeDatetime_User = DateTime.MinValue; + LastChangeDatetime_Curator = DateTime.MinValue; + } + + public CurationEntryModel(CurationEntry curationEntry) + { + Id = curationEntry.Id; + Topic = curationEntry.Topic; + Type = curationEntry.Type; + DatasetId = curationEntry.Dataset.Id; + Name = curationEntry.Name; + Description = curationEntry.Description; + Solution = curationEntry.Solution; + Position = curationEntry.Position; + Source = curationEntry.Source; + CreationDate = curationEntry.CreationDate; + CreatorId = curationEntry.Creator.Id; + UserIsDone = curationEntry.UserIsDone; + IsApproved = curationEntry.IsApproved; + LastChangeDatetime_User = curationEntry.LastChangeDatetime_User; + LastChangeDatetime_Curator = curationEntry.LastChangeDatetime_Curator; + + if (curationEntry.Notes != null && curationEntry.Notes.Any()) + { + Notes = curationEntry.Notes.Select(n => new CurationNoteModel(n)).ToList(); + } else + { + Notes = new List(); + } + } + } + + public class CurationNoteModel + { + public long Id { get; set; } + public CurationUserType UserType { get; set; } + public DateTime CreationDate { get; set; } + public string Comment { get; set; } + public long UserId { get; set; } + + public CurationNoteModel() + { + Id = 0; + UserType = CurationUserType.User; + CreationDate = DateTime.MinValue; + Comment = string.Empty; + UserId = 0; + } + + public CurationNoteModel(CurationNote curationNote) + { + Id = curationNote.Id; + UserType = curationNote.UserType; + CreationDate = curationNote.CreationDate; + Comment = curationNote.Comment; + UserId = curationNote.User.Id; + } + + } + + public class CurationUserModel + { + public long Id { get; set; } + public string DisplayName { get; set; } + public CurationUserType CurationUserType { get; set; } + + public CurationUserModel() + { + Id = 0; + DisplayName = string.Empty; + CurationUserType = CurationUserType.User; + } + + public CurationUserModel(long id, string displayName, CurationUserType curationUserType) + { + Id = id; + DisplayName = displayName; + CurationUserType = curationUserType; + } + + public CurationUserModel(User user, long id, CurationUserType curationUserType) + { + Id = id; + DisplayName = user.DisplayName; + CurationUserType = curationUserType; + } + } + + public class CurationTemplateModel + { + public string name { get; set; } + public string content { get; set; } + + public CurationTemplateModel() + { + name = string.Empty; + content = string.Empty; + } + + public CurationTemplateModel(string name, string content) + { + this.name = name; + this.content = content; + } + } + + public class CurationModel + { + public long DatasetId { get; set; } + public string DatasetTitle { get; set; } + public DateTime DatasetVersionDate { get; set; } + public IEnumerable CurationEntries { get; set; } + public IEnumerable CurationUsers { get; set; } + public IEnumerable CurationLabels { get; set; } + public IEnumerable CurationEntryTypes { get; set; } + public IEnumerable GreetingTemplates { get; set; } + public IEnumerable TaskListTemplates { get; set; } + + public CurationModel() + { + DatasetId = 0; + DatasetTitle = string.Empty; + DatasetVersionDate = DateTime.MinValue; + CurationEntries = new List(); + CurationUsers = new List(); + CurationLabels = new List(); + CurationEntryTypes = Enum.GetValues(typeof(CurationEntryType)).Cast().ToList(); + GreetingTemplates = new List(); + TaskListTemplates = new List(); + } + + public CurationModel(long datasetId, string datasetTitle, DateTime datasetVersionDate, IEnumerable curationEntries, IEnumerable curationUsers, IEnumerable curationLabels, IEnumerable curationEntryTypes, IEnumerable greetingTemplates, IEnumerable taskListTemplates) + { + DatasetId = datasetId; + DatasetTitle = datasetTitle; + DatasetVersionDate = datasetVersionDate; + CurationEntries = curationEntries; + CurationUsers = curationUsers; + CurationLabels = curationLabels; + CurationEntryTypes = curationEntryTypes; + GreetingTemplates = greetingTemplates; + TaskListTemplates = taskListTemplates; + } + + } +} diff --git a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Views/EntityReference/Show.cshtml b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Views/EntityReference/Show.cshtml index 00e2117f3b..34876683f4 100644 --- a/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Views/EntityReference/Show.cshtml +++ b/Console/BExIS.Web.Shell/Areas/DCM/BExIS.Modules.Dcm.UI/Views/EntityReference/Show.cshtml @@ -130,7 +130,8 @@ *@ - -
-
+ + +
+ @**@ +
+
+
- - + + - + - @if (ViewData["IsValid"] == "no" ) - { -
- -
- } - + @if (validationErrors.Any()) {
@@ -488,7 +497,7 @@ if (confirm('Are you sure you want to leave the form? All changes will be lost.')) { - window.open(document.referrer) + window.location.href = document.referrer; // navigate back in the same tab } else { return false; diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/package-lock.json b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/package-lock.json index ae88ceb469..66d131c0b1 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/package-lock.json +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "license": "ISC", "dependencies": { - "@bexis2/bexis2-core-ui": "0.4.47", + "@bexis2/bexis2-core-ui": "0.4.64", "@floating-ui/dom": "1.6.8", "@fortawesome/free-solid-svg-icons": "6.6.0", "@sveltejs/adapter-static": "3.0.2", @@ -77,9 +77,9 @@ } }, "node_modules/@bexis2/bexis2-core-ui": { - "version": "0.4.47", - "resolved": "https://registry.npmjs.org/@bexis2/bexis2-core-ui/-/bexis2-core-ui-0.4.47.tgz", - "integrity": "sha512-PL9g1AvXaNj2Srw0xDAmBjrQiI9qoBGMvdJPj9hydRWmod+uKnbWlMrqDilnrWh2EWy2FzFiqE81FZ2giWO/6Q==", + "version": "0.4.64", + "resolved": "https://registry.npmjs.org/@bexis2/bexis2-core-ui/-/bexis2-core-ui-0.4.64.tgz", + "integrity": "sha512-YK6tKBISQY9DYXsGqG8523kIAKFPETRGEozGhditIify+mQgUoMEcnB4E4vHJHjhX+VAM73jOdCMuv9MF8ev8Q==", "license": "ISC", "dependencies": { "@codemirror/lang-html": "^6.4.9", @@ -261,358 +261,6 @@ "w3c-keyname": "^2.2.4" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@esbuild/win32-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", @@ -1103,201 +751,6 @@ "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==", "license": "MIT" }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", - "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz", - "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz", - "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz", - "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz", - "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz", - "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz", - "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz", - "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz", - "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz", - "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz", - "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz", - "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz", - "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz", - "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz", - "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.20.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz", @@ -3127,20 +2580,6 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -4501,21 +3940,6 @@ "node": ">=18" } }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/postcss": { "version": "8.4.40", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/package.json b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/package.json index 57e7e76b89..82659ad452 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/package.json +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/package.json @@ -50,7 +50,7 @@ }, "type": "module", "dependencies": { - "@bexis2/bexis2-core-ui": "0.4.47", + "@bexis2/bexis2-core-ui": "0.4.65", "@floating-ui/dom": "1.6.8", "@fortawesome/free-solid-svg-icons": "6.6.0", "@sveltejs/adapter-static": "3.0.2", diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/app.html b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/app.html index 01347b0fa9..8b8b6b4199 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/app.html +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/app.html @@ -7,6 +7,12 @@ %sveltekit.head% -
%sveltekit.body%
+ diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/Card.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/Card.svelte index 08f105bf86..cf5f5b05ba 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/Card.svelte +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/Card.svelte @@ -23,7 +23,8 @@ entitytemplate: '' }; - export let authorLabel = 'Authors'; + export let authorLabel = 'Authors'; + export let dateLabel = 'Modified'; const { title, description, author, license, id, doi, entity, date, entitytemplate } = card; @@ -84,6 +85,7 @@ on:keydown={() => window.open(`/ddm/data/Showdata/${id}`)} role="link" tabindex="0" + title="View dataset details in a new tab" >
@@ -95,7 +97,8 @@

{#if date && date.length > 0} - {date} + {dateLabel}: + {date} {/if}

@@ -122,7 +125,7 @@
{#if entitytemplate && entitytemplate.length > 0} -
+
{entitytemplate.toLowerCase()} @@ -130,7 +133,7 @@ {/if} {#if entity && entity.length > 0} -
+
{entity.toLowerCase()} diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/Cards.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/Cards.svelte index e25d9c4f40..2f24b4a689 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/Cards.svelte +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/Cards.svelte @@ -56,7 +56,7 @@ select="!px-3 !py-1.5 select min-w-[150px]" showFirstLastButtons showPreviousNextButtons - on:page ={e => paginationSettings.page = e.detail} + on:page={(e) => (paginationSettings.page = e.detail)} /> {/if}
diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/CardsPlaceHolder.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/CardsPlaceHolder.svelte new file mode 100644 index 0000000000..4f780e85b2 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/CardsPlaceHolder.svelte @@ -0,0 +1,15 @@ + + +
+

+ ...loading +

+ +
+
+
+
+
+ diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/Confetti.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/Confetti.svelte new file mode 100644 index 0000000000..d9b92593e4 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/Confetti.svelte @@ -0,0 +1,118 @@ + + + + +{#if show} + {#each confettiPieces as c} + + {/each} +{/if} + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/MarkdownComponent/MarkdownComponent.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/MarkdownComponent/MarkdownComponent.svelte new file mode 100644 index 0000000000..210b40c40c --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/MarkdownComponent/MarkdownComponent.svelte @@ -0,0 +1,159 @@ + + +

+ {#each parts as part, index (index)} + {#if part.component === 'none'} + + {:else if part.component === 'isHeading'} +

{part.markdown}

+ {:else if part.component === 'isListItem' && part.markdown} + handlePartChange(index, e.detail)} + {customInlineComponents} + /> + {:else if part.component === 'isEnum'} +
    + {#each part.markdown.split('\n') as item} +
  1. {item.replace(/^\s*\d+\. /, '')}
  2. + {/each} +
+ {:else if part.component === 'isHr'} +
+ {:else if part.component === 'isCode'} +
{part.markdown}
+ {:else if part.component === 'isBlockquote'} +
+ +
+ {:else if part.component === 'isBreak'} +
+ {/if} + {/each} +

diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/MarkdownComponent/MarkdownInlineComponent.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/MarkdownComponent/MarkdownInlineComponent.svelte new file mode 100644 index 0000000000..e511356de0 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/MarkdownComponent/MarkdownInlineComponent.svelte @@ -0,0 +1,129 @@ + + +{#each parts as part, index (index)} + {#if part.component === 'none'} + {part.markdown.trim() + ' '} + {:else if part.component === 'bolditalic'} + + + + + + {:else if part.component === 'italic'} + + + + {:else if part.component === 'bold'} + + + + {:else if part.component === 'inlineCode'} + {part.markdown}  + {:else if part.component === 'custom'} + + {/if} +{/each} diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/MarkdownComponent/MarkdownList.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/MarkdownComponent/MarkdownList.svelte new file mode 100644 index 0000000000..287b2ed70e --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/MarkdownComponent/MarkdownList.svelte @@ -0,0 +1,181 @@ + + +
    + {#each parts as part, idx} +
  • + {#if part.isCheckbox} + + {:else} + handlePartChange(idx, e.detail)} + /> + {/if} + {#if part.subListMarkdown} + handleListChange(idx, e.detail)} + /> + {/if} +
  • + {/each} +
+ + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/Popup.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/Popup.svelte new file mode 100644 index 0000000000..7eb8c58299 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/Popup.svelte @@ -0,0 +1,95 @@ + + +{#if showPopup} + +{/if} + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/RelativeDate.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/RelativeDate.svelte new file mode 100644 index 0000000000..210671f47d --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/RelativeDate.svelte @@ -0,0 +1,69 @@ + + +{#if tag} + + {#if showIcon} + + {/if} + {`${prefix}${dateToRelative(date)}${suffix}`} + +{:else} + {#if showIcon} + + {/if} + {`${prefix}${dateToRelative(date)}${suffix}`} +{/if} diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/Search.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/Search.svelte index 517011d273..db30c467d2 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/Search.svelte +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/Search.svelte @@ -6,7 +6,7 @@ import { RadioGroup, RadioItem, SlideToggle } from '@skeletonlabs/skeleton'; - import { Api, Facets, Page, pageContentLayoutType, Table } from '@bexis2/bexis2-core-ui'; + import { Api, Facets, Page, pageContentLayoutType, Table, TablePlaceholder } from '@bexis2/bexis2-core-ui'; import type { Columns, FacetGroup, linkType, TableConfig } from '@bexis2/bexis2-core-ui'; import Cards from '$lib/components/Cards.svelte'; @@ -17,6 +17,7 @@ import { convertTableData } from '$lib/helpers'; import { onMount } from 'svelte'; import { writable } from 'svelte/store'; + import CardsPlaceHolder from './CardsPlaceHolder.svelte'; export let controller: string = 'search'; @@ -26,7 +27,10 @@ let currentCategory = 'All'; let q = ''; - let currentView: 'table' | 'cards' = 'table'; + // get data from parent + let container = document.getElementById(controller); + let currentView:string = ""+container?.getAttribute('search_result_presentation'); //'table' | 'cards' = 'table'; + let facetsRef: any; @@ -249,7 +253,8 @@ if (placeholders.length > 0) { const idIndex = headers.findIndex((header) => header.Name === 'ID'); - authorLabel = headers[headers.findIndex((header) => header.Placeholder === 'author')]?.DisplayName + authorLabel = + headers[headers.findIndex((header) => header.Placeholder === 'author')]?.DisplayName; const data = rows.map((row) => ({ ...placeholders.reduce( (acc, item) => { @@ -265,13 +270,10 @@ placeholderStore.set(data); - console.log("🚀 ~ data ~ data:", data, authorLabel) + console.log('🚀 ~ data ~ data:', data, authorLabel); } }; - - - const deleteCriteriaKey = (criterion: string, value: string) => { const temp = { ...$criteria }; temp[criterion].values = temp[criterion].values.filter((v) => v !== value); @@ -401,29 +403,13 @@ })); }; - const load = async (input: string) => { - const data = [ - { Text: 'a', Value: 'a' }, - { Text: 'b', Value: 'b' }, - { Text: 'c', Value: 'c' }, - { Text: 'd', Value: 'd' }, - { Text: 'e', Value: 'e' }, - { Text: 'f', Value: 'f' }, - { Text: 'g', Value: 'g' } - ]; - - return data.map((item: any) => ({ - label: item.Text, - value: item.Value - })); - }; - const handleAutoCompleteSelect = async (e: { detail: { value: string; label: string } }) => { q = e.detail.value; return null; }; + onMount(async () => { await handleSearch(true); }); @@ -452,6 +438,8 @@ on:showMoreSelect={handleShowMoreSelect} on:showMoreOpenChange={handleShowMoreOpenChange} /> + {:else} + {/if}
@@ -527,7 +515,7 @@ placeholder="Search within selected category" /> @@ -549,7 +537,7 @@ {#if $criteria[key].values.length < 3} {#each $criteria[key].values as value, index (`${key}-${value}`)} { if ($criteria[key].type === 'Facet') { await toggleFacet(key, value); @@ -588,14 +576,25 @@
+ {#if config}
+
+ +
+ {:else} + + {#if currentView === 'table'} + {/if} -
- -
+ {#if currentView === 'cards'} + + {/if} + + {/if} + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/PrimaryData.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/PrimaryData.svelte new file mode 100644 index 0000000000..7bb863e457 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/PrimaryData.svelte @@ -0,0 +1,644 @@ + + +

Primary Data Configuration

+ +
+ Index Primary Data +
+
+ +
+

Overriding

+ {#if searchConfigData.local && searchConfigData.local.length > 0} +
    + {#each searchConfigData.local as localCfg} + {#if localCfg.primary_data} + {#if localCfg.primary_data.to_index != searchConfigData.global.primary_data.to_index} + {entitytemplates.find((et) => et.id === localCfg.entity_template_id)?.name || + localCfg.entity_template_id} +
    + {/if} + {/if} + {/each} +
+ {:else} +

No entity templates are overriding the global settings.

+ {/if} +
+
+

Matching

+ {#if searchConfigData.local && searchConfigData.local.length > 0} +
    + {#each searchConfigData.local as localCfg} + {#if localCfg.primary_data} + {#if localCfg.primary_data.to_index === searchConfigData.global.primary_data.to_index} + {entitytemplates.find((et) => et.id === localCfg.entity_template_id)?.name || + localCfg.entity_template_id} +
    + {/if} + {/if} + {/each} +
+ {:else} +

No entity templates are matching the global settings.

+ {/if} +
+
+ +{#if !searchConfigData} +
+
+
+ +
+
+ +
+ +
+{:else} + +
+
+ {#if 0 < 1} + {#if isEditing}Edit a Configuration{:else}Create a new Configuration{/if} + {:else} + {'name'} + {/if} +
+
+ {#if !showForm} + + + {/if} +
+
+ + {#if showForm} +
+
+ {#if isEditing} +
+
Entity Template: {selectedEntityTemplate.name}
+
Operation: {selectedOperation.text}
+
+ {:else} +
+ onChangeHandlerPrimaryData(e, 'entityTemplate')} + /> +
+
+ + !primaryData.some( + (item) => + item.template_name.toString() === selectedEntityTemplate?.id.toString() && + item.operation === op.id + ) + )} + required={true} + complexTarget={true} + help={true} + invalid={resValidation?.hasErrors('operation')} + feedback={resValidation?.getErrors('operation')} + on:change={(e) => onChangeHandlerPrimaryData(e, 'operation')} + /> +
+ {/if} +
+ onChangeHandlerPrimaryData(e, 'meanings')} + on:change={(e) => onChangeHandlerPrimaryData(e, 'meanings')} + {loading} + /> +
+ +
+
+ + + {#if isEditing} + + {:else} + + {/if} +
+
+ {/if} + +
+
editPrimaryDataCalc(obj.detail.type)} + config={{ + id: 'primaryDataCalcTable', + data: tableStore, + search: false, + paginated: false, + optionsComponent: TableOption, + columns: { + id: { + exclude: true + }, + template_name: { + header: 'Entity Template', + instructions: { + toStringFn: (key) => { + // find entity template name by id + const et = entitytemplates.find((item) => item.id.toString() === key.toString()); + if (et) { + return et.name; + } else { + return key.toString(); + } + } + } + }, + operation: { + header: 'Calculation Operation' + }, + allowed_meanings: { + header: 'Meanings to include', + disableFiltering: true, + instructions: { + renderComponent: TableElements + } + } + } + }} + /> + +{/if} + \ No newline at end of file diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/SearchConfig.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/SearchConfig.svelte new file mode 100644 index 0000000000..eee4094a14 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/SearchConfig.svelte @@ -0,0 +1,12 @@ + + + \ No newline at end of file diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/SearchConfigExample.json b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/SearchConfigExample.json new file mode 100644 index 0000000000..30b0d2e413 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/SearchConfigExample.json @@ -0,0 +1,206 @@ +{ + "global": { + "search_components": { + "facets_to_index": true, + "categories_to_index": true, + "properties_to_index": true, + "generals_to_index": false, + "facets": [ + { + "id": 1, + "component_name": "KeywordFacet", + "description": "Facet on keywords", + "placeholder": "Select keyword", + "header_item": true, + "default_header_item": true + } + ], + "categories": [ + { + "id": 2, + "component_name": "CategoryFacet", + "description": "Facet on categories", + "placeholder": "Select category", + "header_item": false, + "default_header_item": false + } + ], + "properties": [ + { + "id": 3, + "component_name": "PropertyFacet", + "description": "Facet on properties", + "placeholder": "Select property", + "header_item": false, + "default_header_item": false + } + ], + "general": [ + { + "id": 4, + "component_name": "FreeTextSearch", + "description": "Full text search", + "placeholder": "Search...", + "header_item": true, + "default_header_item": true + } + ] + }, + "primary_data": { + "to_index": true, + "calc": [{ + "operation": "avg", + "allowed_meanings": [10, 20, 15] + }], + "spatial_data": { + "allowed_data_type_ids": [1, 2], + "lat_meaning": 100, + "long_meaning": 200 + } + }, + "spatial_data": { + "spatial_search": true, + "spatial_search_settings": { + "crs": "EPSG:4326", + "axis_order": ["lat", "lon"], + "basemap": "satellite", + "start_extend": "0,0,10,10" + } + }, + "general": { + "show_only_completed_metadata": true, + "auto_complete_trigger": 3, + "show_empty_facets": false + } + }, + "local": [ + { + "entity_template_id": 2, + "index_not_completed_metadata": false, + "search_components": { + "facets": [ + { + "global_id": 1, + "data_type_id": "keyword", + "metadata_nodes": [ + "Metadata/Keywords/Keyword", + "Metadata/Subjects/Subject" + ] + } + ], + "categories": [ + { + "global_id": 2, + "data_type_id": "keyword", + "metadata_nodes": ["Metadata/Category"] + } + ], + "properties": [ + { + "global_id": 3, + "data_type_id": "integer", + "metadata_nodes": ["Metadata/Properties/Property"] + } + ], + "general": [ + { + "global_id": 4, + "data_type_id": "text", + "metadata_nodes": ["Metadata/Title", "Metadata/Description"] + } + ] + }, + "spatial_data": { + "spatial_metadata": { + "type": "bbox", + "WestBoundLongitude": -10.0, + "EastBoundLongitude": 10.0, + "SouthBoundLatitude": -5.0, + "NorthBoundLatitude": 5.0 + } + }, + "primary_data": { + "to_index": true, + "calc": [ + { + "operation": "sum", + "allowed_meanings": [4, 5] + }, + { + "operation": "min/max", + "allowed_meanings": [6] + } + ] + }, + "external_sources": { + "source": "filesystem", + "local_path": "C:/data/search_index.json", + "external_name": "PrimaryDataIndex" + } + }, + { + "entity_template_id": 3, + "index_not_completed_metadata": false, + "search_components": { + "facets": [ + { + "global_id": 1, + "data_type_id": "keyword", + "metadata_nodes": [ + "Metadata/Keywords/Keyword", + "Metadata/Subjects/Subject" + ] + } + ], + "categories": [ + { + "global_id": 2, + "data_type_id": "keyword", + "metadata_nodes": ["Metadata/Category"] + } + ], + "properties": [ + { + "global_id": 3, + "data_type_id": "integer", + "metadata_nodes": ["Metadata/Properties/Property"] + } + ], + "general": [ + { + "global_id": 4, + "data_type_id": "text", + "metadata_nodes": ["Metadata/Title", "Metadata/Description"] + } + ] + }, + "spatial_data": { + "spatial_metadata": { + "type": "bbox", + "WestBoundLongitude": -10.0, + "EastBoundLongitude": 10.0, + "SouthBoundLatitude": -5.0, + "NorthBoundLatitude": 5.0 + } + }, + "primary_data": { + "to_index": false, + "calc": [ + { + "operation": "sum", + "allowed_meanings": [4, 5] + }, + { + "operation": "min/max", + "allowed_meanings": [8] + } + ] + }, + "external_sources": { + "source": "filesystem", + "local_path": "C:/data/search_index.json", + "external_name": "PrimaryDataIndex" + } + } + ] +} \ No newline at end of file diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/SearchConfigModel.ts b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/SearchConfigModel.ts new file mode 100644 index 0000000000..e9f71f81b3 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/SearchConfigModel.ts @@ -0,0 +1,226 @@ +// Auto-generated from SearchConfigSchema.json +// Date: 2026-01-15 + +export interface SearchConfigSchema { + global: GlobalConfig; + local: LocalConfig[]; +} + +export interface GlobalConfig { + search_components: GlobalSearchComponents; + primary_data: GlobalPrimaryData; + spatial_data: GlobalSpatialData; + general: GlobalGeneral; +} + +export interface GlobalSearchComponents { + facets_to_index: boolean; + categories_to_index: boolean; + properties_to_index: boolean; + generals_to_index: boolean; + facets?: GlobalComponent[]; + categories?: GlobalComponent[]; + properties?: GlobalComponent[]; + general?: GlobalComponent[]; +} + +export interface GlobalComponent { + id: number; + component_name: string; + description: string; + placeholder: string; + header_item: boolean; + default_header_item: boolean; +} + +export interface GlobalPrimaryData { + to_index: boolean; + calc: CalcBlock; + spatial_data: GlobalPrimarySpatialData; +} + +export interface CalcBlock { + operation: "min/max" | "avg" | "sum"; + allowed_meanings: number[]; +} + +export interface GlobalPrimarySpatialData { + allowed_data_type_ids: number[]; + lat_meaning: number; + long_meaning: number; +} + +export interface GlobalSpatialData { + spatial_search?: boolean; + spatial_search_settings?: SpatialSearchSettings; +} + +export interface SpatialSearchSettings { + crs: "EPSG:4326"; + axis_order: string[]; + basemap: "satellite"; + start_extend: string; +} + +export interface GlobalGeneral { + show_only_completed_metadata?: boolean; + auto_complete_trigger?: number; + show_empty_facets?: boolean; +} + +export interface LocalConfig { + entity_template_id: number; + index_not_completed_metadata: boolean; + search_components: LocalSearchComponents; + spatial_data: LocalSpatialData; + primary_data: LocalPrimaryData; + external_sources: LocalExternalSources; +} + +export interface LocalSearchComponents { + facets?: LocalComponent[]; + categories?: LocalComponent[]; + properties?: LocalComponent[]; + general?: LocalComponent[]; +} + +export interface LocalComponent { + global_id: number; + data_type_id: "byte" | "short" | "integer" | "long" | "float" | "half_float" | "double" | "scaled_float" | "text" | "keyword" | "date" | "date_nanos" | "boolean" | "geo_point" | "geo_shape" | "object" | "nested" | "ip" | "version" | "binary"; + metadata_nodes: string[]; +} + +export interface LocalSpatialData { + spatial_metadata?: LocalSpatialMetadata; +} + +export type LocalSpatialMetadata = + | BBoxSpatialMetadata + | PointSpatialMetadata; + +export interface BBoxSpatialMetadata { + type: "bbox"; + WestBoundLongitude: number; + EastBoundLongitude: number; + SouthBoundLatitude: number; + NorthBoundLatitude: number; +} + +export interface PointSpatialMetadata { + type: "point"; + longitude: number; + latitude: number; + radius: number; +} + +export interface LocalPrimaryData { + to_index: boolean; + calc: CalcBlock | CalcBlock[]; +} + +export interface LocalExternalSources { + source: string; + local_path: string; + external_name: string; +} + +export interface CalcBlockListItem { + id: string; + template_name: string; + operation: "min/max" | "avg" | "sum"; + allowed_meanings: MeaningsListItem[]; +} + +export interface MeaningsListItem { + id: number; + name: string; +} + + + +// copied from RPM module types.ts + +export class MeaningModel { + id: number; + name: string; + description: string; + selectable: boolean; + approved: boolean; + externalLinks: meaningEntryType[]; + related_meaning: MeaningModel[]; + constraints: listItemType[]; + + public constructor(data: any) { + if (data) { + (this.id = data.id), (this.name = data.name); + this.approved = data.approved; + this.description = data.description; + this.selectable = data.selectable; + this.externalLinks = data.externalLinks; + this.related_meaning = data.related_meaning; + this.constraints = data.constraints; + } else { + this.id = 0; + this.name = ''; + this.approved = false; + this.description = ''; + this.selectable = false; + this.externalLinks = []; + this.related_meaning = []; + this.constraints = []; + } + } +} + +import type { listItemType } from '@bexis2/bexis2-core-ui'; + +export class meaningEntryType { + mappingRelation: listItemType; + mappedLinks: listItemType[]; + isValid: boolean = true; + + public constructor() { + this.mappingRelation = { id: -1, text: '', group: '', description: '' }; + this.mappedLinks = []; + } +} + +export class externalLinkType { + id: number; + uri: string; + name: string; + type: listItemType | undefined; + prefix: prefixListItemType | undefined; + prefixCategory: prefixCategoryType | undefined; + + public constructor() { + this.id = 0; + this.uri = ''; + this.name = ''; + this.type = undefined; + this.prefix = undefined; + this.prefixCategory = undefined; + } +} + +export interface prefixListItemType { + id: number; + text: string; + description: string; + url: string; +} + +export interface prefixCategoryType { + id: number; + name: string; + description: string; +} + +export enum externalLinkTypeEnum { + prefix = 1, + link = 2, + entity = 3, + characteristics = 4, + vocabulary = 5, + relationship = 6 +} diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/SearchConfigSchema.json b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/SearchConfigSchema.json new file mode 100644 index 0000000000..3471ac0e00 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/SearchConfigSchema.json @@ -0,0 +1,267 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "global": { + "type": "object", + "properties": { + "search_components": { + "type": "object", + "properties": { + "facets_to_index": { "type": "boolean" }, + "categories_to_index": { "type": "boolean" }, + "properties_to_index": { "type": "boolean" }, + "generals_to_index": { "type": "boolean" }, + + "facets": { + "type": "array", + "items": { "$ref": "#/$defs/globalComponent" } + }, + "categories": { + "type": "array", + "items": { "$ref": "#/$defs/globalComponent" } + }, + "properties": { + "type": "array", + "items": { "$ref": "#/$defs/globalComponent" } + }, + "general": { + "type": "array", + "items": { "$ref": "#/$defs/globalComponent" } + } + }, + "required": [ + "facets_to_index", "categories_to_index", + "properties_to_index", "generals_to_index" + ] + }, + + "primary_data": { + "type": "object", + "properties": { + "to_index": { "type": "boolean" }, + "calc": { + "$ref": "#/$defs/calcBlock" + }, + "spatial_data": { + "type": "object", + "properties": { + "allowed_data_type_ids": { + "type": "array", + "items": { "type": "integer" } + }, + "lat_meaning": { "type": "integer" }, + "long_meaning": { "type": "integer" } + }, + "required": ["allowed_data_type_ids", "lat_meaning", "long_meaning"] + } + }, + "required": ["to_index", "calc", "spatial_data"] + }, + + "spatial_data": { + "type": "object", + "properties": { + "spatial_search": { "type": "boolean" }, + "spatial_search_settings": { + "type": "object", + "properties": { + "crs": { "enum": ["EPSG:4326"] }, + "axis_order": { + "type": "array", + "items": { "type": "string" } + }, + "basemap": { "enum": ["satellite"] }, + "start_extend": { "type": "string" } + }, + "required": ["crs", "axis_order", "basemap", "start_extend"] + } + } + }, + + "general": { + "type": "object", + "properties": { + "show_only_completed_metadata": { "type": "boolean" }, + "auto_complete_trigger": { "type": "integer" }, + "show_empty_facets": { "type": "boolean" } + } + } + }, + "required": ["search_components", "primary_data", "spatial_data", "general"] + }, + + "local": { + "type": "array", + "items": { + "type": "object", + "properties": { + "entity_template_id": { "type": "integer" }, + "index_not_completed_metadata": { "type": "boolean" }, + + "search_components": { + "type": "object", + "properties": { + "facets": { + "type": "array", + "items": { "$ref": "#/$defs/localComponent" } + }, + "categories": { + "type": "array", + "items": { "$ref": "#/$defs/localComponent" } + }, + "properties": { + "type": "array", + "items": { "$ref": "#/$defs/localComponent" } + }, + "general": { + "type": "array", + "items": { "$ref": "#/$defs/localComponent" } + } + } + }, + + "spatial_data": { + "type": "object", + "properties": { + "spatial_metadata": { + "type": "object", + "properties": { + "type": { "enum": ["bbox", "point"] } + }, + "required": ["type"], + "allOf": [ + { + "if": { + "properties": { "type": { "const": "bbox" } } + }, + "then": { + "required": [ + "WestBoundLongitude", + "EastBoundLongitude", + "SouthBoundLatitude", + "NorthBoundLatitude" + ], + "properties": { + "WestBoundLongitude": { "type": "number" }, + "EastBoundLongitude": { "type": "number" }, + "SouthBoundLatitude": { "type": "number" }, + "NorthBoundLatitude": { "type": "number" } + } + } + }, + { + "if": { + "properties": { "type": { "const": "point" } } + }, + "then": { + "required": ["longitude", "latitude", "radius"], + "properties": { + "longitude": { "type": "number" }, + "latitude": { "type": "number" }, + "radius": { "type": "number" } + } + } + } + ] + } + } + }, + + "primary_data": { + "type": "object", + "properties": { + "to_index": { "type": "boolean" }, + "calc": { + "oneOf": [ + { "$ref": "#/$defs/calcBlock" }, + { + "type": "array", + "items": { "$ref": "#/$defs/calcBlock" } + } + ] + } + }, + "required": ["to_index", "calc"] + }, + + "external_sources": { + "type": "object", + "properties": { + "source": { "type": "string" }, + "local_path": { "type": "string" }, + "external_name": { "type": "string" } + }, + "required": ["source", "local_path", "external_name"] + } + }, + "required": ["entity_template_id", "index_not_completed_metadata", "search_components", "spatial_data", "primary_data", "external_sources"] + } + } + }, + + "required": ["global", "local"], + + "$defs": { + "globalComponent": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "component_name": { "type": "string" }, + "description": { "type": "string" }, + "placeholder": { "type": "string" }, + "header_item": { "type": "boolean" }, + "default_header_item": { "type": "boolean" } + }, + "required": ["id", "component_name", "description", "placeholder", "header_item", "default_header_item"] + }, + + "localComponent": { + "type": "object", + "properties": { + "global_id": { "type": "integer" }, + "data_type_id": { + "enum": [ + "byte", + "short", + "integer", + "long", + "float", + "half_float", + "double", + "scaled_float", + "text", + "keyword", + "date", + "date_nanos", + "boolean", + "geo_point", + "geo_shape", + "object", + "nested", + "ip", + "version", + "binary" + ] + }, + "metadata_nodes": { + "type": "array", + "items": { "type": "string" } + } + }, + "required": ["global_id", "data_type_id", "metadata_nodes"] + }, + + "calcBlock": { + "type": "object", + "properties": { + "operation": { "enum": ["min/max", "avg", "sum"] }, + "allowed_meanings": { + "type": "array", + "items": { "type": "integer" } + } + }, + "required": ["operation", "allowed_meanings"] + } + } +} diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/SpatialData.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/SpatialData.svelte new file mode 100644 index 0000000000..7bb863e457 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/SpatialData.svelte @@ -0,0 +1,644 @@ + + +

Primary Data Configuration

+ +
+ Index Primary Data +
+
+ +
+

Overriding

+ {#if searchConfigData.local && searchConfigData.local.length > 0} +
    + {#each searchConfigData.local as localCfg} + {#if localCfg.primary_data} + {#if localCfg.primary_data.to_index != searchConfigData.global.primary_data.to_index} + {entitytemplates.find((et) => et.id === localCfg.entity_template_id)?.name || + localCfg.entity_template_id} +
    + {/if} + {/if} + {/each} +
+ {:else} +

No entity templates are overriding the global settings.

+ {/if} +
+
+

Matching

+ {#if searchConfigData.local && searchConfigData.local.length > 0} +
    + {#each searchConfigData.local as localCfg} + {#if localCfg.primary_data} + {#if localCfg.primary_data.to_index === searchConfigData.global.primary_data.to_index} + {entitytemplates.find((et) => et.id === localCfg.entity_template_id)?.name || + localCfg.entity_template_id} +
    + {/if} + {/if} + {/each} +
+ {:else} +

No entity templates are matching the global settings.

+ {/if} +
+
+ +{#if !searchConfigData} +
+
+
+ +
+
+ +
+ +
+{:else} + +
+
+ {#if 0 < 1} + {#if isEditing}Edit a Configuration{:else}Create a new Configuration{/if} + {:else} + {'name'} + {/if} +
+
+ {#if !showForm} + + + {/if} +
+
+ + {#if showForm} +
+
+ {#if isEditing} +
+
Entity Template: {selectedEntityTemplate.name}
+
Operation: {selectedOperation.text}
+
+ {:else} +
+ onChangeHandlerPrimaryData(e, 'entityTemplate')} + /> +
+
+ + !primaryData.some( + (item) => + item.template_name.toString() === selectedEntityTemplate?.id.toString() && + item.operation === op.id + ) + )} + required={true} + complexTarget={true} + help={true} + invalid={resValidation?.hasErrors('operation')} + feedback={resValidation?.getErrors('operation')} + on:change={(e) => onChangeHandlerPrimaryData(e, 'operation')} + /> +
+ {/if} +
+ onChangeHandlerPrimaryData(e, 'meanings')} + on:change={(e) => onChangeHandlerPrimaryData(e, 'meanings')} + {loading} + /> +
+ +
+
+ + + {#if isEditing} + + {:else} + + {/if} +
+
+ {/if} + +
+
editPrimaryDataCalc(obj.detail.type)} + config={{ + id: 'primaryDataCalcTable', + data: tableStore, + search: false, + paginated: false, + optionsComponent: TableOption, + columns: { + id: { + exclude: true + }, + template_name: { + header: 'Entity Template', + instructions: { + toStringFn: (key) => { + // find entity template name by id + const et = entitytemplates.find((item) => item.id.toString() === key.toString()); + if (et) { + return et.name; + } else { + return key.toString(); + } + } + } + }, + operation: { + header: 'Calculation Operation' + }, + allowed_meanings: { + header: 'Meanings to include', + disableFiltering: true, + instructions: { + renderComponent: TableElements + } + } + } + }} + /> + +{/if} + \ No newline at end of file diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/primaryDataValidation.ts b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/primaryDataValidation.ts new file mode 100644 index 0000000000..90a9aa3392 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/primaryDataValidation.ts @@ -0,0 +1,30 @@ +import { create, test, enforce } from 'vest'; +import type { CalcBlockListItem } from '$lib/components/SearchConfig/SearchConfigModel'; + + + +const suite = create((data: CalcBlockListItem) => { + // Always declare tests in the same order so Vest can track them reliably. + test('entityTemplate', 'entity template is required', () => { + enforce(data.template_name).isNotBlank(); + }); + + test('operation', 'operation is required', () => { + enforce(data.operation).isNotBlank(); + }); + + test('meanings', 'at least one meaning must be selected', () => { + // const count = data.allowed_meanings.length; + // console.log('meanings count for validation:', count); + // enforce(count).isGreaterThan(0); + // Updated to check that allowed_meanings is an array and has at least one item + //enforce(Array.isArray(data.allowed_meanings) && data.allowed_meanings.length).isGreaterThan(0); + + // test now only is array + enforce(data.allowed_meanings).isArray().isNotEmpty(); + //enforce(data.operation).isNotBlank(); + }); + + +}); +export default suite; \ No newline at end of file diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/tableElements.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/tableElements.svelte new file mode 100644 index 0000000000..f98f73d8d5 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/tableElements.svelte @@ -0,0 +1,14 @@ + + +
+ {#if value != undefined} + {#each value as item } +
  • {item.name}
  • + {/each} + {:else} + No items + {/if} +
    diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/tableOptions.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/tableOptions.svelte new file mode 100644 index 0000000000..2c2df3a645 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SearchConfig/tableOptions.svelte @@ -0,0 +1,67 @@ + + + +
    + + + + {#if row.inUseByVariable === true || row.inUseByMeaning === true} + + + + {:else} + + + {/if} +
    +
    diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/ShowData.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/ShowData.svelte index 64596998d2..c372203a0a 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/ShowData.svelte +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/ShowData.svelte @@ -5,6 +5,6 @@ export let row; - + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SpinnerOverlay.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SpinnerOverlay.svelte new file mode 100644 index 0000000000..8990f6b1c6 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/SpinnerOverlay.svelte @@ -0,0 +1,13 @@ + + +
    +
    + +
    +
    diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/Curation.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/Curation.svelte new file mode 100644 index 0000000000..3920a82dc1 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/Curation.svelte @@ -0,0 +1,222 @@ + + +
    + + + {#if $loadingCuration} +
    + +
    + {:else if $loadingError} +
    +

    Error loading curation entries

    +
    + {:else if !$curation?.curationStatusEntry?.isNoDraft()} + + {:else} + + + + {#if $curationInfoExpanded} +
    + +
    + {/if} + +
    + + + {#if $progressInfoExpanded} +
    + {#each $currentTypeViewOrder as progressType} + {#if progressType !== CurationEntryType.None} +
    + +
    + {/if} + {/each} +
    + {/if} +
    + + {#if $curationInfoExpanded} +
    + +
    + {/if} + +
    + + + {#if $curation?.isCurator} + + {/if} +
    + +
    + {#each $currentTypeViewOrder as type} + {#if $typeFilter?.data !== undefined ? $typeFilter.data === type : true} + + {/if} + {/each} +
    + {/if} + + {#if $curation?.isCurator} + + + + + {/if} +
    diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/CurationsTable.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/CurationsTable.svelte new file mode 100644 index 0000000000..8df345e1ff --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/CurationsTable.svelte @@ -0,0 +1,188 @@ + + +
    +

    Curations Overview

    +

    + Welcome to the curation tool. This tool is designed to help you manage and track the curation of + your datasets. Use the table below to see an overview of all datasets that you are able to + access. Click on + + + Open Curation + + to open the curation details for a specific dataset. +

    +
    + +{#if !$errorMessage || $isLoading} +
    +
    openCuration(obj.detail.datasetId)} /> + + {#if $isLoading} + + {/if} + +{:else} +
    +

    Error loading curation entries

    +
    +{/if} diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/AddCurationEntry.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/AddCurationEntry.svelte new file mode 100644 index 0000000000..ff5c76f981 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/AddCurationEntry.svelte @@ -0,0 +1,45 @@ + + + + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/CurationEntryCard.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/CurationEntryCard.svelte new file mode 100644 index 0000000000..a1626e8031 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/CurationEntryCard.svelte @@ -0,0 +1,416 @@ + + + + + {#if $editMode && $cardState.editEntryMode} + + {:else} +
    +
    + + {#if combined} +

    + {$entry?.name} +

    + {/if} + + +

    + {$entry?.description} +

    +
    +
    + + + + + +
    + + {#if $jumpToDataEnabled} + + {/if} + +
    + {#each CurationEntryStatusDetails as statusDetails, index} + {#if index === $entry?.status || (!$editMode && ($curation?.isCurator || (index !== CurationEntryStatus.Ok && index !== CurationEntryStatus.Closed)))} + + {/if} + {/each} +
    + + + + + + + + {#if $entry?.isHidden()} +
    + + Hidden +
    + {/if} + {#if $entry?.isDraft()} +
    + + Draft +
    + {/if} + + {#if $entry?.isDraft()} + + {/if} + + + + + +
    + +
    +
    + {/if} + + {#if isUploading} + + {/if} + + +
    + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/CurationEntryForm.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/CurationEntryForm.svelte new file mode 100644 index 0000000000..a72281476d --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/CurationEntryForm.svelte @@ -0,0 +1,115 @@ + + +
    +

    + {#if $entry?.isDraft()} + Create Curation Entry + (Draft) + {:else} + Edit Curation Entry + (#{$entry?.id}) + {/if} +

    + + + +
    + + + + + + +
    + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/CurationEntryList.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/CurationEntryList.svelte new file mode 100644 index 0000000000..9d9676273c --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/CurationEntryList.svelte @@ -0,0 +1,54 @@ + + +

    {CurationEntryTypeNames[type]}

    +{#if curationEntries.some((entry) => entry.isVisible()) || $editMode} +
      + {#if $editMode} + + {/if} + {#each sortedEntries as entryNameGroup (entryNameGroup.map((e) => e.id).join(' '))} + {#if $editMode || entryNameGroup.some((entry) => entry.isVisible())} + entry.id)} + groupName={entryNameGroup[0]?.name} + /> + {/if} + {/each} +
    +{:else} +
    + + + No entries + +
    +{/if} + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/CurationGroupCard.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/CurationGroupCard.svelte new file mode 100644 index 0000000000..6c0f8e9ed4 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/CurationGroupCard.svelte @@ -0,0 +1,56 @@ + + +{#if entryIds.length === 1} + +{:else} +
  • +

    + {groupName} + + [{entryIds.length} + {entryIds.length === 1 ? 'Entry' : 'Entries'}] + +

    + +
      + {#each entryValues as entry} + {#if entry} + + {/if} + {/each} + {#if $editMode} + + {/if} +
    +
  • +{/if} + +{#if $editMode} + +{/if} diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/CurationNote.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/CurationNote.svelte new file mode 100644 index 0000000000..8c8624f9aa --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/CurationNote.svelte @@ -0,0 +1,150 @@ + + +{#if shortForm} +

    + + {$userName} + {#if note.userId === fixedCurationUserId} + (You) + {/if} + + : + {note.comment} +

    +{:else} +
  • +
    + +

    + + + {$userName} + {#if note.userId === fixedCurationUserId} + (You) + {/if} + + + [{note.userType === CurationUserType.Curator ? 'Curator' : 'User'}] + + +

    +
    + + + {#if note.userId === fixedCurationUserId} + + {/if} +
    +
    +

    + + {#each commentLinesGrouped as group} + {#if group.length > 0 && group[0].startsWith('| ')} +

    + {#each group as line} + {line.slice(2)} +
    + {/each} +

    + {:else} + {group[0]} +
    + {/if} + {/each} +

    +
  • +{/if} + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/CurationNotes.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/CurationNotes.svelte new file mode 100644 index 0000000000..95855e3ad7 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/entryList/CurationNotes.svelte @@ -0,0 +1,111 @@ + + +
    +
      + + {#if visibleNotes.length === 0} +
    • + Add a note to start the conversation about this entry. +
    • + {:else} + {#each visibleNotes as note} + + {/each} + {/if} +
    + {#if visibleNotes.length > 0 || notes.filter((note) => note.userId === fixedCurationUserId).length > 0} + + {/if} +
    + + +
    +
    diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/statusEntry/CurationEntryTemplateButton.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/statusEntry/CurationEntryTemplateButton.svelte new file mode 100644 index 0000000000..b1538b1575 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/statusEntry/CurationEntryTemplateButton.svelte @@ -0,0 +1,185 @@ + + +
    + ​ +
    + +
    +
    + + + {CurationEntryStatusDetails[template.status].name} + + + + {template.name?.length ? template.name : 'Untitled'} + + : + + + {template.description?.length ? template.description : 'Empty description'} + + + + + {#if template.comment?.length} + + + + {/if} + + + +
    +
    + + + {#if template.autoCreate} +
    + + + + +
    + {/if} + +
    +
    + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/statusEntry/CurationLabel.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/statusEntry/CurationLabel.svelte new file mode 100644 index 0000000000..946f66d022 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/statusEntry/CurationLabel.svelte @@ -0,0 +1,149 @@ + + +{#if !labelNote && !remainingLabels} +
    +
    + +
    + +
    +
    +
    +{:else if isCustomLabel} + +{:else if remainingLabels && remainingLabels.length > 0} +
    +
    + +
    + +
    +
    +
    +{/if} + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/statusEntry/CurationStatusEntryCard.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/statusEntry/CurationStatusEntryCard.svelte new file mode 100644 index 0000000000..99afabddd9 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/statusEntry/CurationStatusEntryCard.svelte @@ -0,0 +1,464 @@ + + + +{#if $curation?.isCurator && curationStatusEntry} +
    + + + + {#each curationStatusEntry.visibleNotes.toSorted( (a, b) => a.comment.localeCompare(b.comment) ) as labelNote ((labelNote.comment, labelNote.creationDateObj))} + + {/each} + + + {#if curationStatusEntry.visibleNotes.length > 0} +
    + Click on a label to remove it +
    + {/if} + + + {#if isUploadingStatus} + + {/if} +
    +{/if} + + +
    + {#if $curation?.isCurator && curationStatusEntry} + +
    + + + +
    + + + {#if $currentStatusEntryTab === CurationStatusEntryTab.Greeting} +
    + {#if !$editGreetingMode} + {#key greeting} + + {/key} +
    + +
    + {:else} + + +
    + + + + +
    + {/if} +
    + {/if} + + + {#if $currentStatusEntryTab === CurationStatusEntryTab.Tasks} +
    + {#if !$editTasksMode} +
    10} + > + {#key curationStatusEntry.description} + handleTasksChange(e.detail)} + customInlineComponents={[templateMarkdownComponent]} + /> + {/key} +
    + +
    +
    + + +
    + + +
    + {:else} + + + +
    + + + + +
    + {/if} +
    + {/if} + {:else if curationStatusEntry} + + + +
    + +
    + {/if} + + + {#if isUploadingStatus} + + {/if} +
    + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/statusEntry/Greeting.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/statusEntry/Greeting.svelte new file mode 100644 index 0000000000..6950d58f61 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/statusEntry/Greeting.svelte @@ -0,0 +1,77 @@ + + +

    + {#each parsedGreeting as line, index} + {#each line as part} + {#if !part.isBold && !part.isItalic} + {part.text} + {:else if (part.isBold || part.isItalic) && curationStatusNamesSet.has(part.text.toLowerCase())} + {#each CurationEntryStatusDetails as status, index} + {#if status.name.toLowerCase() === part.text.toLowerCase()} + + + {status.name} + + {/if} + {/each} + {:else if (part.isBold || part.isItalic) && notesNamesSet.has(part.text.toLowerCase())} + + + Chat + + {:else if part.isBold} + {part.text} + {:else if part.isItalic} + {part.text} + {/if} + {/each} + {#if index < parsedGreeting.length - 1} +
    + {/if} + {/each} +

    diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/ColorPalettePicker.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/ColorPalettePicker.svelte new file mode 100644 index 0000000000..7bbf3c99a2 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/ColorPalettePicker.svelte @@ -0,0 +1,26 @@ + + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationDatasetInfo.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationDatasetInfo.svelte new file mode 100644 index 0000000000..799de6c29f --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationDatasetInfo.svelte @@ -0,0 +1,93 @@ + + +
    + {#if $loadingCuration} +
    +

    +
    + {#if $datasetId} + + #{$datasetId}: + + {/if} +
    +
    +
    +

    +
    +
    +
    +
    +
    +
    + {:else if $loadingError} +

    Error: {$loadingError}

    + {:else if $curation} +
    +

    + + #{$datasetId}: + + {$curation?.datasetTitle} + +

    +
    + +
    +
    + {#if $curationInfoExpanded} +
    + +
    +
    + Last Dataset Change + +
    + {#if $curation.curationEntries.length > 0} +
    + Begin of Curation + +
    +
    + Last User Change + +
    +
    + Last Curator Change + +
    + {/if} +
    +
    + {/if} + {/if} +
    diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationEntryInputs.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationEntryInputs.svelte new file mode 100644 index 0000000000..b92ae7a991 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationEntryInputs.svelte @@ -0,0 +1,160 @@ + + + + +{#if !positionHidden} + +{/if} + + + + + +{#if isDraft} + +
    + {#if !showCommentField} + + {:else} + + {/if} + + + +
    +{/if} diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationEntryTemplatePopupContent.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationEntryTemplatePopupContent.svelte new file mode 100644 index 0000000000..13387ad577 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationEntryTemplatePopupContent.svelte @@ -0,0 +1,125 @@ + + +
    + +
    + +
    + + +
    +
    + + + {#if callback} + +
    + + +
    + {/if} + + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationFilter.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationFilter.svelte new file mode 100644 index 0000000000..32186b0353 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationFilter.svelte @@ -0,0 +1,183 @@ + + +
    +
    + + Filter by Status: + {#key $statusFilter?.data.toString} + {#each CurationEntryStatusDetails as statusDetails, index} + + {/each} + {/key} +
    +
    + + Filter by Category: + + {#each $currentTypeViewOrder as i} + + {/each} +
    + +
    + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationHelp.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationHelp.svelte new file mode 100644 index 0000000000..e4c4334a5f --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationHelp.svelte @@ -0,0 +1,507 @@ + + + + +
    +
    + {#if type === curationHelpType.mainResearcher} + +

    Curation Help

    +

    + Welcome to the curation details section for your dataset. Here, you can review important + information and collaborate directly with your curator. Explore the available sections to + understand the interface and its features. +

    +

    + Your curator may have provided a summary or guidance below the dataset title to help you + proceed effectively. +

    + +

    Structure of this help

    +

    + {#each mainResearcherStructure as { id, name }} + scrollToId(id)} + > + {name} + + {/each} +

    + +
    + {:else if type === curationHelpType.mainCurator} + +

    Curation Help

    +

    + Welcome to the curation details section for this dataset. Here, you can manage and track the + curation process, communicate with the researcher, and ensure the quality of the dataset. + Explore the available sections to understand the interface and its features.
    + You can provide a + + + Greeting + for the researcher at the top of the page to welcome them and offer first guidance for + a good start. (see below) +

    + +

    Structure of this help

    +

    + {#each mainCuratorStructure as { id, name }} + scrollToId(id)} + > + {name} + + {/each} +

    + +
    + + +

    Greeting

    +

    + You can provide a + + Greeting + + or summary for the researcher at the top of the curation details page. This can help to make + the curation more personal and should offer guidance or context for the curation process. You + can also use + *Open*, + *Changed*, + *Approved*, + *Paused*, and + *Chat* + inside your greeting to better explain the task for the researcher. These keywords will be replaced + with the respective colored status and function indicators. Additionally, you can use + *Some Text* + to make text italic or + **Some Text** + to make text bold. +

    + {/if} + + {#if type === curationHelpType.tasks || type === curationHelpType.mainCurator} + +

    Curation Tasks

    +

    + The + + Curation Tasks + + section should be filled with prewritten templates that guide you through the curation process. + Many markdown features are supported to help you format the tasks clearly. Especially useful + are unordered lists with checkboxes to mark completed tasks:
    + - [ ] Task 1
    + Many more markdown features are supported but will not be explained here. You can find many guides + online that explain markdown formatting. +

    +

    + Inside the curation tasks, you can also create templates for curation entries by using + [TemplateLink](...). However, + these template links should not be created manually but rather with the provided template + tool that is explained below. This ensures that the links are correctly formatted and + functional. By clicking on the + + + Create + + button that replaces the template link when the markdown is rendered, you can create a new curation + entry based on the template. +

    +

    + Clicking the + + edit icon next to the create button, you can easily edit the template, which updates the markdown + text accordingly. +

    +

    + If AutoCreate is enabled in a template link, a + + + + + icon will be shown next to the create button. This means that a curation entry will be created + once the + + + + + + Auto Create Templates (n) + + button below the tasks is clicked. This allows you to create multiple entries quickly without + having to click each create button individually. The + Create as Draft + checkbox next to the auto create button allows you to decide whether these entries should be + created as drafts, which requires additional clicks by you to push them to the server, or directly + created as open entries. +

    + {/if} + + + {#if type === curationHelpType.mainResearcher} + +

    + Curation Entries / Issues +

    +

    + The most important part of the curation tool are the curation entries that mark issues, + tasks or just general notes about the dataset. These entries are listed below and can be + filtered and sorted. Each entry has a status that indicates its current state: +

    +
      +
    • + + + Open + : The entry has been created and is awaiting action. You should review the issue + and take the necessary steps to address it. If you have questions, you can use the chat + feature to communicate with your curator. +
    • +
    • + + + Changed + : You should mark the entry as changed when you have made the necessary updates + or modifications to the dataset in response to the issue raised. This status indicates + that you have taken action on the entry, but it still requires review by the curator. +
    • +
    • + + + Approved + : The entry has been resolved and approved by the curator. No further action is + required on this entry. In some cases the curator may create approved entries to show you + that they have reviewed certain aspects of the dataset and found them to be satisfactory. + If you do any changes to an entry that is approved, please change the status back to + changed. +
    • +
    • + + + Paused + : The entry is temporarily on hold. This status may be used when waiting for + additional information or resources before proceeding. If you do any changes to an entry + that is paused, please change the status back to changed. +
    • +
    + +

    + You can click on the status of an entry (below the title and description) to change it. Next + to the status, you can also open the + + + Chat + + for this entry to discuss it with your curator. +

    + {:else if type === curationHelpType.mainCurator} + +

    + Curation Entries / Issues +

    +

    + Curation entries are used to document issues, tasks, or notes about the dataset. As a + curator, you can create new entries, update their status, and communicate with the + researcher. Each entry has a status that reflects its current state: +

    +
      +
    • + + + Open + : The entry has been created and is awaiting action from the researcher. Use this + status to highlight issues or tasks that need attention. +
    • +
    • + + + Changed + : The researcher has made updates or modifications in response to your entry. + Review these changes and decide if further action is needed. +
    • +
    • + + + Approved + : The entry has been resolved and approved by you. Use this status to indicate + that the issue has been satisfactorily addressed. +
    • +
    • + + + Paused + : The entry is temporarily on hold, for example, while waiting for additional + information or resources. You can resume the entry by changing its status when ready. +
    • +
    + +

    + You can change the status of an entry by clicking on it. Use the chat feature next to each + entry to communicate directly with the researcher and clarify any questions or provide + additional guidance. To edit or create entries you have to click on + + + Switch to Edit Mode + button at the top right of the entries list. +

    +

    + In edit mode, you can modify existing entries or create new ones. Entries that are marked as + + + Draft + + are not pushed to the server until you edit them again and save them. This also means reloading + the page will discard any unsaved changes. Therefore, you should ensure to not keep too many + draft entries at once and push them to the server as soon as possible. Also note that only drafts + can be deleted. After a draft is pushed to the server, it becomes a regular entry that can only + be edited but not deleted. If you still need to hide an entry after it has been pushed to the + server, you can change its category / type to "None (Hidden)". This will move it to the bottom + of the list and hide it from the researcher. You wil only see hidden entries if you enable the + Edit Mode. +

    +

    When editing or creating an entry, you can set the following parameters:

    +
      +
    • + Category: The category or type of the entry. This helps to organize and + filter entries. If you want to hide an entry from the researcher, set its category to + "None (Hidden)". You will only see hidden entries if you enable the Edit Mode. +
    • +
    • + Title: A brief summary of the issue or task or the source of the issue. + This should be concise yet informative to give a quick overview of the entry. If multiple + entries have the same title and are adjacent, they will be grouped together in the list to + save space. +
    • +
    • + Position: The position of the entry in the list. You can pick any number + and also resort it later. +
    • +
    • + Description: A detailed explanation of the entry. This should provide + enough context and information for the researcher to understand what is required. +
    • +
    • + Initial Comment (Draft only): This is a preliminary comment or note that + you can add to the entry while it is still in draft mode. This may be used to provide + additional context or instructions for the researcher that is too long for the description + field. Once the entry is pushed to the server, this comment becomes a regular comment. +
    • +
    • + Initial Status: The initial status of the entry when it is created. You + can choose from Open, Changed, Approved, or Paused. The default status is Open. +
    • +
    +

    + If you want to create a template from an existing entry, you can do so by first editing the + entry and then selecting the + + + Create Template + + option. This creates opens the template tool with the exact state of your current input fields, + which may be different from the original entry, if you have edited it. This allows you to quickly + create templates based on existing entries that you have already customized. You can read more + about Templates below. +

    + {/if} + + {#if type === curationHelpType.mainCurator} +

    Templates

    +

    + Templates are a powerful feature that allows you to create predefined structures for + curation entries. They can help you standardize the curation process and ensure that + important aspects are consistently addressed. Templates are created using a special markdown + syntax within the curation tasks section. A template link looks like this: + + [TemplateEntry](?type=2&name=Example%20Title&description=This%20is%20an%20example%20description.&draft) + +

    +

    + To create a new template link, you can click on the + + + Create Template + + button when editing a curation entry. This opens the template tool with the current state of + your entry. Afterwards you can adjust the parameters to fit your needs and copy the template + link at the bottom, by clicking on it. This link can then be pasted into the curation tasks markdown. + You can create as many template links as you want. Each link will be replaced with a + + + Create + + button in the rendered markdown. Clicking this button creates a new curation entry based on the + template. +

    +

    + Alternatively, you can copy an existing markdown template link inside the curation tasks and + paste it at another position to duplicate it. You should then save the tasks and edit the + newly created template link by clicking on the + + edit icon next to the create button that replaces the template link in the rendered markdown. + This opens the template tool with the current parameters of the template link, which you can + then adjust as needed. +

    +

    + Inside the template tool, which opens as a popup, you can set the following parameters, + which are all optional: +

    +
      +
    • + Category, Title, Description, Initial Comment, Initial Status: See + Curation Entries above. +
    • +
    • + Placement: The placement of the entry when it is created. You can choose + from "Top" to insert the new entry at the top of the entries list or "Bottom" to insert it + at the bottom of the entries list. +
    • +
    • + Create as Draft: If enabled, the created entry will be marked as draft. + This means that it will not be pushed to the server until you edit it again and save it. +
    • +
    • + Auto Create: If enabled, a curation entry will be created automatically + when the + + + + + + Auto Create Templates (n) + + button below the tasks is clicked. This allows you to create multiple entries quickly without + having to click each create button individually. Ideally you should only use this for a few + templates that are very general. +
    • +
    + {/if} + + {#if type === curationHelpType.mainResearcher || type === curationHelpType.mainCurator} + +

    Filters and Search

    +

    + To help you manage and navigate through the curation entries, you can use the filters and + search functionality at the top of the entries list. You can filter entries by their status + and their category. The status filter allows multiple selections, that are combined with an + OR logic. The category filter allows only a single selection. Additionally, you can use the + search bar to find specific entries based on keywords in their title, description, or + comments. +

    +

    + All filters can be cleared at once by clicking the + + + Clear Applied Filters + button below the filters. +

    + +

    Color Palette

    +

    + If you find it difficult to distinguish between the different status colors, try one of the + other color palettes. You can change the color palette by using the dropdown above the + filters and the progress bar. +

    + {/if} + {#if type !== curationHelpType.mainResearcher && type !== curationHelpType.mainCurator && type !== curationHelpType.tasks} + + This help topic is not yet implemented. + {/if} +
    +
    diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationProgressInfo.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationProgressInfo.svelte new file mode 100644 index 0000000000..4cc649d393 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationProgressInfo.svelte @@ -0,0 +1,160 @@ + + +
    + {#if progress} +
    + {#if expandWritable} + + {:else} +

    + {label} + + Total Issues: {totalIssues} + +

    + {/if} +
    +
    + {#if progress.some((p) => p > 0)} + {#each progress as p, index} + {#if p > 0} +
    handleMouseEnter(index)} + on:mouseleave={handleMouseLeave} + role="presentation" + >
    + {/if} + {/each} + {:else} +
    handleMouseEnter(CurationEntryStatus.Closed)} + on:mouseleave={handleMouseLeave} + role="presentation" + >
    + {/if} +
    +
      + {#if progress && progress.slice(0, CurationEntryStatus.Closed).some((p) => p > 0)} + {#each progress as p, index} + {#if p > 0} +
    • +
      handleMouseEnter(index)} + on:mouseleave={handleMouseLeave} + class:opacity-25={$hasLowOpacity[index]} + role="presentation" + > + + + {CurationEntryStatusDetails[index].name}: {p} + + {((p / totalIssues) * 100).toFixed(2)}% +
      +
    • + {/if} + {/each} + {:else} +
    • +
      handleMouseEnter(CurationEntryStatus.Closed)} + on:mouseleave={handleMouseLeave} + class:opacity-25={$hasLowOpacity[CurationEntryStatus.Closed]} + role="presentation" + > + + {#if progress[CurationEntryStatus.Closed] > 0} + + Everything clean and finished + {:else} + No entries + {/if} + +
      +
    • + {/if} +
    + {/if} +
    + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationTemplate.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationTemplate.svelte new file mode 100644 index 0000000000..edf26e6e50 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/CurationTemplate.svelte @@ -0,0 +1,39 @@ + + +
    + + +
    diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/StartCuration.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/StartCuration.svelte new file mode 100644 index 0000000000..74493f691a --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/details/utils/StartCuration.svelte @@ -0,0 +1,120 @@ + + +

    + The curation process has not started yet. +

    + +{#if $curation?.isCurator} +
    + + + + +{/if} diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/table/TableLabelCell.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/table/TableLabelCell.svelte new file mode 100644 index 0000000000..16a80c42fd --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/table/TableLabelCell.svelte @@ -0,0 +1,64 @@ + + +{#if labels && labels.length > 0} +
    + {#each labels as l} +
    + {l.name} +
    + {/each} +
    +{:else if column.id === 'curationStatus'} + {#if !value.curationStarted} +
    Not Curated
    + {:else if status !== undefined} +
    + {CurationStatusLabels[status].name} +
    + {/if} +{/if} diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/table/TableLabelFilter.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/table/TableLabelFilter.svelte new file mode 100644 index 0000000000..8284ac2e91 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/table/TableLabelFilter.svelte @@ -0,0 +1,111 @@ + + +
    + +
    +
    + + + + + Click on the labels to toggle them + +
    +
    +
    + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/table/TableOptions.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/table/TableOptions.svelte new file mode 100644 index 0000000000..5479384316 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/table/TableOptions.svelte @@ -0,0 +1,20 @@ + + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/table/TableStatusFilter.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/table/TableStatusFilter.svelte new file mode 100644 index 0000000000..f38a22446a --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/curation/table/TableStatusFilter.svelte @@ -0,0 +1,119 @@ + + +
    + +
    +
    + + + + + Click on the labels to toggle them + +
    +
    +
    + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/metadataDiffTool/DiffArray.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/metadataDiffTool/DiffArray.svelte new file mode 100644 index 0000000000..c30f0d62f1 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/metadataDiffTool/DiffArray.svelte @@ -0,0 +1,29 @@ + + +
    + {#each value1 as v, i} + + {/each} + {#if value2.length > value1.length} + {#each value2.slice(value1.length) as v, i} + + {/each} + {/if} +
    diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/metadataDiffTool/DiffNode.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/metadataDiffTool/DiffNode.svelte new file mode 100644 index 0000000000..db0c174a72 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/metadataDiffTool/DiffNode.svelte @@ -0,0 +1,53 @@ + + +{#if isPrimitive(value1) || isPrimitive(value2)} + +{:else if isArray(value1) || isArray(value2)} + +{:else if isObject(value1) || isObject(value2)} + +{:else} + + Unsupported type change from {typeof value1} to {typeof value2} + +{/if} diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/metadataDiffTool/DiffObject.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/metadataDiffTool/DiffObject.svelte new file mode 100644 index 0000000000..982689dd52 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/metadataDiffTool/DiffObject.svelte @@ -0,0 +1,55 @@ + + +
    +
    +
    + {#if name} + {name} + {/if} + {#if attributes.length > 0} +
    + {#each attributes as k} +
    + {k}: + +
    + {/each} +
    + {/if} + + {levelNames.join(' > ')} + +
    + + {#if textKey} + + {/if} +
    + + {#each others as k} + + {/each} +
    diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/metadataDiffTool/DiffPrimitive.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/metadataDiffTool/DiffPrimitive.svelte new file mode 100644 index 0000000000..4bff888718 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/metadataDiffTool/DiffPrimitive.svelte @@ -0,0 +1,173 @@ + + +{#if !isDiff} + {#if !isEmpty(value1String)} + + {#each splitLines(value1String) as line, index} + {renderValue(line)} + {#if index < splitLines(value1String).length - 1} +
    + {/if} + {/each} +
    + {:else} + empty + {/if} +{:else} + + + + {#if !useSimpleFormat} + + {/if} + {#if !useSimpleFormat} + + {/if} +{/if} + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/metadataDiffTool/MetadataDiffTool.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/metadataDiffTool/MetadataDiffTool.svelte new file mode 100644 index 0000000000..8b2dc76144 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/components/metadataDiffTool/MetadataDiffTool.svelte @@ -0,0 +1,315 @@ + + + +

    Metadata Diff Tool

    +

    Select datasets and versions to compare their metadata.

    + +{#if datasetResponse1.error || datasetResponse2.error} +

    Error loading dataset: {datasetResponse1.error.message}

    +{:else if datasetResponse1.maxVersion !== undefined} + +
    + + {} + : () => { + selectedDataset2 = selectedDataset1; + onChangeSelectedDataset2(new Event('init')); + }} + >Sync selection +
    +
    +
    +
    + +
    +
    {selectedDataset1 ? selectedDataset1.text : 'None'}
    +
    + +
    +
    +
    +
    + +
    +
    {selectedDataset2 ? selectedDataset2.text : 'None'}
    +
    + +
    +
    +
    + {#if selectedVersion1 && selectedVersion2} + {#if loading1 || loading2} +
    +
    + {#if loading1} + +

    Loading metadata 1...

    + {/if} +
    +
    + {#if loading2} + +

    Loading metadata 2...

    + {/if} +
    +
    + {:else if metadata1 && metadata2} + {#key `${selectedVersion1}\n\n---\n\n${selectedVersion2}`} + + {/key} + {/if} + {/if} +{:else} +
    + +

    Loading dataset information...

    +
    +{/if} +
    diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/Curation.ts b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/Curation.ts new file mode 100644 index 0000000000..85772e71d0 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/Curation.ts @@ -0,0 +1,363 @@ +import { + CurationEntryClass, + CurationEntryStatusDetails, + CurationEntryType, + CurationEntryTypeNames, + type CurationEntryCreationModel, + type CurationEntryModel +} from './CurationEntry'; +import type { CurationLabelModel } from './CurationStatusEntry'; +import { + CurationUserClass, + CurationUserType, + fixedCurationUserId, + type CurationUserModel +} from './CurationUser'; + +export interface CurationTemplateModel { + name: string; + content: string; +} + +export interface CurationModel { + datasetId: number; + datasetTitle: string; + datasetVersionDate: string; + curationEntryTypes: CurationEntryType[]; + curationEntries: CurationEntryModel[]; + curationUsers: CurationUserModel[]; + curationLabels: CurationLabelModel[]; + greetingTemplates: CurationTemplateModel[]; + taskListTemplates: CurationTemplateModel[]; +} + +export enum CurationFilterType { + status, + type, + search +} + +export type CurationFilterModel = { + type: CurationFilterType; + data: TData; + fn: (entry: CurationEntryClass, data: TData) => boolean; + isClearedFn: (data: TData) => boolean; +}; + +export class CurationClass implements CurationModel { + // Properties from CurationModel + public readonly datasetId: number; + public readonly datasetTitle: string; + public readonly datasetVersionDate: string; + public readonly curationEntryTypes: CurationEntryType[]; + public readonly curationEntries: CurationEntryClass[]; + public readonly curationStatusEntry: CurationEntryClass | null = null; + public readonly curationUsers: CurationUserClass[]; + public readonly curationLabels: CurationLabelModel[]; + public readonly greetingTemplates: CurationTemplateModel[]; + public readonly taskListTemplates: CurationTemplateModel[]; + + // Additional properties + public readonly userMap: Map; + + public readonly datasetVersionDateObj: Date | undefined; + public readonly creationDate: Date | undefined; + public readonly lastUserChangedDate: Date | undefined; + public readonly lastCuratorChangedDate: Date | undefined; + + public readonly curationProgressTotal: number[]; + public readonly curationProgressPerType: number[][]; + + public readonly currentUserType: CurationUserType; + public readonly isCurator: boolean; + + public readonly draftCount: number; + + public readonly highestPositionPerType: number[]; + + constructor( + curation: CurationModel | CurationClass, + doNotSort: boolean | undefined = undefined, + changedEntryPos: CurationEntryClass | undefined = undefined + ) { + // create user map and set current user type + this.curationUsers = (curation.curationUsers || []).map((user) => new CurationUserClass(user)); + this.userMap = new Map(); + this.curationUsers.forEach((user) => { + this.userMap.set(user.id, user); + }); + this.currentUserType = + this.userMap.get(fixedCurationUserId)?.curationUserType ?? CurationUserType.User; + this.isCurator = this.currentUserType === CurationUserType.Curator; + + // dataset information + this.datasetId = curation.datasetId || 0; + this.datasetTitle = curation.datasetTitle || ''; + this.datasetVersionDate = curation.datasetVersionDate || ''; + + this.greetingTemplates = curation.greetingTemplates || []; + this.taskListTemplates = curation.taskListTemplates || []; + + const removeTypes = new Set([ + CurationEntryType.None, + CurationEntryType.StatusEntryItem + ]); + this.curationEntryTypes = (curation.curationEntryTypes || []).filter( + (type) => !removeTypes.has(type) + ); + + // curation entries + const allEntries = (curation.curationEntries || []).map((entry) => { + if (entry instanceof CurationEntryClass) return entry; + return new CurationEntryClass(entry, this.currentUserType); + }); + + this.curationStatusEntry = + allEntries.find((entry) => entry.type === CurationEntryType.StatusEntryItem) || null; + + if (doNotSort) this.curationEntries = allEntries; + else this.curationEntries = CurationClass.applyPositioning(allEntries, changedEntryPos); + + this.highestPositionPerType = CurationEntryTypeNames.map((_) => 0); + this.curationEntries.forEach((entry) => { + if (entry.type !== CurationEntryType.StatusEntryItem) { + this.highestPositionPerType[entry.type] = Math.max( + this.highestPositionPerType[entry.type], + entry.position + ); + } + }); + + this.curationLabels = curation.curationLabels || []; + + // Additional properties + this.datasetVersionDateObj = new Date(curation.datasetVersionDate); + + [this.creationDate, this.lastUserChangedDate, this.lastCuratorChangedDate] = + this.getCalculatedDates(); + + [this.curationProgressTotal, this.curationProgressPerType] = this.getCurationProgress(); + + this.draftCount = this.curationEntries.filter((entry) => entry.isDraft()).length; + } + + public addEntry(entry: CurationEntryClass): CurationClass { + const index = this.curationEntries.findIndex((e) => e.id === entry.id); + const newEntries = [...this.curationEntries]; + + let updatedPosition = true; + + if (index !== -1) { + updatedPosition = + newEntries[index].position !== entry.position || newEntries[index].type !== entry.type; + newEntries[index] = entry; + } else { + newEntries.push(entry); + } + + return new CurationClass( + { + ...this, + curationEntries: newEntries + }, + !updatedPosition, + updatedPosition ? entry : undefined + ); + } + + private getCalculatedDates(): [Date | undefined, Date | undefined, Date | undefined] { + let creationDate: number | undefined = undefined; + let lastUserChangedDate: number | undefined = undefined; + let lastCuratorChangedDate: number | undefined = undefined; + + this.curationEntries.forEach((entry) => { + if ( + !creationDate || + (entry.creationDateObj && entry.creationDateObj.getTime() > creationDate) + ) + creationDate = entry.creationDateObj?.getTime(); + if ( + !lastUserChangedDate || + (entry.lastChangeDatetime_UserObj && + entry.lastChangeDatetime_UserObj.getTime() > lastUserChangedDate) + ) + lastUserChangedDate = entry.lastChangeDatetime_UserObj?.getTime(); + if ( + !lastCuratorChangedDate || + (entry.lastChangeDatetime_CuratorObj && + entry.lastChangeDatetime_CuratorObj.getTime() > lastCuratorChangedDate) + ) + lastCuratorChangedDate = entry.lastChangeDatetime_CuratorObj?.getTime(); + }); + + const cutoff = new Date(); + cutoff.setFullYear(cutoff.getFullYear() - 2000); + const twoThousandYearsAgoTime = cutoff.getTime(); + + return [ + // if creationDate is older than 2000 years set undefined + !creationDate || creationDate < twoThousandYearsAgoTime ? undefined : new Date(creationDate), + !lastUserChangedDate || lastUserChangedDate < twoThousandYearsAgoTime + ? undefined + : new Date(lastUserChangedDate), + !lastCuratorChangedDate || lastCuratorChangedDate < twoThousandYearsAgoTime + ? undefined + : new Date(lastCuratorChangedDate) + ]; + } + + private getCurationProgress(): [number[], number[][]] { + const progressTotal = new Array(CurationEntryStatusDetails.length).fill(0); + const progressPerType = new Array(CurationEntryTypeNames.length) + .fill(0) + .map(() => new Array(CurationEntryStatusDetails.length).fill(0)); + + this.curationEntries.forEach((entry) => { + if (!entry.isVisible()) return; + progressTotal[entry.status]++; + progressPerType[entry.type][entry.status]++; + }); + + return [progressTotal, progressPerType]; + } + + public getEntryById(entryId: number): CurationEntryClass | null { + const entry = this.curationEntries.find((entry) => entry.id === entryId); + if (!entry) return null; + return entry; + } + + public updateEntry(entryId: number, updates: Partial): CurationClass { + const entry = this.getEntryById(entryId); + if (!entry) return this; + const comment = updates.comment; + delete updates.comment; + const { userIsDone, isApproved } = CurationEntryClass.getStatusBoolean( + updates.status ?? entry.status + ); + delete updates.status; + let newEntry = new CurationEntryClass( + { + ...entry, + ...updates, + userIsDone: userIsDone, + isApproved: isApproved + }, + this.currentUserType + ); + if (entry.isDraft()) { + if (comment && comment.trim().length > 0) { + newEntry = newEntry.clearNotes().addNote(comment); + } + } + return this.addEntry(newEntry); + } + + public addEntryModel(entryModel: CurationEntryCreationModel) { + const isStatusEntry = entryModel.type === CurationEntryType.StatusEntryItem; + const invalidPosition = + (isStatusEntry && (entryModel.position !== 0 || this.curationStatusEntry)) || + (!isStatusEntry && entryModel.position < 1); + if (invalidPosition) return { curation: this, newEntryId: 0 }; + let newEntry = CurationEntryClass.emptyEntry(this.datasetId, -this.draftCount - 1, entryModel); + if (entryModel.status !== undefined) newEntry = newEntry.setStatus(entryModel.status); + return { + curation: new CurationClass( + { + ...this, + curationEntries: [...this.curationEntries, newEntry] + }, + false, + newEntry + ), + newEntryId: newEntry.id + }; + } + + private static sortedCurationEntries( + curationEntries: CurationEntryClass[] + ): CurationEntryClass[] { + return curationEntries.toSorted( + (a, b) => + a.position - b.position || // most important is the position + a.id - b.id || // if position is the same, sort by id + (a.topic + a.name + a.description).localeCompare(b.topic + b.name + b.description) // if position and id are the same, sort by topic, name and description + ); + } + + /** + * + * @param curationEntries the full current list of curationEntries + * @param entry a specific entry, that already has its new wanted position (the previous position does not matter as well as if it is inside the curationEntries or not) + * @returns the new sorted list of entries + */ + private static applyPositioning( + curationEntries: CurationEntryClass[], + entry: CurationEntryClass | undefined = undefined + ): CurationEntryClass[] { + let newEntries: CurationEntryClass[] = []; + let nextPositions = CurationEntryTypeNames.map((_) => 1); + let addedEntry = entry?.type === CurationEntryType.StatusEntryItem; // is false if entry is not a StatusEntryItem + const sortedCurationEntries = this.sortedCurationEntries(curationEntries); + for (const e of sortedCurationEntries) { + if (!addedEntry && entry && entry.position === nextPositions[entry.type]) { + // add entry add correct position + newEntries.push(entry); + addedEntry = true; + if (entry.isNoDraft()) nextPositions[entry.type]++; + } + if (e.type === CurationEntryType.StatusEntryItem) { + newEntries.push( + new CurationEntryClass( + { + ...e, + position: 0 + }, + e.currentUserType + ) + ); + } else if (e.id !== entry?.id) { + newEntries.push( + new CurationEntryClass( + { + ...e, + position: nextPositions[e.type] + }, + e.currentUserType + ) + ); + if (!e.isDraft()) { + // if entry is a draft the counter should not be increased because they are not in the backend + nextPositions[e.type]++; + } + } + } + if (!addedEntry && entry) { + // push entry to end if not prviously added + newEntries.push( + new CurationEntryClass( + { + ...entry, + position: nextPositions[entry.type] + }, + entry.currentUserType + ) + ); + } + return newEntries; + } + + /** + * Only for removing the entry locally. An entry can not be removed from the backend, only updated to be hidden. + * + * @param entryId - The id of the entry to be removed + * @returns CurationClass - A new CurationClass object with the entry removed + */ + public removeEntry(entryId: number): CurationClass { + const newEntries = this.curationEntries.filter((entry) => entry.id !== entryId); + return new CurationClass({ + ...this, + curationEntries: newEntries + }); + } +} diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationEntry.ts b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationEntry.ts new file mode 100644 index 0000000000..9a8479bda1 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationEntry.ts @@ -0,0 +1,510 @@ +import { + faCircleCheck, + faCircleDot, + faCircleExclamation, + faCirclePause +} from '@fortawesome/free-solid-svg-icons'; +import { CurationUserType, fixedCurationUserId } from './CurationUser'; +import { + commandTypeMapping, + CurationNoteClass, + CurationNoteCommandType, + type CurationNoteModel +} from './CurationNote'; + +export interface CurationEntryModel { + id: number; + topic: string; + type: CurationEntryType; + datasetId: number; + name: string; + description: string; + solution: string; + position: number; + source: string; + notes: CurationNoteModel[]; + creationDate: string; + creatorId: number; + userIsDone: boolean; + isApproved: boolean; + lastChangeDatetime_User: string; + lastChangeDatetime_Curator: string; +} + +export interface CurationEntryCreationModel { + topic: string; + type: CurationEntryType; + name: string; + description: string; + solution: string; + position: number; + source: string; + comment: string; + status: CurationEntryStatus; +} + +export interface CurationEntryHelperModel extends CurationEntryModel { + isDraft: boolean; + status: CurationEntryStatus; + statusName: string; + statusColor: string; +} + +export enum CurationEntryType { + None = 0, + StatusEntryItem = 1, + GeneralEntryItem = 2, + MetadataEntryItem = 3, + PrimaryDataEntryItem = 4, + DatastructureEntryItem = 5, + LinkEntryItem = 6, + AttachmentEntryItem = 7 +} + +export const CurationEntryTypeNames: string[] = [ + 'Hidden', + 'Status', + 'General', + 'Metadata', + 'Primary Data', + 'Datastructure', + 'Links', + 'Attachments' +]; + +export enum CurationEntryStatus { + Open = 0, + Fixed = 1, + Ok = 2, + Closed = 3 +} + +export const CurationEntryStatusDetails = [ + { + status: CurationEntryStatus.Open, + name: 'Open', + icon: faCircleExclamation + }, + { + status: CurationEntryStatus.Fixed, + name: 'Changed', + icon: faCircleDot + }, + { + status: CurationEntryStatus.Ok, + name: 'Paused', + icon: faCirclePause + }, + { + status: CurationEntryStatus.Closed, + name: 'Approved', + icon: faCircleCheck + } +]; + +export const CurationEntryStatusColorPalettes = [ + { + name: 'Default', + colors: [ + 'hsl(330deg 100% 30%)', + 'hsl(150deg 100% 35%)', + 'hsl(150deg 100% 25%)', + 'hsl(150deg 100% 15%)' + ] + }, + { + name: 'Gray', + colors: ['#555555', '#888888', '#aaaaaa', '#cccccc'] + }, + { + name: 'Colorful', + colors: ['#D55E00', '#56B4E9', '#CC79A7', '#004D40'] + } +]; + +export const DefaultCurationEntryCreationModel: CurationEntryCreationModel = { + topic: '', + type: CurationEntryType.None, + name: '', + description: '', + solution: '', + position: 1, + source: '', + comment: '', + status: CurationEntryStatus.Open +}; + +export class CurationEntryClass implements CurationEntryModel { + // Properties from CurationEntryModel + public readonly id: number; + public readonly topic: string; + public readonly type: CurationEntryType; + public readonly datasetId: number; + public readonly name: string; + public readonly description: string; + public readonly solution: string; + public readonly position: number; + public readonly source: string; + public readonly notes: CurationNoteClass[]; + public readonly creationDate: string; + public readonly creatorId: number; + public readonly userIsDone: boolean; + public readonly isApproved: boolean; + public readonly lastChangeDatetime_User: string; + public readonly lastChangeDatetime_Curator: string; + // Converted Date Object properties + public readonly creationDateObj: Date | undefined; + public readonly lastChangeDatetime_UserObj: Date | undefined; + public readonly lastChangeDatetime_CuratorObj: Date | undefined; + // Additional derived properties + public readonly lastChangedDate: Date | undefined; + public readonly visibleNotes: CurationNoteClass[]; + public readonly noteUsers: Set; + public readonly hasUnreadNotes: boolean; + public readonly status: CurationEntryStatus; + public readonly currentUserType: CurationUserType; + + constructor( + curationEntry: CurationEntryModel | CurationEntryClass, + currentUserType: CurationUserType + ) { + this.id = curationEntry.id || 0; + this.topic = curationEntry.topic || ''; + this.type = curationEntry.type || CurationEntryType.None; + this.datasetId = curationEntry.datasetId || 0; + this.name = curationEntry.name || ''; + this.description = curationEntry.description || ''; + this.solution = curationEntry.solution || ''; + this.position = curationEntry.position || 0; + this.source = curationEntry.source || ''; + this.notes = (curationEntry.notes || []) + .map((note) => new CurationNoteClass(note, false)) + .sort((a, b) => a.creationDateObj.getTime() - b.creationDateObj.getTime()); + this.creationDate = curationEntry.creationDate || ''; + this.creatorId = curationEntry.creatorId || 0; + this.userIsDone = curationEntry.userIsDone || false; + this.isApproved = curationEntry.isApproved || false; + this.lastChangeDatetime_User = curationEntry.lastChangeDatetime_User || ''; + this.lastChangeDatetime_Curator = curationEntry.lastChangeDatetime_Curator || ''; + // Converted Date Object properties + this.creationDateObj = new Date(curationEntry.creationDate); + this.lastChangeDatetime_UserObj = new Date(curationEntry.lastChangeDatetime_User); + this.lastChangeDatetime_CuratorObj = new Date(curationEntry.lastChangeDatetime_Curator); + // Additional derived properties + this.visibleNotes = this.notes.filter((note) => !note.hidden); + this.lastChangedDate = this.getCalculatedLastChangedDate(); + this.noteUsers = new Set(this.visibleNotes.map((note) => note.userId)); + this.hasUnreadNotes = this.getHasUnreadNotes(); + this.status = this._getStatus(); + this.currentUserType = currentUserType; + } + + private getHasUnreadNotes(): boolean { + for (let i = this.notes.length - 1; i >= 0; i--) { + const note = this.notes[i]; + if (note.userId !== fixedCurationUserId) { + if (!note.hidden) return true; + } else if (!note.hidden) { + return false; + } else { + if (note.command === CurationNoteCommandType.Read) return false; + if (note.command === CurationNoteCommandType.Unread) return true; + } + } + return false; + } + + private getCalculatedLastChangedDate() { + let lastChangedDate = Math.max( + this.creationDateObj?.getTime() ?? 0, + this.lastChangeDatetime_UserObj?.getTime() ?? 0, + this.lastChangeDatetime_CuratorObj?.getTime() ?? 0, + ...this.visibleNotes.map((note) => note.creationDateObj?.getTime() ?? 0) + ); + return lastChangedDate ? new Date(lastChangedDate) : undefined; + } + + /** + * Adds the note to the entry and returns a new CurationEntryClass object. This behavior should be necessary to update stores + * + * @param noteId + * @param comment + * @returns CurationEntryClass + * @throws Error if the comment is empty + */ + public updateNote(noteId: number, comment: string, escape: boolean = true): CurationEntryClass { + if (!comment.trim().length) { + console.warn('Comment cannot be empty'); + return this; + } + if (this.type === CurationEntryType.StatusEntryItem) { + // note is a label and should be checked + if (!/^\S*\s#[0-9a-fA-F]+$/.test(comment)) { + console.warn( + 'Note for MetadataEntryItem should correspond to the following regex: /^\\S*\\s#[0-9a-fA-F]+$/' + ); + return this; + } + if ( + this.visibleNotes + .map((n) => /^\S*\s/.exec(n.comment)?.toString().trim()) + .includes(/^\S*\s/.exec(comment)?.toString().trim()) + ) { + console.warn('A note that contains this label already exists on this MetadataEntryItem.'); + return this; + } + } + const note = new CurationNoteClass( + { + id: noteId, + userType: this.currentUserType, + creationDate: new Date().toISOString(), + comment: comment, + userId: fixedCurationUserId + }, + escape + ); + let newNotes = this.notes.filter((note) => note.id !== noteId); + // remove the last note of the current user if it is a read / unread command + let myLastNoteIndex = newNotes.findLastIndex((note) => note.userId === fixedCurationUserId); + if ( + myLastNoteIndex !== -1 && + newNotes[myLastNoteIndex].hidden && + (newNotes[myLastNoteIndex].command === CurationNoteCommandType.Read || + newNotes[myLastNoteIndex].command === CurationNoteCommandType.Unread) + ) { + newNotes = newNotes.filter((note) => note.id !== newNotes[myLastNoteIndex].id); + } + newNotes.push(note); + if (this.currentUserType === CurationUserType.Curator) { + return new CurationEntryClass( + { + ...this, + notes: newNotes, + lastChangeDatetime_Curator: note.creationDate + }, + this.currentUserType + ); + } + return new CurationEntryClass( + { + ...this, + notes: newNotes, + lastChangeDatetime_User: note.creationDate + }, + this.currentUserType + ); + } + + public clearNotes(): CurationEntryClass { + return new CurationEntryClass( + { + ...this, + notes: [] + }, + this.currentUserType + ); + } + + public addNote(comment: string, escape: boolean = true): CurationEntryClass { + return this.updateNote(0, comment, escape); + } + + public deleteNote(noteId: number): CurationEntryClass { + const newNotes = this.notes.filter((note) => note.id !== noteId); + return new CurationEntryClass({ ...this, notes: newNotes }, this.currentUserType); + } + + public deleteNotes(noteIds: number[]): CurationEntryClass { + const newNotes = this.notes.filter((note) => !noteIds.includes(note.id)); + return new CurationEntryClass({ ...this, notes: newNotes }, this.currentUserType); + } + + public setUnread(unread: boolean = true): CurationEntryClass { + if (unread === this.hasUnreadNotes) return this; + + const command = unread ? CurationNoteCommandType.Unread : CurationNoteCommandType.Read; + const oppositeCommand = unread ? CurationNoteCommandType.Read : CurationNoteCommandType.Unread; + const commandComment = `\\${commandTypeMapping[command]}`; + + for (let i = this.notes.length - 1; i >= 0; i--) { + const note = this.notes[i]; + if (note.hidden) { + if (note.userId === fixedCurationUserId && note.command === oppositeCommand) { + return this.updateNote(note.id, commandComment, false); + } + } else if (note.userId === fixedCurationUserId && unread) { + break; + } else if (note.userId !== fixedCurationUserId && !unread) { + break; + } + } + return this.addNote(commandComment, false); + } + + public static getStatus(userIsDone: boolean, isApproved: boolean): CurationEntryStatus { + if (userIsDone && !isApproved) return CurationEntryStatus.Fixed; + if (!userIsDone && isApproved) return CurationEntryStatus.Ok; + if (userIsDone && isApproved) return CurationEntryStatus.Closed; + return CurationEntryStatus.Open; + } + + public static getStatusBoolean(status: CurationEntryStatus): { + userIsDone: boolean; + isApproved: boolean; + } { + if (status === CurationEntryStatus.Fixed) return { userIsDone: true, isApproved: false }; + if (status === CurationEntryStatus.Ok) return { userIsDone: false, isApproved: true }; + if (status === CurationEntryStatus.Closed) return { userIsDone: true, isApproved: true }; + return { userIsDone: false, isApproved: false }; + } + + private _getStatus(): CurationEntryStatus { + return CurationEntryClass.getStatus(this.userIsDone, this.isApproved); + } + + public setStatusBoolean(newuserIsDone: boolean, newIsApproved: boolean): CurationEntryClass { + return new CurationEntryClass( + { + ...this, + userIsDone: newuserIsDone, + isApproved: newIsApproved + }, + this.currentUserType + ); + } + + public setStatus(status: CurationEntryStatus): CurationEntryClass { + const { userIsDone, isApproved } = CurationEntryClass.getStatusBoolean(status); + return this.setStatusBoolean(userIsDone, isApproved); + } + + public setName(name: string): CurationEntryClass { + if (this.name === name) return this; + return new CurationEntryClass( + { + ...this, + name: name + }, + this.currentUserType + ); + } + + public setTopic(topic: string): CurationEntryClass { + if (this.topic === topic) return this; + return new CurationEntryClass( + { + ...this, + topic: topic + }, + this.currentUserType + ); + } + + public setDescription(description: string): CurationEntryClass { + if (this.description === description) return this; + return new CurationEntryClass( + { + ...this, + description: description + }, + this.currentUserType + ); + } + + public setPosition(position: number): CurationEntryClass { + if (position < 1) return this; + if (this.position === position) return this; + + return new CurationEntryClass( + { + ...this, + position: position + }, + this.currentUserType + ); + } + + public getNextStatus(): CurationEntryStatus { + if (this.currentUserType === CurationUserType.User) + return CurationEntryClass.getStatus(!this.userIsDone, false); + if (this.currentUserType === CurationUserType.Curator) { + if (this.status === CurationEntryStatus.Ok) return CurationEntryStatus.Closed; + if (this.status === CurationEntryStatus.Closed) return CurationEntryStatus.Open; + if (this.status === CurationEntryStatus.Fixed) return CurationEntryStatus.Ok; + if (this.status === CurationEntryStatus.Open) return CurationEntryStatus.Ok; + } + return this.status; + } + + public toggleStatus(): CurationEntryClass { + return this.setStatus(this.getNextStatus()); + } + + public static emptyEntry( + datasetId: number, + id: number, + entryModel: CurationEntryCreationModel + ): CurationEntryClass { + let notes: CurationNoteModel[] = []; + if (entryModel.comment && entryModel.comment.trim().length > 0) { + notes.push({ + id: 0, + comment: entryModel.comment, + userType: CurationUserType.Curator, + userId: 0, + creationDate: '' + }); + } + return new CurationEntryClass( + { + ...entryModel, + id, + datasetId, + notes, + creationDate: '', + creatorId: fixedCurationUserId, + userIsDone: false, + isApproved: false, + lastChangeDatetime_User: '', + lastChangeDatetime_Curator: '' + }, + CurationUserType.User + ); + } + + public isHidden(): boolean { + return this.type === CurationEntryType.None; + } + + public isDraft(): boolean { + return this.id < 0; + } + + public isNoDraft() { + return !this.isDraft(); + } + + public isVisible(): boolean { + return !this.isHidden() && !this.isDraft() && this.type !== CurationEntryType.StatusEntryItem; + } + + public isEditable(): boolean { + return this.type !== CurationEntryType.StatusEntryItem; + } + + public getHelperModel( + currentColorPalette: { name: string; colors: string[] } | undefined = undefined + ): CurationEntryHelperModel { + const m = this as CurationEntryModel; + return { + ...m, + isDraft: this.isDraft(), + status: this.status, + statusName: CurationEntryStatusDetails[this.status].name, + statusColor: currentColorPalette + ? currentColorPalette.colors[this.status] + : CurationEntryStatusColorPalettes[0].colors[this.status] + }; + } +} diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationEntryTemplate.ts b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationEntryTemplate.ts new file mode 100644 index 0000000000..da200f5a25 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationEntryTemplate.ts @@ -0,0 +1,212 @@ +import { writable, get } from 'svelte/store'; +import { + CurationEntryType, + DefaultCurationEntryCreationModel, + type CurationEntryCreationModel +} from './CurationEntry'; +import { curationStore } from '$lib/stores/CurationStore'; + +export const entryTemplateRegex = /\[TemplateEntry\]\(\?([^)]*)\)/g; + +export interface CurationEntryTemplateModel extends CurationEntryCreationModel { + placement: 'top' | 'bottom'; + createAsDraft: boolean; + autoCreate: boolean; + scrollToEntry: boolean; +} + +export const entryTemplatePopupState = writable<{ + show: boolean; + template?: Partial; + callback?: (newTemplate: CurationEntryTemplateModel) => void; +}>({ + show: false +}); + +export const DefaultCurationEntryTemplate: CurationEntryTemplateModel = { + ...DefaultCurationEntryCreationModel, + placement: 'bottom', + createAsDraft: false, + autoCreate: false, + scrollToEntry: false +}; // booleans should default to false as presence means true + +const nameMapping: Record = { + createAsDraft: 'draft', + autoCreate: 'auto', + scrollToEntry: 'scroll' +}; + +const mapName = (field: string) => { + if (field in nameMapping) { + return nameMapping[field]; + } + return field; +}; + +const fields: (keyof CurationEntryTemplateModel)[] = [ + 'topic', + 'type', + 'name', + 'description', + 'solution', + 'source', + 'comment', + 'status', + 'placement', + 'createAsDraft', + 'autoCreate' +]; + +function getCreationModelParams(entryCreation: Partial) { + const params: Record = {}; + + for (const field of fields) { + const value = entryCreation[field]; + const defaultValue = DefaultCurationEntryTemplate[field]; + if (value !== undefined && value !== defaultValue) { + if (typeof value === 'boolean') { + params[mapName(field)] = null; + } else if (typeof value === 'string') { + params[mapName(field)] = encodeURIComponent(value); + } else { + params[mapName(field)] = encodeURIComponent(value?.toString() ?? ''); + } + } + } + + return params; +} + +export function getTemplateLinkText(curationEntryTemplate: Partial) { + const params = getCreationModelParams(curationEntryTemplate); + const paramString = Object.entries(params) + .map(([k, v]) => (v === null ? k : `${k}=${v}`)) + .join('&'); + return `[TemplateEntry](?${paramString})`; +} + +export function parseTemplateLink(link: string) { + const regex = /\[TemplateEntry\]\(\?([^)]*)\)/; + const match = RegExp(regex).exec(link); + if (!match) { + throw new Error('Invalid template link format'); + } + const paramString = match[1]; + const params: Record = {}; + for (const pair of paramString.split('&')) { + if (pair.includes('=')) { + const [key, value] = pair.split('='); + if (key && value) params[key] = decodeURIComponent(value); + } else if (pair) { + params[pair] = null; + } + } + + // Map params back to model fields + for (const [key, mappedKey] of Object.entries(nameMapping)) { + if (params[mappedKey] !== undefined) { + params[key] = params[mappedKey]; + delete params[mappedKey]; + } + } + + const template: CurationEntryTemplateModel = { ...DefaultCurationEntryTemplate }; + + Object.entries(params).forEach(([key, value]) => { + const field = key as keyof CurationEntryTemplateModel; + if ( + value !== null && + value !== undefined && + typeof DefaultCurationEntryTemplate[field] === 'string' + ) { + (template as any)[field] = value; + } else if ( + value !== null && + value !== undefined && + typeof DefaultCurationEntryTemplate[field] === 'number' + ) { + if (field === 'type') { + const intValue = parseInt(value); + if (!isNaN(intValue)) { + template.type = intValue; + } + } else if (field === 'status') { + const intValue = parseInt(value); + if (!isNaN(intValue)) { + template.status = intValue; + } + } + } else if (value === null && typeof DefaultCurationEntryTemplate[field] === 'boolean') { + (template as any)[field] = true; + } + }); + + return template; +} + +export function createEntryFromTemplate( + template: CurationEntryTemplateModel, + scrollToEntry = true +): Promise { + const type = template.type ?? DefaultCurationEntryCreationModel.type; + const highestPosition = get(curationStore.curation)?.highestPositionPerType?.[type]; + let position: number; + if (template.placement === 'top' || highestPosition === undefined) { + position = 1; + } else { + position = highestPosition + 1; + } + const entryModel = { + ...template, + position + }; + return curationStore.addEmptyEntry( + entryModel, + false, + template.createAsDraft ?? false, + scrollToEntry + ); +} + +export const entriesFromTemplatesProgress = writable(-1); + +entriesFromTemplatesProgress.subscribe((value) => { + if (value === 100) { + setTimeout(() => entriesFromTemplatesProgress.set(-1), 500); + } +}); + +export function createEntriesFromTemplates( + templates: CurationEntryTemplateModel[], + asDraft = true +): Promise { + entriesFromTemplatesProgress.set(0); + const total = templates.length; + let completed = 0; + const filteredTemplates = templates.filter((t) => t.type !== CurationEntryType.StatusEntryItem); + const run = async () => { + for (const [i, template] of filteredTemplates.entries()) { + await createEntryFromTemplate( + { ...template, createAsDraft: asDraft }, + i === filteredTemplates.length - 1 + ); + completed++; + entriesFromTemplatesProgress.set((completed / total) * 100); + } + entriesFromTemplatesProgress.set(1); + }; + return run(); +} + +export function getAllAutoTemplates(markdown: string) { + const matches = markdown.matchAll(entryTemplateRegex); + const templates: CurationEntryTemplateModel[] = []; + for (const match of matches) { + const template = parseTemplateLink(match[0]); + if (template.autoCreate) { + templates.push(template); + } + } + return templates; +} diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationHelp.ts b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationHelp.ts new file mode 100644 index 0000000000..f264129a89 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationHelp.ts @@ -0,0 +1,13 @@ +export enum curationHelpType { + empty = 0, + mainResearcher = 1, + mainCurator = 2, + tasks = 3 +} + +export const helpTypeNames = [ + 'nothing', // empty = 0, + 'users', // mainResearcher = 1, + 'curators', // mainCurator = 2, + 'tasks' // tasks = 3, +]; diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationNote.ts b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationNote.ts new file mode 100644 index 0000000000..5dd4c19618 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationNote.ts @@ -0,0 +1,77 @@ +import { CurationUserType } from './CurationUser'; + +export interface CurationNoteModel { + id: number; + userType: CurationUserType; + creationDate: string; + comment: string; + userId: number; +} + +export enum CurationNoteCommandType { + None, + Read, + Unread +} + +export const commandTypeMapping = { + [CurationNoteCommandType.None]: 'none', + [CurationNoteCommandType.Read]: 'read', + [CurationNoteCommandType.Unread]: 'unread' +}; + +export class CurationNoteClass implements CurationNoteModel { + // Properties from CurationNoteModel + public readonly id: number; + public readonly userType: CurationUserType; + public readonly creationDate: string; + public readonly comment: string; + public readonly userId: number; + + // Additional properties + public readonly creationDateObj: Date; + public readonly hidden: boolean; + public readonly readableComment: string; + public readonly command: CurationNoteCommandType; + + constructor(curationNote: CurationNoteModel, escape: boolean = false) { + this.id = curationNote.id || 0; + this.userType = curationNote.userType || CurationUserType.User; + this.creationDate = curationNote.creationDate || ''; + if (escape) { + this.comment = curationNote.comment.replace(/\\/g, '\\\\') || ''; + } else { + this.comment = curationNote.comment || ''; + } + this.userId = curationNote.userId || 0; + // derive additional properties + this.creationDateObj = new Date(curationNote.creationDate); + this.hidden = this.isHidden(); + this.readableComment = this.getReadableComment(); + this.command = this.getCommandType(); + } + + private isHidden(): boolean { + if (this.comment.length > 1) { + return this.comment.startsWith('\\') && this.comment[1] !== '\\'; + } + return this.comment.startsWith('\\'); + } + + private getReadableComment(): string { + return this.comment.replace(/\\/, ''); + } + + private getCommandType(): CurationNoteCommandType { + if (this.hidden) { + if (this.comment.startsWith(`\\${commandTypeMapping[CurationNoteCommandType.Read]}`)) { + return CurationNoteCommandType.Read; + } else if ( + this.comment.startsWith(`\\${commandTypeMapping[CurationNoteCommandType.Unread]}`) + ) { + return CurationNoteCommandType.Unread; + } + } + return CurationNoteCommandType.None; + } +} diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationStatusEntry.ts b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationStatusEntry.ts new file mode 100644 index 0000000000..aa83608e82 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationStatusEntry.ts @@ -0,0 +1,69 @@ +// Curation Status (for full curation not for entries) +export enum CurationStatus { + Check = 0, + Back = 1, + Changes = 2, + Finished = 3 +} + +export type CurationLabelModel = { + name: string; + color: string; +}; + +export function noteCommentToLabel(noteComment: string) { + return { + name: /^\S*\s/.exec(noteComment)?.toString().trim(), + color: RegExp(/\s#[0-9a-fA-F]+$/) + .exec(noteComment) + ?.toString() + .slice(1, 8) + } as CurationLabelModel; +} + +export const CurationStatusLabels = [ + { name: 'check', bgColor: '#D55E00', fontColor: 'white' }, + { name: 'back-to-author', bgColor: '#56B4E9', fontColor: 'white' }, + { name: 'changes', bgColor: '#CC79A7', fontColor: 'white' }, + { name: 'finished', bgColor: '#004D40', fontColor: 'white' } +]; + +export function getCurationStatusFromBoolean( + userIsDone: boolean, + isApproved: boolean +): CurationStatus { + if (userIsDone && isApproved) return 3; + if (userIsDone && !isApproved) return 2; + if (!userIsDone && isApproved) return 1; + return 0; +} + +export function getBooleanFromCurationStatus(statusIndex: CurationStatus) { + if (statusIndex === 0) { + return { userIsDone: false, isApproved: false }; + } else if (statusIndex === 1) { + return { userIsDone: false, isApproved: true }; + } else if (statusIndex === 2) { + return { userIsDone: true, isApproved: false }; + } else if (statusIndex === 3) { + return { userIsDone: true, isApproved: true }; + } +} + +export type taskLine = { + id: string; + fullString: string; + text: string; + indentation: number; + isListItem: boolean; + isBold: boolean; + isCheckbox: boolean; + isChecked: boolean | undefined; + entryTemplateMD: string | undefined; +}; + +export enum CurationStatusEntryTab { + Greeting, + Tasks, + Hide +} diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationUser.ts b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationUser.ts new file mode 100644 index 0000000000..95751b13c4 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationUser.ts @@ -0,0 +1,24 @@ +export interface CurationUserModel { + id: number; + displayName: string; + curationUserType: CurationUserType; +} + +export enum CurationUserType { + User, + Curator +} + +export const fixedCurationUserId = 1; // This is set by the backend when requesting the curation entries + +export class CurationUserClass implements CurationUserModel { + public readonly id: number; + public readonly displayName: string; + public readonly curationUserType: CurationUserType; + + constructor(curationUser: CurationUserModel) { + this.id = curationUser.id || 0; + this.displayName = curationUser.displayName || 'None'; + this.curationUserType = curationUser.curationUserType || CurationUserType.User; + } +} diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationsTable.ts b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationsTable.ts new file mode 100644 index 0000000000..75c108fbed --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/models/CurationsTable.ts @@ -0,0 +1,21 @@ +import type { CurationLabelModel } from './CurationStatusEntry'; + +export interface CurationsOverviewModel { + datasets: CurationDetailModel[]; + curationLabels: CurationLabelModel[]; +} + +export interface CurationDetailModel { + datasetId: number; + datasetName: string; + notesComments: string[]; + curationStarted: boolean; + userIsDone: boolean; + isApproved: boolean; + lastChangeDatetime_Curator: string; + lastChangeDatetime_User: string; + count_UserIsDone_True_IsApproved_True: number; + count_UserIsDone_True_IsApproved_False: number; + count_UserIsDone_False_IsApproved_True: number; + count_UserIsDone_False_IsApproved_False: number; +} diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/services/curationService.ts b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/services/curationService.ts new file mode 100644 index 0000000000..9e204391fa --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/services/curationService.ts @@ -0,0 +1,117 @@ +import type { CurationModel } from '$lib/models/Curation'; +import type { CurationEntryModel } from '$lib/models/CurationEntry'; +import type { CurationsOverviewModel } from '$lib/models/CurationsTable'; +import { Api } from '@bexis2/bexis2-core-ui'; + +/** + * Translate first letter of the keys to lowercase. + * This may not be needed if the backend returns the keys as lowerCase + * + * @param response - response object (could be an array or an object) + * @returns - new object with first letter of the keys in lowercase + */ +function firstLetterToLowerCase(response: any): any { + if (Array.isArray(response)) { + // Only process array elements if they are objects (not strings, numbers, etc.) + return response.map((item) => + typeof item === 'object' && item !== null ? firstLetterToLowerCase(item) : item + ); + } else if (response && typeof response === 'object') { + const newEntry: any = {}; + for (const key in response) { + const newKey = key.charAt(0).toLowerCase() + key.slice(1); + newEntry[newKey] = firstLetterToLowerCase(response[key]); + } + return newEntry; + } + return response; +} + +function fixModel(model: CurationEntryModel): CurationEntryModel { + if (model.name.trim() === '') model.name = 'None'; + if (model.description.trim() === '') model.description = 'None'; + if (model.topic.trim() === '') model.topic = 'None'; + if (model.solution.trim() === '') model.solution = 'None'; + if (model.source.trim() === '') model.source = 'None'; + return model; +} + +const curationEntryModelKeys = [ + 'id', + 'topic', + 'type', + 'datasetId', + 'name', + 'description', + 'solution', + 'position', + 'source', + 'notes', + 'userIsDone', + 'isApproved' +]; + +const curationNoteModelKeys = ['id', 'comment', 'userId']; + +function filterObjectKeys(obj: any, keys: string[]): any { + const filtered: any = {}; + for (const key of keys) { + filtered[key] = obj[key]; + } + return filtered; +} + +function filterCurationEntryModel(obj: any): CurationEntryModel { + const filtered = filterObjectKeys(obj, curationEntryModelKeys); + if (Array.isArray(filtered.notes)) { + filtered.notes = filtered.notes.map((note: any) => + filterObjectKeys(note, curationNoteModelKeys) + ); + } + return filtered as CurationEntryModel; +} + +export const getCurationEntries = async () => { + const response = await Api.get('/api/curationentries'); + + console.log('🎈 ~ GET ~ response:', response); + + response.data = firstLetterToLowerCase(response.data); + + return response.data as CurationsOverviewModel; +}; + +export const getCurationDataset = async (id: number) => { + const response = await Api.get(`/api/datasets/${id}/curation`); + + console.log('🎈 ~ GET by dataset ~ response:', response); + + response.data = firstLetterToLowerCase(response.data); + + return response.data as CurationModel; +}; + +export const putCurationEntry = async (model: CurationEntryModel) => { + model = fixModel(model); + const filteredModel = filterCurationEntryModel(model); + const response = await Api.put('/api/curationentries', filteredModel); + + console.log('🎈 ~ PUT ~ Response:', response); + + response.data = firstLetterToLowerCase(response.data); + + return response.data as CurationEntryModel; +}; + +export const postCurationEntry = async (model: CurationEntryModel) => { + model = fixModel(model); + model.id = 0; + const filteredModel = filterCurationEntryModel(model); + const response = await Api.post('/api/curationentries', filteredModel); + + console.log('🎈 ~ POST ~ response:', response); + + response.data = firstLetterToLowerCase(response.data); + + return response.data as CurationEntryModel; +}; diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/stores/CurationOverviewStore.ts b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/stores/CurationOverviewStore.ts new file mode 100644 index 0000000000..489e6964b1 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/stores/CurationOverviewStore.ts @@ -0,0 +1,43 @@ +import type { CurationDetailModel } from '$lib/models/CurationsTable'; +import type { CurationLabelModel } from '$lib/models/CurationStatusEntry'; +import { getCurationEntries } from '$lib/services/curationService'; +import { writable, type Readable } from 'svelte/store'; + +class OverviewStore { + private readonly _curationDetails = writable([]); + public get curationDetails(): Readable { + return this._curationDetails; + } + private readonly _curationLabels = writable([]); + public get curationLabels(): Readable { + return this._curationLabels; + } + private readonly _isLoading = writable(true); + public get isLoading(): Readable { + return this._isLoading; + } + private readonly _errorMessage = writable(); + public get errorMessage(): Readable { + return this._errorMessage; + } + + public fetch() { + this._curationDetails.set([]); + this._curationLabels.set([]); + this._isLoading.set(true); + this._errorMessage.set(undefined); + getCurationEntries() + .then((response) => { + this._curationDetails.set(response.datasets); + this._curationLabels.set(response.curationLabels); + this._isLoading.set(false); + }) + .catch((error) => { + this._errorMessage.set(error.message); + console.error('🎈 ~ Error fetching curation dataset:', error); + this._isLoading.set(false); + }); + } +} + +export const overviewStore = new OverviewStore(); diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/stores/CurationStore.ts b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/stores/CurationStore.ts new file mode 100644 index 0000000000..10f92543e3 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/stores/CurationStore.ts @@ -0,0 +1,488 @@ +import { derived, writable, type Readable, type Writable } from 'svelte/store'; +import { tick } from 'svelte'; +import { get } from 'svelte/store'; +import { CurationClass, CurationFilterType, type CurationFilterModel } from '$lib/models/Curation'; +import { + CurationEntryClass, + CurationEntryStatus, + CurationEntryStatusColorPalettes, + CurationEntryType, + DefaultCurationEntryCreationModel, + type CurationEntryCreationModel, + type CurationEntryHelperModel +} from '$lib/models/CurationEntry'; +import { CurationStatusEntryTab } from '$lib/models/CurationStatusEntry'; +import { + getCurationDataset, + postCurationEntry, + putCurationEntry +} from '$lib/services/curationService'; + +class CurationStore { + public readonly datasetId = writable(null); + + private readonly _curation = writable(null); + public get curation(): Readable { + return this._curation; + } + private readonly _loadingCuration = writable(true); + public get loadingCuration(): Readable { + return this._loadingCuration; + } + private readonly _loadingError = writable(null); + public get loadingError(): Readable { + return this._loadingError; + } + + private readonly _uploadingEntries = writable([]); + public readonly uploadingEntries: Readable = this._uploadingEntries; + + private readonly debounceList = writable([]); + private timer: NodeJS.Timeout | null = null; + private readonly debounceTime = 1000; // 1 second + + private readonly _entryFilters = writable[]>([]); + + public readonly editMode = writable(false); + public readonly statusColorPalette = writable(CurationEntryStatusColorPalettes[0]); + + public readonly jumpToEntryWhere = writable< + ((entry: CurationEntryHelperModel) => boolean) | undefined + >(); + private jumpToEntryWhereTimer: NodeJS.Timeout | null = null; + + private readonly _entryCardStates = new Map< + number, + Writable<{ + isExpanded: boolean; + editEntryMode: boolean; + inputData: CurationEntryCreationModel | undefined; + }> + >(); + + public readonly currentStatusEntryTab = writable( + CurationStatusEntryTab.Tasks + ); + + public readonly progressInfoExpanded = writable(false); + public readonly curationInfoExpanded = writable(true); + + // Jump to Data and Dispatch Jump + public readonly jumpToDataEnabled = writable(false); + private jumpToDataCallback: + | ((curationEntryHelper: Partial) => void) + | undefined; + public setJumpToDataCallback( + callback: (curationEntryHelper: Partial) => void + ) { + this.jumpToDataCallback = callback; + } + public dispatchJumpToData(curationEntryHelper: Partial) { + this.jumpToDataCallback?.(curationEntryHelper); + } + + constructor() { + this.datasetId.subscribe((datasetId) => { + if (!datasetId) { + this.setState(null, true, null); + return; + } + this.fetch(); + }); + this.jumpToEntryWhere.subscribe((fn) => { + if (this.jumpToEntryWhereTimer) clearTimeout(this.jumpToEntryWhereTimer); + if (fn !== undefined) { + this.jumpToEntryWhereTimer = setTimeout(() => { + this.jumpToEntryWhere.set(undefined); + }, 1000); + } + }); + this.curationInfoExpanded.subscribe((expanded) => { + if (!expanded) { + this.currentStatusEntryTab.set(CurationStatusEntryTab.Hide); + this.progressInfoExpanded.set(false); + } + }); + } + + private setState(curation: CurationClass | null, loading: boolean, error: string | null) { + this._curation.set(curation); + this._loadingCuration.set(loading); + this._loadingError.set(error); + } + + private fetch() { + const datasetId: number | null = get(this.datasetId); + if (!datasetId) { + this.setState(null, false, 'No dataset selected'); + return; + } + this.setState(null, true, null); + + getCurationDataset(datasetId) + .then((response) => { + this.setState(new CurationClass(response), false, null); + }) + .catch((error) => { + this.setState(null, false, error.message); + console.error('🎈 ~ Error fetching curation dataset:', error); + }); + } + + private addEntryToDebounceList(entryId: number) { + this.debounceList.update((ids) => { + if (!ids.includes(entryId)) { + return [...ids, entryId]; + } + return ids; + }); + if (this.timer) { + clearTimeout(this.timer); + } + this.timer = setTimeout(() => { + this.saveDebouncedEntries(); + }, this.debounceTime); + } + + private static getCurationEntry( + entryId: number, + curation: CurationClass | null + ): CurationEntryClass | null { + if (!curation) return null; + const entry = curation.curationEntries.find((entry) => entry.id === entryId); + if (!entry) return null; + return entry; + } + + private applyAndSaveEntry( + entryId: number, + callback: (entry: CurationEntryClass) => CurationEntryClass, + debounce: boolean = false + ) { + this._curation.update((curation) => { + const entry = CurationStore.getCurationEntry(entryId, curation); + if (!entry) return curation; + const newEntry = callback(entry); + if (!debounce) this.saveEntry(newEntry); + else this.addEntryToDebounceList(newEntry.id); + return curation!.addEntry(newEntry); + }); + } + + public saveDebouncedEntries() { + this.debounceList.update((ids) => { + ids.forEach((entryId) => { + this._curation.update((curation) => { + const entry = CurationStore.getCurationEntry(entryId, curation); + if (!entry) return curation; + this.saveEntry(entry); + return curation!; + }); + }); + return []; + }); + this.timer = null; + } + + public addNote(entryId: number, comment: string) { + if (entryId <= 0) return; + this.applyAndSaveEntry(entryId, (entry) => entry.addNote(comment, true)); + } + + public updateNote(entryId: number, noteId: number, comment: string) { + this.applyAndSaveEntry(entryId, (entry) => entry.updateNote(noteId, comment, true)); + } + + public deleteNote(entryId: number, noteId: number) { + this.applyAndSaveEntry(entryId, (entry) => entry.deleteNote(noteId)); + } + + public setUnread(entryId: number, unread: boolean = true) { + this.applyAndSaveEntry(entryId, (entry) => entry.setUnread(unread)); + } + + public toggleStatus(entryId: number) { + this.applyAndSaveEntry(entryId, (entry) => entry.toggleStatus(), true); + } + + public setStatusBoolean( + entryId: number, + newuserIsDone: boolean, + newIsApproved: boolean, + debounce = true + ) { + this.applyAndSaveEntry( + entryId, + (entry) => entry.setStatusBoolean(newuserIsDone, newIsApproved), + debounce + ); + } + + public setStatus(entryId: number, status: CurationEntryStatus, debounce: boolean = true) { + this.applyAndSaveEntry(entryId, (entry) => entry.setStatus(status), debounce); + } + + public setName(entryId: number, name: string, debounce: boolean = false) { + this.applyAndSaveEntry(entryId, (entry) => entry.setName(name), debounce); + } + + public setTopic(entryId: number, topic: string, debounce: boolean = false) { + this.applyAndSaveEntry(entryId, (entry) => entry.setTopic(topic), debounce); + } + + public setDescription(entryId: number, description: string, debounce: boolean = false) { + this.applyAndSaveEntry(entryId, (entry) => entry.setDescription(description), debounce); + } + + public updateEntryPosition(entryId: number, position: number) { + if (position <= 0) return; + this._curation.update((curation) => { + let oldPosition = curation?.getEntryById(entryId)?.position; + let newCuration = curation?.updateEntry(entryId, { position }); + if (!newCuration || newCuration == curation) return curation; + if (newCuration.getEntryById(entryId)?.position === oldPosition) return curation; + this.saveEntry(newCuration.getEntryById(entryId)!); + return newCuration; + }); + } + + public jumpToEntryWithId(entryId: number) { + this.jumpToEntryWhere.set((entry) => entry.id === entryId); + } + + public addEmptyEntry( + entryModel: Partial, + acceptCurationStatusEntry = false, + asDraft = true, + jumpToEntry = false + ): Promise { + return new Promise((resolve) => { + if ( + !acceptCurationStatusEntry && + entryModel.type && + entryModel.type === CurationEntryType.StatusEntryItem + ) + return resolve(undefined); + this._curation.update((curation) => { + if (!curation) return curation; + const result = curation.addEntryModel({ + ...DefaultCurationEntryCreationModel, + ...entryModel + }); + this.handleAddEmptyEntryResult(result, entryModel, jumpToEntry, asDraft, resolve); + return result.curation; + }); + }); + } + + private handleAddEmptyEntryResult( + result: { curation: CurationClass; newEntryId: number }, + entryModel: Partial, + jumpToEntry: boolean, + asDraft: boolean, + resolve: (value: number | void) => void + ) { + if (jumpToEntry) { + if (asDraft) this.editMode.set(true); + if (entryModel.type) { + this.updateEntryFilter( + CurationFilterType.type, + (type: (CurationEntryType | undefined) | undefined) => + type === entryModel.type ? type : undefined + ); + this.removeEntryFilters([CurationFilterType.status, CurationFilterType.search]); + } else { + this.clearEntryFilters(); + } + } + tick().then(() => { + if (jumpToEntry) { + this.jumpToEntryWithId(result.newEntryId); + } + if (!asDraft) + tick().then(() => { + const newEntry = result.curation.getEntryById(result.newEntryId); + if (!newEntry) return; + this.saveEntry(newEntry).then((id) => { + if (jumpToEntry && id) this.jumpToEntryWithId(id); + resolve(id); + }); + }); + else resolve(result.newEntryId); + }); + } + + public updateEntry(entryId: number, updates: Partial) { + this._curation.update((curation) => { + let newCuration = curation?.updateEntry(entryId, updates); + if (!newCuration || newCuration == curation) return curation; + if (!newCuration.getEntryById(entryId)) return curation; + this.saveEntry(newCuration.getEntryById(entryId)!); + return newCuration; + }); + } + + public updateEntryFromEntry(entry: CurationEntryClass) { + this.updateEntry(entry.id, { + position: entry.position, + topic: entry.topic, + type: entry.type, + name: entry.name, + description: entry.description, + solution: entry.solution, + source: entry.source + }); + } + + public saveEntry(entry: CurationEntryClass): Promise { + return new Promise((resolve) => { + const prevEntryId = entry.id; + const entryWasDraft = entry.isDraft(); + this.setEntryLoading(prevEntryId, true); + let f = entryWasDraft ? postCurationEntry : putCurationEntry; + return f(entry) + .then((response) => { + this._curation.update((curation) => { + if (!curation) return curation; + const newEntry = new CurationEntryClass(response, curation.currentUserType); + let newCuration = curation; + if (entryWasDraft) { + newCuration = curation.removeEntry(entry.id); + this._entryCardStates.delete(prevEntryId); + this.setEntryLoading(prevEntryId, false); + } + newCuration = newCuration.addEntry(newEntry); + this.setEntryLoading(newEntry.id, false); + resolve(newEntry.id); + return newCuration; + }); + return response; + }) + .catch((error) => { + console.error('🎈 ~ Error saving entry:', error); + this._uploadingEntries.set([]); + this.fetch(); + resolve(undefined); + }); + }); + } + + public getEntryReadable(entryId: number): Readable { + return derived(this._curation, ($curation) => { + if (!$curation) return null; + const entry = $curation.curationEntries.find((entry) => entry.id === entryId); + if (!entry) return null; + return entry; + }); + } + + public setEntryLoading(entryId: number, loading: boolean) { + this._uploadingEntries.update((ids) => { + if (!loading) { + return ids.filter((id) => id !== entryId); + } + if (!ids.includes(entryId)) { + return [...ids, entryId]; + } + return ids; + }); + } + + public updateEntryFilter( + type: CurationFilterType, + dataUpdateFn: (data: TData | undefined) => TData | undefined, + fn: ((entry: CurationEntryClass, data: TData) => boolean) | undefined = undefined, + isClearedFn: ((data: TData) => boolean) | undefined = undefined + ) { + this._entryFilters.update((filters) => { + if (!filters.map((f) => f.type).includes(type)) { + // create filter + if (!fn || !isClearedFn) return filters; + return [...filters, { type: type, data: dataUpdateFn(undefined), fn, isClearedFn }]; + } + // update filter data + const currentFilter = filters.find((f) => f.type === type)!; + const newData = dataUpdateFn(currentFilter.data); + if (newData === currentFilter.data) return filters; + return [ + ...filters.filter((f) => f.type !== type), + { + type: type, + data: newData, + fn: fn || currentFilter.fn, + isClearedFn: isClearedFn || currentFilter.isClearedFn + } + ]; + }); + } + + public removeEntryFilter(type: CurationFilterType) { + this._entryFilters.update((filters) => { + return filters.filter((f) => f.type !== type); + }); + } + + public removeEntryFilters(types: CurationFilterType[]) { + this._entryFilters.update((filters) => { + const typeSet = new Set(types); + return filters.filter((f) => !typeSet.has(f.type)); + }); + } + + public clearEntryFilters() { + this._entryFilters.set([]); + } + + public readonly hasFiltersApplied = derived(this._entryFilters, (filters) => { + return filters.length > 0 && filters.some((f) => f.isClearedFn && !f.isClearedFn(f.data)); + }); + + public getEntryFilterData(filterType: CurationFilterType) { + return derived(this._entryFilters, (filters) => filters.find((f) => f.type === filterType)); + } + + public getCurrentTypeViewOrder(): Readable { + return derived([this._curation, this.editMode], ([curation, editMode]) => { + const types = curation?.curationEntryTypes ?? []; + return editMode ? [...types, CurationEntryType.None] : types; + }); + } + + public getFilteredEntriesReadable(): Readable { + return derived( + [this._curation, this._entryFilters, this.editMode], + ([curation, filters, editMode]) => { + if (!curation) return []; + const typeSet = new Set(curation.curationEntryTypes); + if (editMode) typeSet.add(CurationEntryType.None); + return curation.curationEntries + .filter((entry) => typeSet.has(entry.type)) + .filter((entry) => filters.every((f) => !f.fn || f.fn(entry, f.data))); + } + ); + } + + public getEntryCardState(entryId: number) { + if (!this._entryCardStates.has(entryId)) + this._entryCardStates.set( + entryId, + writable({ isExpanded: false, editEntryMode: entryId <= 0, inputData: undefined }) + ); + return this._entryCardStates.get(entryId)!; + } + + public resetCardState(entryId: number) { + this._entryCardStates.delete(entryId); + } + + public deleteEntry(entryId: number) { + if (entryId >= 0) return; + this.resetCardState(entryId); + this._curation.update((curation) => { + if (!curation) return curation; + return curation.removeEntry(entryId); + }); + } +} + +export const curationStore = new CurationStore(); diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/utils/ColorUtils.ts b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/utils/ColorUtils.ts new file mode 100644 index 0000000000..5df2ff9461 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/lib/utils/ColorUtils.ts @@ -0,0 +1,23 @@ +// --- The function getContrastColor was generated using Copilot with GPT-4.1 --- +// It calculates the contrast color (black or white) for a given hex color and +// is used for the custom labels, where the curator can set custom colors, +// creating the necessary visual distinction. +export function getContrastColor(hex: string | undefined) { + if (!hex) return '#000000'; + // Remove hash if present + hex = hex.replace('#', ''); + // Expand shorthand form (e.g. "03F") to full form ("0033FF") + if (hex.length === 3) { + hex = hex + .split('') + .map((x) => x + x) + .join(''); + } + const r = parseInt(hex.substring(0, 2), 16); + const g = parseInt(hex.substring(2, 4), 16); + const b = parseInt(hex.substring(4, 6), 16); + // Calculate luminance + const luminance = 0.299 * r + 0.587 * g + 0.114 * b; + return luminance > 186 ? '#000000' : '#ffffff'; +} +// ------------------------------------------------------------------------------ diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/+page.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/+page.svelte index bb453139a1..fcce2a1a34 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/+page.svelte +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/+page.svelte @@ -5,6 +5,7 @@ diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/curation/+page.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/curation/+page.svelte new file mode 100644 index 0000000000..b8943f36e3 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/curation/+page.svelte @@ -0,0 +1,40 @@ + + + + {#if !searchParDatasetId} + + + {:else} + {#if !showExampleIntegration} + +
    + +
    + {:else} + + + {/if} + +
    + + Toggle example for data integration + +
    + {/if} +
    diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/curation/ExampleIntegration.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/curation/ExampleIntegration.svelte new file mode 100644 index 0000000000..9ba726d5ef --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/curation/ExampleIntegration.svelte @@ -0,0 +1,331 @@ + + + +
    +
    +
    +

    Pick Data View

    + + {#each typeViews as typeView} + + {typeView.name} + + {/each} + + {#if currentTypeView === CurationEntryType.PrimaryDataEntryItem} + + {#each primaryDataViews as primaryDataView} + + {primaryDataView.name} + + {/each} + + {/if} +
    + + {#if currentTypeView === CurationEntryType.MetadataEntryItem} + +
    + +
    + + {#if showChanges} + + {:else} +
    + {#each exampleMetadataInputs as metadataInput} + + {/each} +
    + {/if} + {:else if currentTypeView === CurationEntryType.DatastructureEntryItem} +
    + {:else if currentTypeView === CurationEntryType.PrimaryDataEntryItem} + {#if currentPrimaryDataView === PrimaryDataView.eval} +
    + {:else if currentPrimaryDataView === PrimaryDataView.table} + +
    +
    + + + {#each Array(20) as _, colIdx} + + {/each} + + + + {#each Array(50) as _, rowIdx} + + {#each Array(20) as _, colIdx} + + {/each} + + {/each} + +
    + Col {colIdx + 1} +
    + {Math.floor(Math.random() * 1000)} + +
    +
    + {/if} + {/if} +
    + + {#if overlayView} + +
    + {#if !overlayActive} + + {/if} + {#if overlayActive} +
    + {#key datasetId} + jumpToData(event.detail)} + bind:applyTypeFilter + bind:addEntry + bind:jumpToEntryWhere + bind:curationEntriesReadable + /> + {/key} +
    +
    + + +
    + {/if} +
    + {:else} + +
    + {#key datasetId} + jumpToData(event.detail)} + bind:applyTypeFilter + bind:addEntry + bind:jumpToEntryWhere + bind:curationEntriesReadable + /> + {/key} +
    + {/if} +
    + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/metadiff/+page.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/metadiff/+page.svelte new file mode 100644 index 0000000000..f8fd47aa68 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/metadiff/+page.svelte @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/search/+page.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/search/+page.svelte index 564aca5193..afd87f48de 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/search/+page.svelte +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/search/+page.svelte @@ -4,6 +4,7 @@ const controller = 'search'; + const links: linkType[] = [ { label: 'Public Search', url: '/ddm/publicsearch' }, { label: 'Manual', url: '/home/docs/Search%20and%20Download%20Data/' } diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/+page.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/+page.svelte index b1406fac9e..0ffd5c1229 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/+page.svelte +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/+page.svelte @@ -16,8 +16,8 @@ diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/TagInfoEdit.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/TagInfoEdit.svelte index ef55649fa0..553648627c 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/TagInfoEdit.svelte +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/TagInfoEdit.svelte @@ -8,16 +8,21 @@ } from '@bexis2/bexis2-core-ui'; import { add, get, save, updateSearch } from './services'; - import { tagInfoModelStore, withMinorStore } from './stores.js'; + import { tagInfoModelStore, withMinorStore, originalTagInfoModelStore } from './stores.js'; import TablePublish from './table/tablePublish.svelte'; import TableShow from './table/tableShow.svelte'; import TableNr from './table/tableNr.svelte'; import TableText from './table/tableText.svelte'; + import TableTextType from './table/tableTextType.svelte'; + import TableTextAuthor from './table/tableTextAuthor.svelte'; import TableOptions from './table/tableOptions.svelte'; import TableDate from './table/tableDate.svelte'; + import TableVersionNumber from './table/tableVersionNumber.svelte'; import { type TagInfoEditModel, TagType } from './types'; import TableReleaseNote from './table/tableReleaseNote.svelte'; import { createEventDispatcher, onMount } from 'svelte'; + import { Tab } from '@skeletonlabs/skeleton'; + import TableDateCreate from './table/tableDateCreate.svelte'; let container; let id: number = 0; @@ -25,6 +30,7 @@ let rows: number = 3; let promise: Promise; + let originalRows: TagInfoEditModel[] = []; const dispatch = createEventDispatcher(); @@ -36,6 +42,9 @@ promise = get(id); const tagInfos = await promise; rows = tagInfos.length; + // deep copy + originalRows = JSON.parse(JSON.stringify(tagInfos)); + originalTagInfoModelStore.update(() => originalRows); tagInfoModelStore.set(tagInfos); withMinorStore.set(withMinor); @@ -75,14 +84,16 @@ notificationStore.showNotification({ notificationType: notificationType.success, - message: 'Tag is saved.' + message: 'Release tag changes are saved.' }); - + originalTagInfoModelStore.update((arr) => + arr.map((x) => (x.versionId === tagInfo.versionId ? { ...x, ...tagInfo } : x)) + ); dispatch('reload'); } else { notificationStore.showNotification({ notificationType: notificationType.error, - message: 'Tag is not saved.' + message: 'Release tag changes are not saved.' }); } } @@ -94,7 +105,7 @@ if (responce.status === 200) { notificationStore.showNotification({ notificationType: notificationType.success, - message: 'Tag is generated.' + message: 'Release Tag is generated.' }); reload(); @@ -102,7 +113,7 @@ } else { notificationStore.showNotification({ notificationType: notificationType.error, - message: 'Tag is not generated.' + message: 'Release Tag is not generated.' }); } } @@ -113,7 +124,7 @@
    {:then model} -

    Tag Management

    +

    Release Tag Management - Dataset ID {id}

    {:then model} -

    Tag Preview

    +

    Preview

    - - + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/stores.ts b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/stores.ts index 9d21bade37..ce9c382b9d 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/stores.ts +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/stores.ts @@ -2,4 +2,5 @@ import type { TagInfoEditModel } from './types'; import { writable } from 'svelte/store'; export const tagInfoModelStore = writable([]); +export const originalTagInfoModelStore = writable([]); export const withMinorStore = writable(); diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableDate.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableDate.svelte index a98f0d9920..d3a3e9a7a4 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableDate.svelte +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableDate.svelte @@ -16,6 +16,8 @@ } -{#if !emtpy} - {new Date(value).toLocaleString('de-DE')} -{/if} + + {#if !emtpy} + {new Date(value).toLocaleString('de-DE')} + {/if} + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableDateCreate.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableDateCreate.svelte new file mode 100644 index 0000000000..c11986a2ee --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableDateCreate.svelte @@ -0,0 +1,23 @@ + + + + {#if !emtpy} + {new Date(value).toLocaleString('de-DE')} + {/if} + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableNr.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableNr.svelte index 4ddc7002b9..44680c4c8c 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableNr.svelte +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableNr.svelte @@ -4,7 +4,9 @@
    - {#if value > 0} - {value.toFixed(1)} - {/if} + + {#if value > 0} + {value.toFixed(1)} + {/if} +
    diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableOptions.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableOptions.svelte index 3c79893d58..6b71ba732e 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableOptions.svelte +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableOptions.svelte @@ -1,15 +1,16 @@
    - + {row.tagId} + {#if hasChanges && row.tagId > 0} + + {/if} {#if noTag} {#if withMinor} {/if} {/if} diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tablePublish.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tablePublish.svelte index 9a05aefbee..43eceefa6c 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tablePublish.svelte +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tablePublish.svelte @@ -2,17 +2,31 @@ import { SlideToggle } from '@skeletonlabs/skeleton'; import { tagInfoModelStore } from '../stores'; import type { TagInfoEditModel } from '../types'; - import { get } from 'svelte/store'; export let value: boolean; export let row: any; let currentRow: TagInfoEditModel; - $: currentRow = get(tagInfoModelStore).find((x) => x.versionId == row.original.versionId); + $: currentRow = $tagInfoModelStore.find((x) => x.versionId == row.original.versionId); + + // Update the store when the toggle is changed + function togglePublish(versionId: number, value: boolean) { + tagInfoModelStore.update((arr) => + arr.map((x) => (x.versionId === versionId ? { ...x, publish: !!value } : x)) + ); + }
    - {#if currentRow.tagId > 0} - - {/if} +
    + {#if currentRow.tagId > 0} + togglePublish(currentRow.versionId, !!(e.detail ?? e.target?.checked))} + /> + {/if} +
    diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableReleaseNote.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableReleaseNote.svelte index c62522320f..91fdccfd7a 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableReleaseNote.svelte +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableReleaseNote.svelte @@ -1,11 +1,33 @@ - + + + diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableShow.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableShow.svelte index cf7b811934..e5354e063e 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableShow.svelte +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableShow.svelte @@ -2,24 +2,37 @@ import { SlideToggle } from '@skeletonlabs/skeleton'; import { tagInfoModelStore } from '../stores'; import type { TagInfoEditModel } from '../types'; - import { get } from 'svelte/store'; export let value: boolean; export let row: any; let currentRow: TagInfoEditModel; - $: currentRow = get(tagInfoModelStore).find((x) => x.versionId == row.original.versionId); + $: currentRow = + $tagInfoModelStore.find((x) => x.versionId == row.original.versionId) ?? + ({ + versionId: row.original.versionId, + tagId: 0, + show: false + } as TagInfoEditModel); + + // Update the store when the toggle is changed + function toggleShow(versionId: number, value: boolean) { + tagInfoModelStore.update((arr) => + arr.map((x) => (x.versionId === versionId ? { ...x, show: !!value } : x)) + ); + }
    -
    +
    {#if currentRow && currentRow.tagId > 0} toggleShow(currentRow.versionId, !!(e.detail ?? e.target?.checked))} /> {/if}
    diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableText.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableText.svelte index 30795f3989..386c62ede2 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableText.svelte +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableText.svelte @@ -1,6 +1,5 @@ + +
    + + {value} + +
    diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableTextType.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableTextType.svelte new file mode 100644 index 0000000000..b8e8947647 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableTextType.svelte @@ -0,0 +1,9 @@ + + +
    + + {value} + +
    diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableVersionNumber.svelte b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableVersionNumber.svelte new file mode 100644 index 0000000000..6320f2ccb8 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI.Svelte/src/routes/taginfo/table/tableVersionNumber.svelte @@ -0,0 +1,10 @@ + + +
    + + {value} + +
    diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/BExIS.Modules.Ddm.UI.csproj b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/BExIS.Modules.Ddm.UI.csproj index 1df771eb09..d6f4d92c68 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/BExIS.Modules.Ddm.UI.csproj +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/BExIS.Modules.Ddm.UI.csproj @@ -143,14 +143,16 @@ - + + + @@ -270,6 +272,8 @@ + + Web.config diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/Api/CitationsController.cs b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/Api/CitationController.cs similarity index 59% rename from Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/Api/CitationsController.cs rename to Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/Api/CitationController.cs index be99f8a9d8..d1bc7f3f71 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/Api/CitationsController.cs +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/Api/CitationController.cs @@ -1,31 +1,31 @@ using BExIS.App.Bootstrap.Attributes; +using BExIS.Dim.Helpers.Mappings; +using BExIS.Dim.Services.Mappings; using BExIS.Dlm.Entities.Data; using BExIS.Dlm.Services.Data; +using BExIS.Dlm.Services.Party; +using BExIS.Modules.Ddm.UI.Helpers; using BExIS.Modules.Ddm.UI.Models; using BExIS.Security.Entities.Subjects; using BExIS.Security.Services.Authorization; using BExIS.Security.Services.Objects; using BExIS.Security.Services.Subjects; using BExIS.Utils.Route; +using NameParser; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using System.Web; using System.Web.Http; using System.Web.Http.Description; using System.Xml; using System.Xml.Serialization; -using BExIS.Dim.Helpers.Mappings; -using BExIS.Dim.Services.Mappings; -using Newtonsoft.Json; -using System.Net.Http.Headers; using Vaiona.Web.Mvc.Modularity; -using BExIS.Dlm.Services.Party; -using NameParser; -using BExIS.Dim.Services; namespace BExIS.Modules.MCD.UI.Controllers.API { @@ -33,8 +33,8 @@ public class CitationController : ApiController { [BExISApiAuthorize] [GetRoute("api/datasets/citations")] - [ResponseType(typeof(CitationModel))] - public HttpResponseMessage Get([FromUri] Format format = Format.Bibtex) + //[ResponseType(typeof(CitationModel))] + public HttpResponseMessage Get([FromUri] CitationFormat format = CitationFormat.Bibtex) { return GetAllCitations(); } @@ -42,21 +42,11 @@ public HttpResponseMessage Get([FromUri] Format format = Format.Bibtex) [BExISApiAuthorize] [GetRoute("api/datasets/{datasetId}/citations")] [ResponseType(typeof(CitationModel))] - public HttpResponseMessage GetCitationFromLatestVersion(long datasetId, [FromUri] Format format = Format.Bibtex) + public HttpResponseMessage GetCitationFromLatestVersion(long datasetId, [FromUri] CitationFormat format = CitationFormat.Bibtex) { - try { - using (var datasetManager = new DatasetManager()) - { - var datasetVersionId = datasetManager.GetDatasetLatestVersion(datasetId)?.Id; - - if( datasetVersionId == null) - { - return Request.CreateErrorResponse(HttpStatusCode.NotFound, "Dataset version not found."); - } - - return Request.CreateResponse(HttpStatusCode.OK, new CitationModel() { CitationString = "jsdjufkjsdkfjf"}); } + return GetCitation(datasetId, format); } catch(Exception ex) { @@ -67,11 +57,9 @@ public HttpResponseMessage GetCitationFromLatestVersion(long datasetId, [FromUri [BExISApiAuthorize] [GetRoute("api/datasets/{datasetId}/citations/{versionNumber}")] [ResponseType(typeof(CitationModel))] - public HttpResponseMessage GetCitationFromSpecificVersionNumber(long datasetId, int versionNumber, [FromUri] Format format = Format.Bibtex) + public HttpResponseMessage GetCitationFromSpecificVersionNumber(long datasetId, int versionNumber, [FromUri] CitationFormat format = CitationFormat.Bibtex) { - - //return GetCitation(id, format, versionNumber); - return null; + return GetCitation(datasetId, format, versionNumber); } // GET api/Citation/GetCitations @@ -81,7 +69,7 @@ public HttpResponseMessage GetCitationFromSpecificVersionNumber(long datasetId, /// /// Identifier of a dataset [BExISApiAuthorize] - [PostRoute("api/Citation/GetCitations")] + [PostRoute("api/datasets/citations")] public HttpResponseMessage Post([FromBody] CitationDatasetIds data) { var datasetIds = data.DatasetIds.Distinct().ToArray(); @@ -164,8 +152,18 @@ public HttpResponseMessage Post([FromBody] CitationDatasetIds data) return Request.CreateErrorResponse(HttpStatusCode.PreconditionFailed, "The dataset with the id (" + id + ") does not exist."); } - CitationDataModel model = GetModel(datasetVersion); - citationString += generateCitation(id.ToString(), model, isPublic, Format.Text) + "\n\n"; + var moduleSettings = ModuleManager.GetModuleSettings("Ddm"); + var useTags = Convert.ToBoolean(moduleSettings.GetValueByKey("use_tags")); + + CitationDataModel model = CitationsHelper.CreateCitationDataModel(datasetVersion, CitationFormat.Text); + string datasetCitationString = ""; + + if (CitationsHelper.IsCitationDataModelValid(model)) + datasetCitationString = CitationsHelper.GetCitationString(model, CitationFormat.Text, isPublic, datasetVersion.Dataset.Id, useTags); + else + datasetCitationString = "Citation metadata for id " + datasetid + " is incomplete."; + + citationString += datasetCitationString; } } @@ -277,7 +275,7 @@ private HttpResponseMessage GetAllCitations() return response; } - private HttpResponseMessage GetCitation(long id, Format format, int version_number = 0) + private HttpResponseMessage GetCitation(long id, CitationFormat format, int version_number = 0) { DatasetVersion datasetVersion = null; @@ -319,7 +317,8 @@ private HttpResponseMessage GetCitation(long id, Format format, int version_numb } // Check if a dataset version was set - if (datasetVersion == null) return Request.CreateResponse(HttpStatusCode.InternalServerError, "It is not possible to load the latest or given version."); + if (datasetVersion == null) + return Request.CreateResponse(HttpStatusCode.InternalServerError, "It is not possible to load the latest or given version."); //entity permissions if (id > 0) @@ -329,7 +328,6 @@ private HttpResponseMessage GetCitation(long id, Format format, int version_numb // Check if dataset is public long? entityTypeId = entityManager.FindByName(typeof(Dataset).Name)?.Id; entityTypeId = entityTypeId.HasValue ? entityTypeId.Value : -1; - isPublic = false; isPublic = entityPermissionManager.ExistsAsync(entityTypeId.Value, id).Result; // If dataset is not public check if a valid token is provided @@ -348,8 +346,16 @@ private HttpResponseMessage GetCitation(long id, Format format, int version_numb return Request.CreateErrorResponse(HttpStatusCode.PreconditionFailed, "The dataset with the id (" + id + ") does not exist."); } - CitationDataModel model = GetModel(datasetVersion); - string citationString = generateCitation(id.ToString(), model, isPublic, format); + var moduleSettings = ModuleManager.GetModuleSettings("Ddm"); + var useTags = Convert.ToBoolean(moduleSettings.GetValueByKey("use_tags")); + + CitationDataModel model = CitationsHelper.CreateCitationDataModel(datasetVersion, format); + string citationString = ""; + if (CitationsHelper.IsCitationDataModelValid(model)) + citationString = CitationsHelper.GetCitationString(model, format, isPublic, datasetVersion.Dataset.Id, useTags); + else + citationString = "Citation metadata is incomplete."; + HttpResponseMessage response = new HttpResponseMessage { Content = new StringContent(citationString, Encoding.UTF8, "application/text") }; return response; } @@ -367,19 +373,23 @@ private DatasetCitationEntry CreateCitationEntry(long datasetId, CitationDataMod else datasetCitationEntry.URL = url; - datasetCitationEntry.Publisher = settings.GetValueByKey("publisher").ToString(); - datasetCitationEntry.InstanceName = settings.GetValueByKey("instanceName").ToString(); + datasetCitationEntry.Publisher = model.Publisher; + //datasetCitationEntry.InstanceName = settings.GetValueByKey("instanceName").ToString(); - using (PartyManager partyManager = new PartyManager()) + if (model.Projects != null || model.Projects.Count > 0) { - foreach (string project in model.Projects) + using (PartyManager partyManager = new PartyManager()) { - Project p = new Project(); - p.Id = partyManager.Parties.Where(c => c.Name == project).Select(c => c.Id).FirstOrDefault(); - p.Name = project; - datasetCitationEntry.Projects.Add(p); + foreach (string project in model.Projects) + { + Project p = new Project(); + p.Id = partyManager.Parties.Where(c => c.Name == project).Select(c => c.Id).FirstOrDefault(); + p.Name = project; + datasetCitationEntry.Projects.Add(p); + } } } + datasetCitationEntry.DatasetId = datasetId.ToString(); datasetCitationEntry.IsPublic = isPublic; datasetCitationEntry.Title = model.Title; @@ -387,7 +397,7 @@ private DatasetCitationEntry CreateCitationEntry(long datasetId, CitationDataMod if (!String.IsNullOrEmpty(model.DOI)) datasetCitationEntry.DOI = model.DOI; datasetCitationEntry.Authors = model.Authors; - datasetCitationEntry.Year = getYear(model.Date); + datasetCitationEntry.Year = model.Year; //create authorname in the correct format List authors = new List(); @@ -400,230 +410,14 @@ private DatasetCitationEntry CreateCitationEntry(long datasetId, CitationDataMod authors.Add(name.Last + ", " + name.First + " " + name.Middle); } - datasetCitationEntry.CitationStringTxt = generateText(datasetCitationEntry.DatasetId, model.Title, datasetCitationEntry.Year, model.Version, model.DOI, datasetCitationEntry.URL, datasetCitationEntry.Publisher, datasetCitationEntry.InstanceName, authors, isPublic); - - return datasetCitationEntry; - } - - private string generateCitation(string datasetId, CitationDataModel model, bool isPublic, Format format) - { - string citationString = ""; - var settings = ModuleManager.GetModuleSettings("ddm"); - string url = HttpContext.Current.Request.Url.Host; - - string publisher = settings.GetValueByKey("publisher").ToString(); - string instanceName = settings.GetValueByKey("instanceName").ToString(); - - string year = getYear(model.Date); - - //create authorname in the correct format - List authors = new List(); - foreach (string author in model.Authors) + var useTags = Convert.ToBoolean(settings.GetValueByKey("use_tags")); + if (CitationsHelper.IsCitationDataModelValid(model)) { - HumanName name = new HumanName(author); - if (String.IsNullOrEmpty(name.Middle)) - authors.Add(name.Last + ", " + name.First); - else - authors.Add(name.Last + ", " + name.Middle + " " + name.First); - } - - switch (format) - { - case Format.Bibtex: - citationString = generateBibtex(datasetId, model.Title, year, model.Version, model.DOI, url, publisher, instanceName, authors, isPublic); - break; - case Format.RIS: - citationString = generateRis(datasetId, model.Title, year, model.Version, model.DOI, url, publisher, instanceName, authors, isPublic); - break; - case Format.Text: - citationString = generateText(datasetId, model.Title, year, model.Version, model.DOI, url, publisher, instanceName, authors, isPublic); - break; + datasetCitationEntry.CitationStringTxt = CitationsHelper.GetCitationString(model, CitationFormat.Text, isPublic, datasetId, useTags); + return datasetCitationEntry; } - - return citationString; - } - - private CitationDataModel GetModel(DatasetVersion datasetVersion) - { - CitationDataModel model = new CitationDataModel(); - - XmlDocument xmlDoc = datasetVersion.Metadata; - string citationString = ""; - //get citation concept - using (var conceptManager = new ConceptManager()) - { - var concept = conceptManager.MappingConceptRepository.Get().Where(c => c.Name.Equals("Citation")).FirstOrDefault(); - - long mdId = datasetVersion.Dataset.MetadataStructure.Id; - - xmlDoc = MappingUtils.GetConceptOutput(mdId, concept.Id, xmlDoc); - - XmlSerializer serializer = new XmlSerializer(typeof(CitationDataModel), new XmlRootAttribute("data")); - using (XmlReader reader = new XmlNodeReader(xmlDoc)) - { - model = (CitationDataModel)serializer.Deserialize(reader); - } - - if(String.IsNullOrEmpty(model.Version)) - model.Version = datasetVersion.VersionNo.ToString(); - if(String.IsNullOrEmpty(model.Date)) - { - if(String.IsNullOrEmpty(datasetVersion.PublicAccessDate.ToString())) - model.Date = datasetVersion.PublicAccessDate.ToString(); - else - model.Date = datasetVersion.Timestamp.ToString(); - } - if(String.IsNullOrEmpty(model.DOI)) - { - using (var publicationManager = new PublicationManager()) - { - var pub = publicationManager.GetPublication(datasetVersion.Dataset.Id); - if(pub != null) - { - if(!String.IsNullOrEmpty(pub.Doi)) - model.DOI = pub.Doi; - } - } - } - - } - - return model; - } - - private string generateBibtex(string datasetId, string title, string year, string version, string doi, string url, string publisher, string instanceName, List authors, bool isPublic) - { - string bibtex = "@misc{" + instanceName + "_" + datasetId + "_v" + version + "\n"; - bibtex += "title ={" + title + "},\n"; - bibtex += "author ={"; - var lastAuthor = authors.Last(); - foreach (string author in authors) - { - if (author == lastAuthor) - bibtex += author + ""; - else - bibtex += author + " and "; - } - bibtex += "},\n"; - bibtex += "version ={" + version + "},\n"; - bibtex += "year ={" + year + "},\n"; - bibtex += "publisher ={" + publisher + "},\n"; - if (isPublic) - { - url += "/ddm/data/Showdata/" + datasetId + "?version=" + version + ""; - bibtex += "url ={" + url + "},\n"; - } - else - bibtex += "url ={" + url + "},\n"; - - if (!String.IsNullOrEmpty(doi)) - bibtex += "doi ={" + doi + "},\n"; - - if (isPublic) - bibtex += "type ={Dataset. Published.},\n"; - else - bibtex += "type ={Dataset. Unpublished.},\n"; - - bibtex += "note ={Dataset ID: " + datasetId + "},\n"; - bibtex += "}"; - - return bibtex; - } - - private string generateRis(string datasetId, string title, string year, string version, string doi, string url, string publisher, string instanceName, List authors, bool isPublic) - { - string ris = "TY - DATA \n"; - ris += "T1 - " + title + "\n"; - foreach (string author in authors) - { - ris += "AU - " + author + "\n"; - } - ris += "PY - " + year + "/// \n"; - ris += "PB - " + publisher + " \n"; - - if (!String.IsNullOrEmpty(doi)) - ris += "DO - " + doi + "\n"; - - if (isPublic) - { - url += "/ddm/data/Showdata/" + datasetId + "?version=" + version + ""; - ris += "UR - " + url + "\n"; - } - else - ris += "UR - " + url + "\n"; - - if (isPublic) - ris += "N1 - Dataset ID: " + datasetId + ", Published. \n"; else - ris += "N1 - Dataset ID: " + datasetId + ", Unpublished. \n`"; - ris += "ER -"; - - return ris; + return null; } - - private string generateText(string datasetId, string title, string year, string version, string doi, string url, string publisher, string instanceName, List authors, bool isPublic) - { - string text = ""; - var lastAuthor = authors.Last(); - foreach (string author in authors) - { - if (author.Equals(lastAuthor)) - text += author; - else - text += author + "; "; - } - text += " (" + year + "): "; - text += title + ". "; - text += "Version " + version + ". "; - text += publisher + ". "; - text += "Dataset. "; - if (!String.IsNullOrEmpty(doi)) - { - text += doi; - } - else - { - if (isPublic) - { - url += "/ddm/data/Showdata/" + datasetId + "?version=" + version + ""; - text += url + ". "; - } - else - text += url + ". "; - - text += "Dataset ID= " + datasetId; - } - - return text; - } - - private string getYear(string dateString) - { - - string year = ""; - var date_time_parts = dateString.Split(' '); - // Format: "3/6/2022 7:47:10 PM" - if (dateString.Contains("/")) - { - var date_parts = date_time_parts[0].Split('/'); - year = date_parts[2]; - } - // Format: "2008-02-22" - else - { - var date_parts = date_time_parts[0].Split('-'); - year = date_parts[0]; - } - - return year; - } - - public enum Format - { - RIS, - Text, - Bibtex - } - } } \ No newline at end of file diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/CurationController.cs b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/CurationController.cs new file mode 100644 index 0000000000..bbdfc99305 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/CurationController.cs @@ -0,0 +1,17 @@ +using System.Web.Mvc; +using BExIS.UI.Helpers; + +namespace BExIS.Modules.Ddm.UI.Controllers +{ + public class CurationController : Controller + { + public ActionResult Index(long id = 0) + { + string module = "DDM"; + ViewData["app"] = SvelteHelper.GetApp(module); + ViewData["start"] = SvelteHelper.GetStart(module); + ViewData["id"] = id; + return View(); + } + } +} \ No newline at end of file diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/DashboardController.cs b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/DashboardController.cs index 6b392a9640..eb12258a91 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/DashboardController.cs +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/DashboardController.cs @@ -320,13 +320,16 @@ public ActionResult ShowMyDatasets(string entityname, RightType rightType, strin } List datasetVersions = datasetManager.GetDatasetLatestVersions(datasetIds, true); + foreach (var dsv in datasetVersions) { Object[] rowArray = new Object[8]; string isValid = "no"; + bool isOwn = rightType == RightType.Grant || rightType == RightType.Write ? true : false ; + string type = "file"; - if (dsv.Dataset.DataStructure?.Self is StructuredDataStructure) + if (dsv.Dataset.DataStructure!=null) { type = "tabular"; } @@ -355,7 +358,7 @@ public ActionResult ShowMyDatasets(string entityname, RightType rightType, strin } // - rowArray[7] = entityPermissionManager.HasEffectiveRightsAsync(HttpContext.User.Identity.Name, typeof(Dataset), dsv.Dataset.Id, RightType.Write).Result; + rowArray[7] = isOwn; model.Add(new MyDatasetsModel( (long)rowArray[0], diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/DataController.cs b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/DataController.cs index 0e241b1d66..e4886cceae 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/DataController.cs +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/DataController.cs @@ -1,5 +1,6 @@ using BExIS.App.Bootstrap.Attributes; using BExIS.Dim.Entities.Export.GBIF; +using BExIS.Dim.Entities.Mappings; using BExIS.Dim.Entities.Publications; using BExIS.Dim.Helpers.BIOSCHEMA; using BExIS.Dim.Helpers.Mappings; @@ -7,6 +8,7 @@ using BExIS.Dim.Services.Mappings; using BExIS.Dlm.Entities.Data; using BExIS.Dlm.Entities.DataStructure; +using BExIS.Dlm.Entities.MetadataStructure; using BExIS.Dlm.Entities.Party; using BExIS.Dlm.Services.Data; using BExIS.Dlm.Services.DataStructure; @@ -108,6 +110,8 @@ public ActionResult Show(long id, long version = 0) //get the researchobject (cuurently called dataset) to get the id of a metadata structure Dataset researcobject = this.GetUnitOfWork().GetReadOnlyRepository().Get(id); + + if (researcobject != null) { long metadataStrutcureId = researcobject.MetadataStructure.Id; @@ -169,7 +173,12 @@ public ActionResult ShowData(long id, int version = 0, bool asPartial = false, s ViewData["use_minor"] = moduleSettings.GetValueByKey("use_minor"); ViewData["check_public_metadata"] = moduleSettings.GetValueByKey("check_public_metadata"); ViewData["has_data"] = false; + ViewData["data_aggreement"] = moduleSettings.GetValueByKey("data_aggreement"); Session["Filter"] = null; + // reset sessions + Session["DataFilter"] = null; + Session["DataOrderBy"] = null; + Session["DataProjection"] = null; Dataset researcobject = dm.GetDataset(id); @@ -201,6 +210,12 @@ public ActionResult ShowData(long id, int version = 0, bool asPartial = false, s // Retrieve data for active and hidden (marked as deleted) datasets if (dm.IsDatasetCheckedIn(id) || dm.IsDatasetDeleted(id)) { + // check is public + long? entityTypeId = entityManager.FindByName(typeof(Dataset).Name)?.Id; + entityTypeId = entityTypeId.HasValue ? entityTypeId.Value : -1; + + isPublic = entityPermissionManager.ExistsAsync(entityTypeId.Value, id).Result; + List datasetVersions = dm.GetDatasetVersions(id); List datasetVersionsAllowed = new List(); @@ -245,7 +260,7 @@ public ActionResult ShowData(long id, int version = 0, bool asPartial = false, s // Throw error if no version id was found. if (versionId <= 0) { - ModelState.AddModelError("", string.Format("The version with the requested name {1} or id {0} does not exist or is not publicly accessible", version, versionName)); + ModelState.AddModelError("", string.Format("The requested version (release tag or version ID: {0}{1}) could not be found or you don’t have permission to access it.", version, versionName)); } else { @@ -269,11 +284,7 @@ public ActionResult ShowData(long id, int version = 0, bool asPartial = false, s researchPlanId = dsv.Dataset.ResearchPlan.Id; metadata = dsv.Metadata; - // check is public - long? entityTypeId = entityManager.FindByName(typeof(Dataset).Name)?.Id; - entityTypeId = entityTypeId.HasValue ? entityTypeId.Value : -1; - - isPublic = entityPermissionManager.ExistsAsync(entityTypeId.Value, id).Result; + // check if the user has download rights downloadAccess = entityPermissionManager.HasEffectiveRightsAsync(HttpContext.User.Identity.Name, typeof(Dataset), id, RightType.Read).Result; @@ -336,6 +347,22 @@ public ActionResult ShowData(long id, int version = 0, bool asPartial = false, s ViewData["State"] = "hidden"; ViewData["HasEditRight"] = model.HasEditRight; + var deletedVersion = dm.GetDeletedDatasetLatestVersion(id); + latestVersion = true; + latestVersionId = deletedVersion.Id; + versionId = deletedVersion.Id; + version = dm.GetDatasetVersionNr(versionId); + latestVersionNr = dm.GetDatasetVersionNr(latestVersionId); + + if (deletedVersion != null && deletedVersion.StateInfo != null) + { + isValid = DatasetStateInfo.Valid.ToString().Equals(deletedVersion.StateInfo.State) ? "yes" : "no"; + ViewData["IsValid"] = isValid; + } + + title = deletedVersion != null ? deletedVersion.Title : "n.a."; + labels = getLabels(id, versionId, tag, deletedVersion.Dataset.EntityTemplate.Name); + model.GrantAccess = false; model.ViewAccess = false; model.DownloadAccess = false; @@ -371,9 +398,6 @@ public ActionResult ShowData(long id, int version = 0, bool asPartial = false, s } - - - // load all hooks for the edit view HookManager hooksManager = new HookManager(); model.Hooks = hooksManager.GetHooksFor("dataset", "details", HookMode.view); @@ -415,13 +439,23 @@ public ActionResult ShowData(long id, int version = 0, bool asPartial = false, s } [ChildActionOnly] - public async Task GetCitationOrTitle(long datasetVersionId) + public async Task GetCitationOrTitle(long Id, long datasetVersionId) { try { using (var datasetManager = new DatasetManager()) + using (var conceptManager = new ConceptManager()) { - var datasetVersion = datasetManager.GetDatasetVersion(datasetVersionId); + var dataset = datasetManager.GetDataset(Id); + DatasetVersion datasetVersion = null; + if (dataset.Status == DatasetStatus.Deleted) + { + datasetVersion = datasetManager.GetDeletedDatasetLatestVersion(Id); + } + else + { + datasetVersion = datasetManager.GetDatasetVersion(datasetVersionId); + } if (datasetVersion == null) { @@ -431,12 +465,16 @@ public async Task GetCitationOrTitle(long datasetVersionId) var settingsHelper = new SettingsHelper(); var citationSettings = settingsHelper.GetCitationSettings(); - if (citationSettings == null || !citationSettings.ShowCitation) + var errors = new List(); + string conceptName = "Citation_" + citationSettings.ReadCitationFormat; + var concept = conceptManager.FindByName(conceptName); + + if (citationSettings == null || !citationSettings.ShowCitation || concept == null || !MappingUtils.IsMapped(datasetVersion.Dataset.MetadataStructure.Id, LinkElementType.MetadataStructure, concept.Id, LinkElementType.MappingConcept, out errors)) { return PartialView("_Title", datasetVersion.Title); } - var model = CitationsHelper.GetCitationDataModel(datasetVersionId); + var model = CitationsHelper.CreateCitationDataModel(datasetVersion); if (model == null) return PartialView("_Title", datasetVersion.Title); @@ -580,11 +618,11 @@ public ActionResult Reload(long id, int version = 0) } [BExISEntityAuthorize(typeof(Dataset), "id", RightType.Read)] - public ActionResult DownloadZip(long id, string format, long version = -1) + public ActionResult DownloadZip(long id, string format, long version = -1,bool withFilter = false, bool withUnits=false) { if (this.IsAccessible("DIM", "Export", "GenerateZip")) { - var actionresult = this.Run("DIM", "Export", "GenerateZip", new RouteValueDictionary() { { "id", id }, { "versionid", version }, { "format", format } }); + var actionresult = this.Run("DIM", "Export", "GenerateZip", new RouteValueDictionary() { { "id", id }, { "versionid", version }, { "format", format }, { "withFilter", withFilter }, { "withUnits", withUnits } }); return actionresult; } @@ -778,6 +816,7 @@ public ActionResult ShowPrimaryData(long datasetID, int versionId) } ViewData["gridTotal"] = dm.RowCount(dataset.Id, null); + ViewData["isPublic"] = entityPermissionManager.ExistsAsync(dataset.EntityTemplate.EntityType.Id, dataset.Id).Result; sds.Variables = sds.Variables.OrderBy(v => v.OrderNo).ToList(); @@ -858,6 +897,13 @@ public ActionResult _CustomPrimaryDataBinding(GridCommand command, string column FilterExpression filter = TelerikGridHelper.Convert(command.FilterDescriptors.ToList()); OrderByExpression orderBy = TelerikGridHelper.Convert(command.SortDescriptors.ToList()); + ProjectionExpression projection = null; + if(!string.IsNullOrEmpty(columns)) projection = TelerikGridHelper.Convert(columns.Replace("ID", "").Split(',')); + + Session["DataFilter"] = filter; + Session["DataOrderBy"] = orderBy; + Session["DataProjection"] = projection; + table = dm.GetLatestDatasetVersionTuples(datasetId, filter, orderBy, null, "", command.Page - 1, command.PageSize); ViewData["gridTotal"] = dm.RowCount(datasetId, filter); @@ -1417,6 +1463,7 @@ private DataTable getFilteredData(long datasetId) ProjectionExpression projection = TelerikGridHelper.Convert(columns); + long count = datasetManager.RowCount(datasetId, filter); DataTable table = datasetManager.GetLatestDatasetVersionTuples(datasetId, filter, orderBy, projection, "", 0, (int)count); @@ -1827,7 +1874,8 @@ private SelectList getVersionsSelectList(long id, DatasetManager datasetManager) } // user has edit permission and can see all versions -> show full list - if (hasEditPermission || helper.GetValue("reduce_versions_select_logged_in").ToString() == "false") + var moduleSettings = ModuleManager.GetModuleSettings("Ddm"); + if (hasEditPermission || !Convert.ToBoolean(moduleSettings.GetValueByKey("reduce_versions_select_logged_in"))) { datasetVersionsAllowed = datasetVersions; } diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/MetadiffController.cs b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/MetadiffController.cs new file mode 100644 index 0000000000..fd40d0f3b1 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/MetadiffController.cs @@ -0,0 +1,17 @@ +using System.Web.Mvc; +using BExIS.UI.Helpers; + +namespace BExIS.Modules.Ddm.UI.Controllers +{ + public class MetadiffController : Controller + { + public ActionResult Index(long id = 0) + { + string module = "DDM"; + ViewData["app"] = SvelteHelper.GetApp(module); + ViewData["start"] = SvelteHelper.GetStart(module); + ViewData["id"] = id; + return View(); + } + } +} \ No newline at end of file diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/PublicSearchController.cs b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/PublicSearchController.cs index 415e580050..4f63b27b50 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/PublicSearchController.cs +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/PublicSearchController.cs @@ -1,4 +1,5 @@ -using BExIS.Ddm.Api; +using BExIS.App.Bootstrap.Attributes; +using BExIS.Ddm.Api; using BExIS.UI.Helpers; using BExIS.Utils.Models; using Newtonsoft.Json; @@ -11,6 +12,7 @@ using Vaiona.IoC; using Vaiona.Web.Extensions; using Vaiona.Web.Mvc.Models; +using Vaiona.Web.Mvc.Modularity; namespace BExIS.Modules.Ddm.UI.Controllers { @@ -23,6 +25,9 @@ public ActionResult Index() ViewData["app"] = SvelteHelper.GetApp(module); ViewData["start"] = SvelteHelper.GetStart(module); + var settings = ModuleManager.GetModuleSettings("DDM"); + ViewData["search_result_presentation"] = settings.GetValueByKey("search_result_presentation").ToString(); + return View(); } @@ -55,7 +60,7 @@ public JsonResult Query() /// /// /// - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult Query(string autoComplete, string FilterList, string searchType) { ViewBag.Title = PresentationModel.GetViewTitleForTenant("Search in Public Datasets", this.Session.GetTenant()); @@ -100,7 +105,7 @@ public JsonResult Query(string autoComplete, string FilterList, string searchTyp /// /// /// - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult FilterByDropDownList(string SelectedFilter, string searchType) { ViewBag.Title = PresentationModel.GetViewTitleForTenant("Search", this.Session.GetTenant()); @@ -115,7 +120,7 @@ public JsonResult FilterByDropDownList(string SelectedFilter, string searchType) /// /// /// - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult _AutoCompleteAjaxLoading(string text) { ISearchProvider provider = IoCFactory.Container.ResolveForSession(); @@ -131,7 +136,7 @@ public JsonResult _AutoCompleteAjaxLoading(string text) /// consist the searchType /// /// - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public void ChangeSearchValuesACBySearchType(string value) { ISearchProvider provider = IoCFactory.Container.ResolveForSession(); @@ -151,7 +156,7 @@ public void ChangeSearchValuesACBySearchType(string value) /// show the status of the checkbox (true = selected/false=deselected) /// /// - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult ToggleFacet(string SelectedItem, string Parent) { ISearchProvider provider = IoCFactory.Container.ResolveForSession(); @@ -222,7 +227,7 @@ public JsonResult OnSelectTreeViewItem(string SelectedItem, string Parent) /// /// /// - [HttpPost] + [HttpPost] public JsonResult AddFacetsToSearch() { ViewBag.Title = PresentationModel.GetViewTitleForTenant("Search", this.Session.GetTenant()); @@ -327,7 +332,7 @@ public JsonResult RemoveSearchCriteria(string value, string parent) #endregion BreadcrumbView #region Datagrid - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult GetTableData() { ISearchProvider provider = IoCFactory.Container.ResolveForSession(); @@ -348,14 +353,14 @@ public JsonResult SetResultViewVar(string key, string value) #region Properties _searchProperties //+++++++++++++++++++++ Properties Sliders Action +++++++++++++++++++++++++++ - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult FilterByRangeSlider(int start, int end, string parent) { ISearchProvider provider = IoCFactory.Container.ResolveForSession(); return Json(provider.WorkingSearchModel); } - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult FilterBySlider(int value, string parent) { ISearchProvider provider = IoCFactory.Container.ResolveForSession(); @@ -368,7 +373,7 @@ public JsonResult FilterBySlider(int value, string parent) } //+++++++++++++++++++++Properties DropDown Action +++++++++++++++++++++++++++ - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult FilterByDropDown(string value, string node) { ISearchProvider provider = IoCFactory.Container.ResolveForSession(); @@ -381,7 +386,7 @@ public JsonResult FilterByDropDown(string value, string node) } //+++++++++++++++++++++Properties RadioButton Action +++++++++++++++++++++++++++ - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult FilterByRadioButton(string value, string node, bool isChecked) { ISearchProvider provider = IoCFactory.Container.ResolveForSession(); @@ -393,7 +398,7 @@ public JsonResult FilterByRadioButton(string value, string node, bool isChecked) //+++++++++++++++++++++Properties ´CheckButton Action +++++++++++++++++++++++++++ - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult FilterByCheckBox(string value, string node, bool isChecked) { ISearchProvider provider = IoCFactory.Container.ResolveForSession(); diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/SearchController.cs b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/SearchController.cs index bae70350e0..82b510173d 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/SearchController.cs +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Controllers/SearchController.cs @@ -1,4 +1,6 @@ -using BExIS.Ddm.Api; +using BExIS.App.Bootstrap.Attributes; +using BExIS.Ddm.Api; +using BExIS.Security.Entities.Requests; using BExIS.UI.Helpers; using BExIS.Utils.Models; using BExIS.Xml.Helpers; @@ -7,11 +9,13 @@ using System.Collections.Generic; using System.Data; using System.Linq; +using System.Web.Helpers; using System.Web.Mvc; using Telerik.Web.Mvc; using Vaiona.IoC; using Vaiona.Web.Extensions; using Vaiona.Web.Mvc.Models; +using Vaiona.Web.Mvc.Modularity; using SearchCriteria = BExIS.Utils.Models.SearchCriteria; namespace BExIS.Modules.Ddm.UI.Controllers @@ -29,6 +33,9 @@ public ActionResult Index() ViewData["app"] = SvelteHelper.GetApp(module); ViewData["start"] = SvelteHelper.GetStart(module); + var settings = ModuleManager.GetModuleSettings("DDM"); + ViewData["search_result_presentation"] = settings.GetValueByKey("search_result_presentation").ToString(); + return View(); } @@ -61,7 +68,7 @@ public JsonResult Query() /// /// /// - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult Query(string autoComplete, string FilterList, string searchType) { ViewBag.Title = PresentationModel.GetViewTitleForTenant("Search", this.Session.GetTenant()); @@ -122,7 +129,7 @@ public JsonResult Query(string autoComplete, string FilterList, string searchTyp /// /// /// - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult FilterByDropDownList(string SelectedFilter, string searchType) { ViewBag.Title = PresentationModel.GetViewTitleForTenant("Search", this.Session.GetTenant()); @@ -139,7 +146,7 @@ public JsonResult FilterByDropDownList(string SelectedFilter, string searchType) /// /// /// - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult _AutoCompleteAjaxLoading(string text) { ISearchProvider provider = IoCFactory.Container.ResolveForSession(); @@ -155,7 +162,7 @@ public JsonResult _AutoCompleteAjaxLoading(string text) /// consist the searchType /// /// - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public void ChangeSearchValuesACBySearchType(string value) { ISearchProvider provider = IoCFactory.Container.ResolveForSession(); @@ -175,7 +182,7 @@ public void ChangeSearchValuesACBySearchType(string value) /// show the status of the checkbox (true = selected/false=deselected) /// /// - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult ToggleFacet(string SelectedItem, string Parent) { ISearchProvider provider = IoCFactory.Container.ResolveForSession(); @@ -367,7 +374,7 @@ public JsonResult RemoveSearchCriteria(string value, string parent) #endregion BreadcrumbView #region Datagrid - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult GetTableData() { ISearchProvider provider = IoCFactory.Container.ResolveForSession(); @@ -388,7 +395,7 @@ public JsonResult SetResultViewVar(string key, string value) #region Properties _searchProperties //+++++++++++++++++++++ Properties Sliders Action +++++++++++++++++++++++++++ - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult FilterByRangeSlider(int start, int end, string parent) { ISearchProvider provider = IoCFactory.Container.ResolveForSession(); @@ -397,7 +404,7 @@ public JsonResult FilterByRangeSlider(int start, int end, string parent) return j; } - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult FilterBySlider(int value, string parent) { ISearchProvider provider = IoCFactory.Container.ResolveForSession(); @@ -410,7 +417,7 @@ public JsonResult FilterBySlider(int value, string parent) } //+++++++++++++++++++++Properties DropDown Action +++++++++++++++++++++++++++ - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult FilterByDropDown(string value, string node) { ISearchProvider provider = IoCFactory.Container.ResolveForSession(); @@ -423,7 +430,7 @@ public JsonResult FilterByDropDown(string value, string node) } //+++++++++++++++++++++Properties RadioButton Action +++++++++++++++++++++++++++ - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult FilterByRadioButton(string value, string node, bool isChecked) { ISearchProvider provider = IoCFactory.Container.ResolveForSession(); @@ -435,7 +442,7 @@ public JsonResult FilterByRadioButton(string value, string node, bool isChecked) //+++++++++++++++++++++Properties ´CheckButton Action +++++++++++++++++++++++++++ - [HttpPost] + [HttpPost, CustomValidateAntiForgeryToken] public JsonResult FilterByCheckBox(string value, string node, bool isChecked) { ISearchProvider provider = IoCFactory.Container.ResolveForSession(); diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Ddm.Settings.json b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Ddm.Settings.json index cf505ea9b5..17387f9c27 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Ddm.Settings.json +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Ddm.Settings.json @@ -45,21 +45,62 @@ "type": "Boolean", "description": "If set to true, the minor version tag can be assigned to dataset versions to display changes in more detail." }, + { + "key": "search_result_presentation", + "title": "Search Result Presentation", + "value": "table", + "description": "Select the initial display format for all user search results across the system. This setting establishes the global baseline for result presentation.", + "type": "String", + "options": [ + "table", + "cards" + ] + }, + { + "key": "data_aggreement", + "title": "Data Aggreement by download", + "value": "none", + "description": "Set up if user need to aggree the data aggreements before download and where the aggreement is writen.", + "type": "String", + "options": [ + "none", + "data policy", + "terms and conditions" + ] + }, { "key": "citationSettings", "title": "settings for citations", "options": [], "type": "JSON", "value": { - "publisher": "BEXIS2 DevOps Team", - "instance": "BEXIS2 - DevOps Test", "numberOfAuthors": 0, "showCitation": true, "defaultViewCitationFormat": "Text", - "viewCitationFormats": ["Text", "APA"], + "viewCitationFormats": [ "Text", "APA" ], "downloadCitationFormats": [ "Text", "APA", "RIS", "BibTex" ] - }, - "description": "..." + } + }, + { + "key": "curatorsGroupName", + "title": "Group name for curators", + "value": "curator", + "type": "String", + "description": "Group name all curators assigned to. This is needed for the curation tool to know who is curator." + }, + { + "key": "curation", + "title": "Configuration Settings for Curation", + "value": { + "curationLabels": [ + { + "name": "custom-label-1", + "color": "#ff0000" + } + ] + }, + "type": "JSON", + "description": "Customize the labels that can be used in the curation. \nExample: \"curationLabels\": [ { \"name\": \"custom-label-1\", \"color\": \"#ff0000\" }, ... ]" } ] diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Helpers/CitationsHelper.cs b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Helpers/CitationsHelper.cs index 199cab9b8a..23261703d9 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Helpers/CitationsHelper.cs +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Helpers/CitationsHelper.cs @@ -1,37 +1,38 @@ using BExIS.Dim.Helpers.Mappings; +using BExIS.Dim.Services; using BExIS.Dim.Services.Mappings; using BExIS.Dlm.Entities.Data; +using BExIS.Dlm.Services.Data; using BExIS.Modules.Ddm.UI.Models; +using BExIS.Security.Services.Authorization; +using BExIS.Security.Services.Objects; +using NameParser; using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Text; using System.Web; -using System.Xml.Serialization; using System.Xml; -using BExIS.Dlm.Services.Data; -using BExIS.Dim.Services; -using System.Text; -using BExIS.Security.Entities.Versions; -using System.Data; -using System.Security.Policy; -using Telerik.Web.Mvc.Infrastructure; -using System.ComponentModel.DataAnnotations; +using System.Xml.Serialization; +using Vaiona.Web.Mvc.Modularity; namespace BExIS.Modules.Ddm.UI.Helpers { public class CitationsHelper { - public static CitationDataModel GetCitationDataModel(long datasetVersionId) + public static CitationDataModel CreateCitationDataModel(DatasetVersion datasetVersion, CitationFormat format = 0) { try { using (var datasetManager = new DatasetManager()) using (var conceptManager = new ConceptManager()) + using (var entityPermissionManager = new EntityPermissionManager()) + using (var entityManager = new EntityManager()) { - var datasetVersion = datasetManager.GetDatasetVersion(datasetVersionId); var metadata = datasetVersion.Metadata; - var concept = conceptManager.MappingConceptRepository.Query(c => c.Name.ToLower() == "citation").FirstOrDefault(); + var concept = conceptManager.MappingConceptRepository.Query(c => c.Name.ToLower() == "citation_" + format.ToString().ToLower()).FirstOrDefault(); if (concept == null) return null; @@ -45,7 +46,13 @@ public static CitationDataModel GetCitationDataModel(long datasetVersionId) model = (CitationDataModel)serializer.Deserialize(reader); } + if (model == null) + return null; + var settingsHelper = new SettingsHelper(); + + var moduleSettings = ModuleManager.GetModuleSettings("Ddm"); + var useTags = Convert.ToBoolean(moduleSettings.GetValueByKey("use_tags")); var citationSettings = settingsHelper.GetCitationSettings(); // Authors @@ -54,45 +61,60 @@ public static CitationDataModel GetCitationDataModel(long datasetVersionId) model.Authors = new List() { model.Authors.Take(citationSettings.NumberOfAuthors) + " et al." }; } - // Version - if (String.IsNullOrEmpty(model.Version)) - model.Version = datasetVersion.VersionNo.ToString(); - - // Publication Year - if (String.IsNullOrEmpty(model.Date)) + //create authorname in the correct format + List authors = new List(); + foreach (string author in model.Authors) { - if (String.IsNullOrEmpty(datasetVersion.PublicAccessDate.ToString())) - model.Date = datasetVersion.PublicAccessDate.ToString(); + HumanName name = new HumanName(author); + if (String.IsNullOrEmpty(name.Middle)) + authors.Add(name.Last + ", " + name.First); else - model.Date = datasetVersion.Timestamp.ToString(); + authors.Add(name.Last + ", " + name.Middle + " " + name.First); } - // Publisher - if (citationSettings != null && !String.IsNullOrEmpty(citationSettings.Publisher)) + model.Authors = authors; + + // Version + if (model.Version == null || string.IsNullOrEmpty(model.Version)) { - model.Publisher = citationSettings.Publisher; + if (useTags && datasetVersion.Tag != null) + model.Version = datasetVersion.Tag.ToString(); + + model.Version = datasetManager.GetDatasetVersionNr(datasetVersion.Id).ToString(); } - else + + var isPublic = entityPermissionManager.IsPublicAsync(datasetVersion.Dataset.EntityTemplate.EntityType.Id, datasetVersion.Dataset.Id).Result; + + // Entity Type + if (String.IsNullOrEmpty(model.EntityName)) { - model.Publisher = "BEXIS2"; + model.EntityName = datasetVersion.Dataset.EntityTemplate.EntityType.Name; + } + // Publication Year + if (model.Year == null || String.IsNullOrEmpty(model.Year)) + { + if (isPublic) + model.Year = datasetVersion.PublicAccessDate.Date.ToString("yyyy"); + + model.Year = datasetVersion.Timestamp.Date.ToString("yyyy"); } // URL if (String.IsNullOrEmpty(model.URL)) { - using (var publicationManager = new PublicationManager()) + model.URL = HttpContext.Current.Request.Url.Host; + } + + using (var publicationManager = new PublicationManager()) + { + var pub = publicationManager.GetPublication(datasetVersion.Dataset.Id); + if (pub != null) { - var pub = publicationManager.GetPublication(datasetVersion.Dataset.Id); - if (pub != null) - { - if (!String.IsNullOrEmpty(pub.Doi)) - model.URL = pub.Doi; - } - else - { - model.URL = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority) + "/ddm/dataset/" + datasetVersion.Dataset.Id; - } + if (!String.IsNullOrEmpty(pub.Doi)) + model.DOI = pub.Doi; + //else if (!String.IsNullOrEmpty(pub.ExternalLink)) + // model.URL = pub.ExternalLink; } } @@ -117,15 +139,22 @@ public static bool IsCitationDataModelValid(CitationDataModel model) { return false; } - } - public static string DownloadCitationString(CitationDataModel model, DownloadCitationFormat format) + public static string GetCitationString(CitationDataModel model, CitationFormat format, bool isPublic, long entityId, bool useTags) { try { switch (format) { + case CitationFormat.Bibtex: + return GenerateBibtex(entityId, model, isPublic, useTags); + case CitationFormat.RIS: + return GenerateRis(entityId, model, isPublic, useTags); + case CitationFormat.Text: + return GenerateText(entityId, model, isPublic, useTags); + case CitationFormat.APA: + return GetApaFromCitationDataModel(model); default: return ""; } @@ -137,14 +166,14 @@ public static string DownloadCitationString(CitationDataModel model, DownloadCit } } - public static string DownloadApaFromCitationDataModel(CitationDataModel model) + public static string GetApaFromCitationDataModel(CitationDataModel model) { if (model == null) { return string.Empty; } var citation = new StringBuilder(); - citation.AppendLine($"{string.Join(", ", model.Authors)} ({model.Date}). {model.Title}. Version {model.Version}."); + citation.AppendLine($"{string.Join(", ", model.Authors)} ({model.Year}). {model.Title}. Version {model.Version}."); if (!string.IsNullOrEmpty(model.DOI)) { citation.AppendLine($"https://doi.org/{model.DOI}"); @@ -155,86 +184,150 @@ public static string DownloadApaFromCitationDataModel(CitationDataModel model) } return citation.ToString(); } - public static string DownloadBibTexFromCitationDataModel(CitationDataModel model) + + public static string GenerateBibtex(long entityId,CitationDataModel model, bool isPublic, bool useTags) { if (model == null) { return string.Empty; } - var bibTex = new StringBuilder(); - bibTex.AppendLine("@misc{"); - bibTex.AppendLine($" title = {{{model.Title}}},"); - bibTex.AppendLine($" version = {{{model.Version}}},"); - bibTex.AppendLine($" date = {{{model.Date}}},"); - bibTex.AppendLine($" doi = {{{model.DOI}}},"); - if (model.Authors != null && model.Authors.Count > 0) + + string bibtex = "@misc{" + model.Keyword + "\n"; + bibtex += "title ={" + model.Title + "},\n"; + bibtex += "author ={"; + var lastAuthor = model.Authors.Last(); + foreach (string author in model.Authors) { - bibTex.AppendLine(" author = {"); - for (int i = 0; i < model.Authors.Count; i++) - { - bibTex.Append($" {model.Authors[i]}"); - if (i < model.Authors.Count - 1) - { - bibTex.Append(","); - } - bibTex.AppendLine(); - } - bibTex.AppendLine(" },"); + if (author == lastAuthor) + bibtex += author + ""; + else + bibtex += author + " and "; } - if (model.Projects != null && model.Projects.Count > 0) + bibtex += "},\n"; + + bibtex += "version ={" + model.Version + "},\n"; + + bibtex += "year ={" + model.Year + "},\n"; + bibtex += "publisher ={" + model.Publisher + "},\n"; + + string url = model.URL; + if (isPublic) { - bibTex.AppendLine(" projects = {"); - for (int i = 0; i < model.Projects.Count; i++) - { - bibTex.Append($" {model.Projects[i]}"); - if (i < model.Projects.Count - 1) - { - bibTex.Append(","); - } - bibTex.AppendLine(); - } - bibTex.AppendLine(" },"); + if (useTags) + url += "/ddm/data/Showdata/" + entityId + "?tag=" + model.Version + ""; + else + url += "/ddm/data/Showdata/" + entityId + "?version=" + model.Version + ""; + + bibtex += "url ={" + url + "},\n"; + } + else + bibtex += "url ={" + url + "},\n"; + + if (!String.IsNullOrEmpty(model.DOI)) + bibtex += "doi ={" + model.DOI + "},\n"; + + if (!String.IsNullOrEmpty(model.EntityName)) + { + if (isPublic) + bibtex += "type ={" + model.EntityName + ". Published.},\n"; + else + bibtex += "type ={" + model.EntityName + ". Unpublished.},\n"; } - bibTex.Append("}"); - return bibTex.ToString(); + if(String.IsNullOrEmpty(model.Note)) + bibtex += "note ={" + model.EntityName + " ID: " + entityId + "},\n"; + else + bibtex += "note ={"+ model.Note + "},\n"; + + bibtex += "}"; + + return bibtex; } - public static string DownloadRISFromCitationDataModel(CitationDataModel model) + public static string GenerateRis(long entityId, CitationDataModel model, bool isPublic, bool useTags) { if (model == null) { return string.Empty; } - var ris = new StringBuilder(); - ris.AppendLine("TY - GEN"); - ris.AppendLine($"TI - {model.Title}"); - ris.AppendLine($"VL - {model.Version}"); - ris.AppendLine($"PY - {model.Date}"); - if (!string.IsNullOrEmpty(model.DOI)) + string ris = "TY - " + model.EntryType + " \n"; + ris += "T1 - " + model.Title + "\n"; + + foreach (string author in model.Authors) { - ris.AppendLine($"DO - {model.DOI}"); + ris += "AU - " + author + "\n"; } - if (model.Authors != null && model.Authors.Count > 0) + ris += "PY - " + model.Year + " \n"; + ris += "ET - " + model.Version + " \n"; + ris += "PB - " + model.Publisher + " \n"; + + if (!String.IsNullOrEmpty(model.DOI)) + ris += "DO - " + model.DOI + "\n"; + + string url = model.URL; + if (isPublic) { - foreach (var author in model.Authors) - { - ris.AppendLine($"AU - {author}"); - } + if (useTags) + url += "/ddm/data/Showdata/" + entityId + "?tag=" + model.Version + ""; + else + url += "/ddm/data/Showdata/" + entityId + "?version=" + model.Version + ""; + + ris += "UR - " + url + "\n"; } - if (model.Projects != null && model.Projects.Count > 0) + else + ris += "UR - " + url + "\n"; + + if (isPublic) + ris += "N1 - " + model.EntityName +" ID: " + entityId + ", Published. \n"; + else + ris += "N1 - " + model.EntityName + " ID: " + entityId + ", Unpublished. \n"; + ris += "ER -"; + + return ris; + } + + public static string GenerateText(long entityId,CitationDataModel model, bool isPublic, bool useTags) + { + string text = ""; + var lastAuthor = model.Authors.Last(); + foreach (string author in model.Authors) { - foreach (var project in model.Projects) + if (author.Equals(lastAuthor)) + text += author; + else + text += author + "; "; + } + text += " (" + model.Year + "): "; + text += model.Title + ". "; + text += "Version " + model.Version + ". "; + text += model.Publisher + ". "; + text += model.EntityName + ". "; + + if (!String.IsNullOrEmpty(model.DOI)) + { + text += model.DOI; + } + else + { + string url = model.URL; + if (isPublic) { - ris.AppendLine($"PB - {project}"); + if (useTags) + url += "/ddm/data/Showdata/" + entityId + "?tag=" + model.Version + ""; + else + url += "/ddm/data/Showdata/" + entityId + "?version=" + model.Version + ""; + + text += url + ". "; } + else + text += url + ". "; + + if(String.IsNullOrEmpty(model.Note)) + text += model.EntityName + " ID= " + entityId; + else + text += model.Note; } - ris.AppendLine("ER - "); - return ris.ToString(); - } - public static string GetTextFromCitationDataModel(CitationDataModel model) - { - return $"{model.Title}. "; + return text; } } } \ No newline at end of file diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Helpers/DdmSeedDataGenerator.cs b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Helpers/DdmSeedDataGenerator.cs index 6458242a7a..0128eb1b5b 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Helpers/DdmSeedDataGenerator.cs +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Helpers/DdmSeedDataGenerator.cs @@ -1,12 +1,15 @@ using BExIS.Dim.Entities.Mappings; using BExIS.Dim.Services.Mappings; +using BExIS.Modules.Ddm.UI.Models; using BExIS.Security.Entities.Authorization; using BExIS.Security.Entities.Objects; using BExIS.Security.Services.Authorization; using BExIS.Security.Services.Objects; +using System; using System.Collections.Generic; using System.Linq; using Vaiona.Web.Mvc.Modularity; +using static System.Net.Mime.MediaTypeNames; namespace BExIS.Modules.Ddm.UI.Helpers { @@ -162,6 +165,26 @@ public void GenerateSeedData() #endregion Requests + #region Curation + + Feature curationFeature = featureManager.FeatureRepository.Get().FirstOrDefault(f => f.Name.Equals("Curation") && f.Parent.Equals(DataDiscovery)); + if (curationFeature == null) curationFeature = featureManager.Create("Curation", "Curation", DataDiscovery); + + operationManager.Create("DDM", "Curation", "*", curationFeature); + + #endregion + + #region Metadata Diff Tool + + Feature MetadiffFeature = featureManager.FeatureRepository.Get().FirstOrDefault(f => f.Name.Equals("Metadiff") && f.Parent.Equals(DataDiscovery)); + if (MetadiffFeature == null) MetadiffFeature = featureManager.Create("Metadiff", "Metadiff", DataDiscovery); + + operationManager.Create("DDM", "Metadiff", "*", MetadiffFeature); + + #endregion + + + #endregion SECURITY } @@ -180,50 +203,84 @@ private void createCitationMappingConcept() { using (var conceptManager = new ConceptManager()) { - // check if concept exist - var concept = conceptManager.MappingConceptRepository.Query(c => c.Name.Equals("Citation")).FirstOrDefault(); - - var keys = new List(); - if (concept == null) //if not create - concept = conceptManager.CreateMappingConcept("Citation", "The concept is needed to create a citation string", "", ""); - else // if exist load available keys + /* + APA, + RIS, + Text, + Bibtex*/ + foreach (CitationFormat value in Enum.GetValues(typeof(CitationFormat))) { - keys = conceptManager.MappingKeyRepo.Query(k => k.Concept.Id.Equals(concept.Id)).ToList(); + var keys = new List(); + // check if concept exist + var concept = conceptManager.MappingConceptRepository.Query(c => c.Name.Equals("Citation_" + value)).FirstOrDefault(); + if (concept == null) //if not create + concept = conceptManager.CreateMappingConcept("Citation_"+ value, "The concept is needed to create a " + value + " citation string", "", ""); + else // if exist load available keys + { + keys = conceptManager.MappingKeyRepo.Query(k => k.Concept.Id.Equals(concept.Id)).ToList(); + } + + //title + if (!keys.Any(k => k.Name.Equals("data/title"))) + conceptManager.CreateMappingKey("Title", "", "", false, false, "data/title", concept); + //version + if (!keys.Any(k => k.Name.Equals("data/version"))) + conceptManager.CreateMappingKey("Version", "", "", true, false, "data/version", concept); + //year + if (!keys.Any(k => k.Name.Equals("data/year"))) + conceptManager.CreateMappingKey("Year", "", "", true, false, "data/year", concept); + + //entityType + if (!keys.Any(k => k.Name.Equals("data/entityType"))) + conceptManager.CreateMappingKey("EntityType", "", "", true, false, "data/entityType", concept); + + //entryType + if (value == CitationFormat.Text) + { + if (!keys.Any(k => k.Name.Equals("data/entryType"))) + conceptManager.CreateMappingKey("EntryType", "", "", true, false, "data/entryType", concept); + } + else + { + if (!keys.Any(k => k.Name.Equals("data/entryType"))) + conceptManager.CreateMappingKey("EntryType", "", "", false, false, "data/entryType", concept); + } + + //publisher + if (!keys.Any(k => k.Name.Equals("data/publisher"))) + conceptManager.CreateMappingKey("Publisher", "", "", false, false, "data/publisher", concept); + + if (value == CitationFormat.Bibtex) + { + if (!keys.Any(k => k.Name.Equals("data/keyword"))) + conceptManager.CreateMappingKey("Keyword", "", "", false, false, "data/keyword", concept); + } + + //note + if (!keys.Any(k => k.Name.Equals("data/note"))) + conceptManager.CreateMappingKey("Note", "", "", true, false, "data/note", concept); + + //doi + if (!keys.Any(k => k.Name.Equals("data/doi"))) + conceptManager.CreateMappingKey("DOI", "", "", true, false, "data/doi", concept); + + //projects + MappingKey projects = null; + if (!keys.Any(k => k.Name.Equals("data/projects"))) + projects = conceptManager.CreateMappingKey("Projects", "", "", true, true, "data/projects", concept); + + if (!keys.Any(k => k.Name.Equals("data/projects/project"))) + conceptManager.CreateMappingKey("Project", "", "", true, false, "data/projects/project", concept, projects); + + //authors + MappingKey authors = null; + if (!keys.Any(k => k.Name.Equals("data/authorNames"))) + authors = conceptManager.CreateMappingKey("AuthorNames", "", "", false, true, "data/authorNames", concept); + + if (!keys.Any(k => k.Name.Equals("data/authorNames/authorname"))) + conceptManager.CreateMappingKey("AuthorName", "", "", false, false, "data/authorNames/authorName", concept, authors); } - - //title - if (!keys.Any(k => k.Name.Equals("data/title"))) - conceptManager.CreateMappingKey("Title", "", "", false, false, "data/title", concept); - //version - if (!keys.Any(k => k.Name.Equals("data/version"))) - conceptManager.CreateMappingKey("Version", "", "", false, false, "data/version", concept); - //year - if (!keys.Any(k => k.Name.Equals("data/year"))) - conceptManager.CreateMappingKey("Year", "", "", false, false, "data/year", concept); - //entityType - if (!keys.Any(k => k.Name.Equals("data/entityType"))) - conceptManager.CreateMappingKey("EntityType", "", "", false, false, "data/entityType", concept); - - //doi - if (!keys.Any(k => k.Name.Equals("data/doi"))) - conceptManager.CreateMappingKey("Doi", "", "", true, false, "data/doi", concept); - - //projects - MappingKey projects = null; - if (!keys.Any(k => k.Name.Equals("data/projects"))) - projects = conceptManager.CreateMappingKey("Projects", "", "", true, false, "data/projects", concept); - - if (!keys.Any(k => k.Name.Equals("data/projects/project"))) - conceptManager.CreateMappingKey("Project", "", "", true, false, "data/projects/project", concept, projects); - - //authors - MappingKey authors = null; - if (!keys.Any(k => k.Name.Equals("data/authorNames"))) - authors = conceptManager.CreateMappingKey("AuthorNames", "", "", false, true, "data/authorNames", concept); - - if (!keys.Any(k => k.Name.Equals("data/authorNames/authorName"))) - conceptManager.CreateMappingKey("AuthorName", "", "", false, false, "data/authorNames/authorName", concept, authors); } } } diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Helpers/SettingsHelper.cs b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Helpers/SettingsHelper.cs index cfbecb930a..2459502feb 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Helpers/SettingsHelper.cs +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Helpers/SettingsHelper.cs @@ -27,18 +27,30 @@ public bool KeyExist(string key) return element != null ? true : false; } - public string GetValue(string key) - { - XDocument settings = XDocument.Load(filePath); - XElement element = XmlUtility.GetXElementByAttribute("entry", "key", key.ToLower(), settings); + //public string GetValue(string key) + //{ + // XDocument settings = XDocument.Load(filePath); + // XElement element = XmlUtility.GetXElementByAttribute("entry", "key", key.ToLower(), settings); - string value = ""; - if (element != null) + // string value = ""; + // if (element != null) + // { + // value = element.Attribute("value")?.Value; + // } + + // return value; + //} + + public T GetValue(string key) where T : class + { + try { - value = element.Attribute("value")?.Value; + return _settings.GetValueByKey(key); + } + catch (Exception ex) + { + return null; } - - return value; } public CitationSettings GetCitationSettings() diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Models/CitationModels.cs b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Models/CitationModels.cs index 38341d4033..04e0f1b3f3 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Models/CitationModels.cs +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Models/CitationModels.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using BExIS.App.Bootstrap.Attributes; +using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Xml.Serialization; @@ -21,7 +23,7 @@ public enum ReadCitationFormat Text } - public enum DownloadCitationFormat + public enum CitationFormat { APA, RIS, @@ -41,13 +43,14 @@ public class CitationDataModel [XmlElement("version")] public string Version { get; set; } - [XmlElement("projects")] + [XmlArray("projects")] + [XmlArrayItem("project")] public List Projects { get; set; } - [XmlElement("date")] - [Required(ErrorMessage = "Date is required")] - [MinLength(1, ErrorMessage = "Date cannot be empty")] - public string Date { get; set; } + [XmlElement("year")] + [Required(ErrorMessage = "Year is required")] + [MinLength(1, ErrorMessage = "Year cannot be empty")] + public string Year { get; set; } [XmlElement("doi")] public string DOI { get; set; } @@ -59,19 +62,31 @@ public class CitationDataModel [XmlArray("authorNames")] [XmlArrayItem("authorName")] - //[MinCapacity(1)] + [MinCapacity(1), NoNullOrEmptyItems] public List Authors { get; set; } - [XmlElement("entityType")] - [Required(ErrorMessage = "Entity Type is required")] - [MinLength(1, ErrorMessage = "Entity Type cannot be empty")] - public string EntityType { get; set; } + //[XmlElement("entityType")] + //[Required(ErrorMessage = "Entity Type is required")] + //[MinLength(1, ErrorMessage = "Entity Type cannot be empty")] + //public string EntityType { get; set; } + + [XmlElement("entryType")] + public string EntryType { get; set; } + + [XmlElement("entityName")] + public string EntityName { get; set; } [XmlElement("publisher")] - [Required(ErrorMessage = "Title is required")] - [MinLength(1, ErrorMessage = "Title cannot be empty")] + [Required(ErrorMessage = "Publisher is required")] + [MinLength(1, ErrorMessage = "Publisher cannot be empty")] public string Publisher { get; set; } + [XmlElement("keyword")] + public string Keyword { get; set; } + + [XmlElement("note")] + public string Note { get; set; } + public CitationDataModel() { Authors = new List(); @@ -93,7 +108,6 @@ public DatasetCitationEntry() public string URL { get; set; } public string Publisher { get; set; } - public string InstanceName { get; set; } public string Year { get; set; } public List Authors { get; set; } public string Title { get; set; } @@ -118,7 +132,7 @@ public class CitationSettings public int NumberOfAuthors { get; set; } public ReadCitationFormat ReadCitationFormat { get; set; } - public List DownloadCitationFormats { get; set; } + public List DownloadCitationFormats { get; set; } public CitationSettings() { @@ -127,7 +141,7 @@ public CitationSettings() ShowCitation = false; NumberOfAuthors = 1; ReadCitationFormat = ReadCitationFormat.Text; - DownloadCitationFormats = new List() { }; + DownloadCitationFormats = new List() { }; } } diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Curation/Index.cshtml b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Curation/Index.cshtml new file mode 100644 index 0000000000..e45d172e83 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Curation/Index.cshtml @@ -0,0 +1,23 @@ + +@{ + ViewBag.Title = "curation"; + Layout = "~/Themes/Default/Layouts/_svelteLayout.cshtml"; + + long id = 0; + bool use_minor = false; + + if (ViewData["id"] != null) + { + id = (long)ViewData["id"]; + } + + //if (ViewData["use_minor"] != null) + //{ + // use_minor = (Boolean)ViewData["use_minor"]; + //} + +} + +
    + @Html.Partial("_sveltePage") +
    \ No newline at end of file diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/ShowData.cshtml b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/ShowData.cshtml index 0b0022f6ca..65710c849b 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/ShowData.cshtml +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/ShowData.cshtml @@ -10,6 +10,10 @@ string bioSchemaInfos = ""; long total = 0; bool has_data = false; + string data_aggreement = "none"; + bool aggrreement_checked = true; + bool withFilter = false; + bool withUnits = false; if (ViewData["TabIndex"] != null) { @@ -41,6 +45,14 @@ has_data = (bool)ViewData["has_data"]; } + if (ViewData["data_aggreement"] != null && String.IsNullOrEmpty(User.Identity.Name)) + { + data_aggreement = (string)ViewData["data_aggreement"]; + if (data_aggreement != "none") { + aggrreement_checked = false; + } + } + var show_tabs = new Dictionary(); bool check_public_metadata = false; @@ -231,99 +243,135 @@
    - @{Html.RenderAction("GetCitationOrTitle", "Data", new { datasetVersionId = Model.VersionId });} + @{Html.RenderAction("GetCitationOrTitle", "Data", new { Id = Model.Id, datasetVersionId = Model.VersionId });}
    -
    +
    @if (Model.DownloadAccess) { + /* data aggreement state: none, data policy, terms and conditions**/ - if (Model.DataStructureType.ToLower().Equals("structured")) + if (data_aggreement.Equals("data policy")) { - - - - +
    + @Html.CheckBox("data_policy", false, new { @id = "data-policy" }) + + I accept the public regulations from the + privacy policy. + +
    } - else + else if (data_aggreement.Equals("terms and conditions")) { - Download Dataset +
    + @Html.CheckBox("terms_and_conditions", false, new { @id = "terms-and-conditions" }) + + I accept the public regulations from the + terms and conditions. + +
    } - } - else - { - if (Model.HasRequestRight) + + if (Model.DataStructureType.ToLower().Equals("structured")) { - if (Model.RequestAble) - { +
    + + @Html.CheckBox("WithFilter", false, new { text = "download filtered data", @id = "withFilter" }) + use filter + + + + @Html.CheckBox("WithUnits", false, new { text = "download data with units", @id = "withUnits" }) + add units + +
    + + + + + + + } + else + { + Download Dataset + } + } + else + { + + + if (Model.HasRequestRight) + { + if (Model.RequestAble) + { if (Model.RequestExist) { - + } - else if(!has_data) + else if (!has_data) { - - + } else { -
    - @Html.TextArea("intention", new { @class = "bx-input", placeholder = "describe your intention " }); -
    - +
    + @Html.TextArea("intention", new { @class = "bx-input", placeholder = "describe your intention " }); +
    + } - } - else - { + } + else + { if (show_tabs["show_tabs_deactivated"] == "true") { - + } - } - } - else - { - if (show_tabs["show_tabs_deactivated"] == "true") - { + } + } + else + { + if (show_tabs["show_tabs_deactivated"] == "true") + { - } - } - } + } + } + } - @if (Model.HasEditRight && ViewData["state"].ToString() != "hidden") - { + @if (Model.HasEditRight && ViewData["state"].ToString() != "hidden") + { - + - } -
    + } +
    @@ -444,7 +492,7 @@
    }
    - + } else if (Model.IsPublic == false) @@ -672,14 +777,17 @@ else float: right; } + .tab-pane { min-height: 70px; } - .flex{ + .flex { display: flex; flex-wrap: wrap; justify-content: space-between; + gap: 5px; + align-content: center; } .grow { @@ -698,8 +806,17 @@ else padding-top: 5px; } + + .position-releative { + position: relative; + } + #identifier-container { padding-left:2px; } + .data-aggreement { + width:300px; + } + \ No newline at end of file diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/_Citation_Text.cshtml b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/_Citation_Text.cshtml index 55a5ababb7..8a98b8318d 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/_Citation_Text.cshtml +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/_Citation_Text.cshtml @@ -1,5 +1,41 @@ @model BExIS.Modules.Ddm.UI.Models.CitationDataModel -
    @String.Join("; ", Model.Authors) (@(Model.Date)): @(Model.Title). Version @(Model.Version). @(Model.Publisher). @(Model.EntityType). @(Model.URL)
    +
    Citation:
    @String.Join("; ", Model.Authors) (@(Model.Year)): @(Model.Title). Version @(Model.Version). @(Model.Publisher). @(Model.EntityName). @(Model.URL)
    + +
    -@*{Nachname Ersteller}, {Initialen Ersteller}. ({Jahr}). {Titel des Datensatzes} [Datensatz]. {Herausgeber/Organisation}. Abgerufen am {Datum des Zugriffs}, von https://doi.org/{DOI} oder {URL}*@ \ No newline at end of file +@*{Nachname Ersteller}, {Initialen Ersteller}. ({Jahr}). {Titel des Datensatzes} [Datensatz]. {Herausgeber/Organisation}. Abgerufen am {Datum des Zugriffs}, von https://doi.org/{DOI} oder {URL}*@ + + + + \ No newline at end of file diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/_labelsView.cshtml b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/_labelsView.cshtml index ab26de4122..8e6fe1bb2d 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/_labelsView.cshtml +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/_labelsView.cshtml @@ -3,16 +3,22 @@
    @foreach (var label in Model) { - if (@label.Value == "template") + + if (label.Value == "template") { @label.Key } - else + else if(label.Value.ToLower() == "doi") { - @label.Value: @label.Key - } - + string displayName = label.Key; + if (label.Key.StartsWith("http")) + { + string[] parts = label.Key.Split('/'); + displayName = string.Join("/", parts.Skip(parts.Length - 2)); + } + @label.Value: @displayName + } }
    @@ -20,4 +26,8 @@ .publication-container { padding-bottom:10px; } + .label { + font-size: 100%; + margin-right: 5px; + } diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/_structuredDataView.cshtml b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/_structuredDataView.cshtml index 372a96e06e..597df0c373 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/_structuredDataView.cshtml +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/_structuredDataView.cshtml @@ -36,6 +36,12 @@ tableClass = "scroll_in_screen"; } + bool isPublic = false; + + if(ViewData["isPublic"]!=null) + { + isPublic = (bool)ViewData["isPublic"]; + } } @section Scripts{ @@ -58,64 +64,68 @@ }
    - @if (Model.DownloadAccess && Model.Data != null && Model.Data.Rows.Count > 0) - { - @* + @if (!isPublic || User.Identity.Name != "") /* dont sho download options if the dataset is public */ + { + if (Model.DownloadAccess && Model.Data != null && Model.Data.Rows.Count > 0) + { + + @* - *@ + *@ if (@total < 1048576) // hide Excel download for bigger datasets, which exceed the max number of rows in excel { + + + @* + *@ + + } + - - @* - *@ + + + + + @Html.CheckBox("WithUnits", false, new { text = "download data with units", @id = "WithUnits" }) + Download data with units. } + else + { - - + - - - - - @Html.CheckBox("WithUnits", false, new { text = "download data with units", @id = "WithUnits" }) - Download data with units. - } - else - { - - - } @@ -125,7 +135,7 @@
    -
    + @(Html.Telerik().Grid(Model.Data) .Name("PrimaryDataResultGrid") .DataBinding(dataBinding => dataBinding diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/_tagsView.cshtml b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/_tagsView.cshtml index 1b690a6530..a870a23f87 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/_tagsView.cshtml +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Data/_tagsView.cshtml @@ -34,6 +34,10 @@
    TagRelease NoteRelease TagRelease Note
    + + + + @foreach (var tag in Model) { @@ -44,8 +48,12 @@
    @note
    } + + + } +
    Release TagRelease NotesRelease DateShow Version
    @tag.ReleaseDate.ToString("dd.MM.yyyy")Show Version
    @@ -54,37 +62,40 @@
    - - - - - @{ - var singletag = Model.FirstOrDefault(t => t.Version == tagNr); - - - - - - - } - -
    @singletag.Version - @foreach (var note in singletag.ReleaseNotes) - { -
    @note
    - } -
    - + @{ + var singletag = Model.FirstOrDefault(t => t.Version == tagNr); + +
    Release Tag: @singletag.Version (@singletag.ReleaseDate.ToString("dd.MM.yyyy"))
    +
    +
    Release Notes:
    +
      + @if (singletag.ReleaseNotes != null && singletag.ReleaseNotes.Any()) + { + foreach (var note in singletag.ReleaseNotes) + { + +
    • @note
    • + + + } + } +
    + }
    - + @if (hasEditRigths) { - + }
    @@ -137,9 +148,17 @@ tagsview.style.display = "block"; } - const element = document.getElementById("showTags"); + const element = document.getElementById("showTags").querySelector("span.fa"); element.classList.toggle("fa-arrow-up"); element.classList.toggle("fa-arrow-down"); - + // look at text within span and change it accordingly (2nd span child of button) + const span = element.querySelector("span:nth-child(2)"); + if (span) { + if (span.innerText.trim() === "Show History") { + span.innerText = " Hide History"; + } else { + span.innerText = " Show History"; + } + } } diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Metadiff/Index.cshtml b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Metadiff/Index.cshtml new file mode 100644 index 0000000000..cfb8cb2019 --- /dev/null +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Metadiff/Index.cshtml @@ -0,0 +1,11 @@ + +@{ + ViewBag.Title = "Metadata Diff Tool"; + Layout = "~/Themes/Default/Layouts/_svelteLayout.cshtml"; + + +} + +
    + @Html.Partial("_sveltePage") +
    \ No newline at end of file diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/PublicSearch/Index.cshtml b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/PublicSearch/Index.cshtml index 38fa536a2d..1445c7de2e 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/PublicSearch/Index.cshtml +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/PublicSearch/Index.cshtml @@ -2,15 +2,15 @@ ViewBag.Title = "Public search"; Layout = "~/Themes/Default/Layouts/_svelteLayout.cshtml"; - bool isTemplateRequired = false; + string search_result_presentation = ""; - if (ViewData["isTemplateRequired"] != null) + if (ViewData["search_result_presentation"] != null) { - isTemplateRequired = (bool)ViewData["isTemplateRequired"]; + search_result_presentation = ViewData["search_result_presentation"].ToString(); } } -
    +
    @Html.Partial("_sveltePage")
    \ No newline at end of file diff --git a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Search/Index.cshtml b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Search/Index.cshtml index 2ab3bb7832..e88255d76a 100644 --- a/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Search/Index.cshtml +++ b/Console/BExIS.Web.Shell/Areas/DDM/BExIS.Modules.Ddm.UI/Views/Search/Index.cshtml @@ -2,15 +2,15 @@ ViewBag.Title = "Search"; Layout = "~/Themes/Default/Layouts/_svelteLayout.cshtml"; - bool isTemplateRequired = false; + string search_result_presentation = ""; - if (ViewData["isTemplateRequired"] != null) + if (ViewData["search_result_presentation"] != null) { - isTemplateRequired = (bool)ViewData["isTemplateRequired"]; + search_result_presentation = ViewData["search_result_presentation"].ToString(); } } -