-
Notifications
You must be signed in to change notification settings - Fork 70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(subform): backend handling of datatype calls from preview #13971
Open
Jondyr
wants to merge
8
commits into
main
Choose a base branch
from
13942-backend-endpoint-for-preview-datamodel-json-schema-fails-for-subforms
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+571
−319
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
0e047cc
Organize preview endpoints for data and instance
Jondyr dd4dc68
Add dataservice
Jondyr c6afec5
Implement distributedCache handling for datatype object CRU
Jondyr 858e0bf
Formatting and removing unnecessary code
Jondyr f381cfb
Remove unnecessary code
Jondyr f495bbf
Cleanup
Jondyr 4150fc2
Add tests for dataService
Jondyr 7792222
Remove unused imports
Jondyr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
using System; | ||
|
||
namespace Altinn.Studio.Designer.Constants | ||
{ | ||
public static class PreviewConstants | ||
{ | ||
public static readonly Guid MockInstanceGUID = new("f1e23d45-6789-1bcd-8c34-56789abcdef0"); | ||
} | ||
} |
131 changes: 131 additions & 0 deletions
131
backend/src/Designer/Controllers/Preview/DataController.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text.Json.Nodes; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using System.Web; | ||
using Altinn.Platform.Storage.Interface.Models; | ||
using Altinn.Studio.Designer.Filters; | ||
using Altinn.Studio.Designer.Helpers; | ||
using Altinn.Studio.Designer.Models; | ||
using Altinn.Studio.Designer.Models.Preview; | ||
using Altinn.Studio.Designer.Services.Implementation; | ||
using Altinn.Studio.Designer.Services.Interfaces; | ||
using Altinn.Studio.Designer.Services.Interfaces.Preview; | ||
using Microsoft.AspNetCore.Authorization; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
|
||
namespace Altinn.Studio.Designer.Controllers.Preview | ||
{ | ||
[Authorize] | ||
[AutoValidateAntiforgeryToken] | ||
[Route("{org:regex(^(?!designer))}/{app:regex(^(?!datamodels$)[[a-z]][[a-z0-9-]]{{1,28}}[[a-z0-9]]$)}/instances/{partyId}/{instanceGuid}/data")] | ||
public class DataController(IHttpContextAccessor httpContextAccessor, | ||
IPreviewService previewService, | ||
ISchemaModelService schemaModelService, | ||
IDataService dataService | ||
) : Controller | ||
{ | ||
[HttpGet("{dataGuid}")] | ||
public ActionResult Get([FromRoute] Guid dataGuid) | ||
{ | ||
JsonNode dataItem = dataService.GetDataElement(dataGuid); | ||
return Ok(dataItem); | ||
} | ||
|
||
[HttpPost] | ||
public ActionResult Post( | ||
[FromRoute] int partyId, | ||
[FromRoute] Guid instanceGuid, | ||
[FromQuery] string dataType | ||
) | ||
{ | ||
DataElement dataElement = dataService.CreateDataElement(partyId, instanceGuid, dataType); | ||
return Created("link-to-app-placeholder", dataElement); | ||
} | ||
|
||
[HttpPatch("{dataGuid}")] | ||
[UseSystemTextJson] | ||
public ActionResult<DataPatchResponse> Patch( | ||
[FromRoute] Guid dataGuid, | ||
[FromBody] DataPatchRequest dataPatch | ||
) | ||
{ | ||
JsonNode dataItem = dataService.PatchDataElement(dataGuid, dataPatch.Patch); | ||
return Ok(new DataPatchResponse() | ||
{ | ||
ValidationIssues = [], | ||
NewDataModel = dataItem, | ||
}); | ||
} | ||
|
||
[HttpDelete("{dataTypeId}")] | ||
public ActionResult DeleteAttachment([FromRoute] Guid dataGuid) | ||
{ | ||
return Ok(); | ||
} | ||
|
||
[HttpGet("{dataGuid}/validate")] | ||
public ActionResult ValidateInstanceForData([FromRoute] Guid dataGuid) | ||
{ | ||
return Ok(new List<string>()); | ||
} | ||
|
||
[HttpPost("{dataTypeId}/tags")] | ||
public ActionResult UpdateTagsForAttachment([FromBody] string tag) | ||
{ | ||
return Created("link-to-app-placeholder", tag); | ||
} | ||
|
||
[HttpGet(PreviewService.MockDataTaskId)] | ||
public async Task<ActionResult> GetDefaultFormData( | ||
[FromRoute] string org, | ||
[FromRoute] string app, | ||
[FromRoute] int partyId, | ||
CancellationToken cancellationToken | ||
) | ||
{ | ||
string developer = AuthenticationHelper.GetDeveloperUserName(httpContextAccessor.HttpContext); | ||
string refererHeader = Request.Headers.Referer; | ||
string layoutSetName = GetSelectedLayoutSetInEditorFromRefererHeader(refererHeader); | ||
DataType dataType = await previewService.GetDataTypeForLayoutSetName(org, app, developer, layoutSetName, cancellationToken); | ||
// For apps that does not have a datamodel | ||
if (dataType == null) | ||
{ | ||
Instance mockInstance = await previewService.GetMockInstance(org, app, developer, partyId, layoutSetName, cancellationToken); | ||
return Ok(mockInstance.Id); | ||
} | ||
string modelPath = $"/App/models/{dataType.Id}.schema.json"; | ||
string decodedPath = Uri.UnescapeDataString(modelPath); | ||
string formData = await schemaModelService.GetSchema(AltinnRepoEditingContext.FromOrgRepoDeveloper(org, app, developer), decodedPath, cancellationToken); | ||
return Ok(formData); | ||
} | ||
|
||
[HttpPut(PreviewService.MockDataTaskId)] | ||
public async Task<ActionResult> UpdateFormData( | ||
[FromRoute] string org, | ||
[FromRoute] string app, | ||
[FromRoute] int partyId, | ||
CancellationToken cancellationToken | ||
) | ||
{ | ||
return await GetDefaultFormData(org, app, partyId, cancellationToken); | ||
} | ||
|
||
[HttpPatch(PreviewService.MockDataTaskId)] | ||
public ActionResult PatchFormData() | ||
{ | ||
return Ok(); | ||
} | ||
|
||
private static string GetSelectedLayoutSetInEditorFromRefererHeader(string refererHeader) | ||
{ | ||
Uri refererUri = new(refererHeader); | ||
string layoutSetName = HttpUtility.ParseQueryString(refererUri.Query)["selectedLayoutSet"]; | ||
|
||
return string.IsNullOrEmpty(layoutSetName) ? null : layoutSetName; | ||
} | ||
} | ||
} | ||
|
214 changes: 214 additions & 0 deletions
214
backend/src/Designer/Controllers/Preview/InstancesController.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,214 @@ | ||||||
using System; | ||||||
using System.Collections.Generic; | ||||||
using System.Threading; | ||||||
using System.Threading.Tasks; | ||||||
using System.Web; | ||||||
using Altinn.App.Core.Internal.Process.Elements; | ||||||
using Altinn.Platform.Storage.Interface.Models; | ||||||
using Altinn.Studio.Designer.Helpers; | ||||||
using Altinn.Studio.Designer.Infrastructure.GitRepository; | ||||||
using Altinn.Studio.Designer.Services.Interfaces; | ||||||
using LibGit2Sharp; | ||||||
using Microsoft.AspNetCore.Authorization; | ||||||
using Microsoft.AspNetCore.Http; | ||||||
using Microsoft.AspNetCore.Mvc; | ||||||
|
||||||
namespace Altinn.Studio.Designer.Controllers.Preview | ||||||
{ | ||||||
[Authorize] | ||||||
[AutoValidateAntiforgeryToken] | ||||||
[Route("{org:regex(^(?!designer))}/{app:regex(^(?!datamodels$)[[a-z]][[a-z0-9-]]{{1,28}}[[a-z0-9]]$)}")] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd suggest moving
Suggested change
|
||||||
public class InstancesController(IHttpContextAccessor httpContextAccessor, | ||||||
IPreviewService previewService, | ||||||
IAltinnGitRepositoryFactory altinnGitRepositoryFactory | ||||||
) : Controller | ||||||
{ | ||||||
/// <summary> | ||||||
/// Action for creating the mocked instance object | ||||||
/// </summary> | ||||||
/// <param name="org">Unique identifier of the organisation responsible for the app.</param> | ||||||
/// <param name="app">Application identifier which is unique within an organisation.</param> | ||||||
/// <param name="instanceOwnerPartyId"></param> | ||||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that observes if operation is cancelled.</param> | ||||||
/// <returns>The mocked instance object</returns> | ||||||
[HttpPost] | ||||||
[Route("instances")] | ||||||
public async Task<ActionResult<Instance>> Instances(string org, string app, [FromQuery] int? instanceOwnerPartyId, CancellationToken cancellationToken) | ||||||
{ | ||||||
string developer = AuthenticationHelper.GetDeveloperUserName(httpContextAccessor.HttpContext); | ||||||
string refererHeader = Request.Headers["Referer"]; | ||||||
string layoutSetName = GetSelectedLayoutSetInEditorFromRefererHeader(refererHeader); | ||||||
Instance mockInstance = await previewService.GetMockInstance(org, app, developer, instanceOwnerPartyId, layoutSetName, cancellationToken); | ||||||
return Ok(mockInstance); | ||||||
} | ||||||
|
||||||
/// <summary> | ||||||
/// Action for getting a mocked response for the current task connected to the instance | ||||||
/// </summary> | ||||||
/// <returns>The processState</returns> | ||||||
[HttpGet] | ||||||
[Route("instances/{partyId}/{instanceGuId}/process")] | ||||||
public async Task<ActionResult<AppProcessState>> Process(string org, string app, [FromRoute] int partyId, CancellationToken cancellationToken) | ||||||
{ | ||||||
string developer = AuthenticationHelper.GetDeveloperUserName(httpContextAccessor.HttpContext); | ||||||
string refererHeader = Request.Headers["Referer"]; | ||||||
string layoutSetName = GetSelectedLayoutSetInEditorFromRefererHeader(refererHeader); | ||||||
Instance mockInstance = await previewService.GetMockInstance(org, app, developer, partyId, layoutSetName, cancellationToken); | ||||||
List<string> tasks = await previewService.GetTasksForAllLayoutSets(org, app, developer, cancellationToken); | ||||||
AppProcessState processState = new AppProcessState(mockInstance.Process) | ||||||
{ | ||||||
ProcessTasks = tasks != null | ||||||
? new List<AppProcessTaskTypeInfo>(tasks?.ConvertAll(task => new AppProcessTaskTypeInfo { ElementId = task, AltinnTaskType = "data" })) | ||||||
: null | ||||||
}; | ||||||
|
||||||
return Ok(processState); | ||||||
} | ||||||
|
||||||
/// <summary> | ||||||
/// Endpoint to get instance for next process step | ||||||
/// </summary> | ||||||
/// <returns>A mocked instance object</returns> | ||||||
[HttpGet] | ||||||
[Route("instances/{partyId}/{instanceGuId}")] | ||||||
public async Task<ActionResult<Instance>> InstanceForNextTask(string org, string app, [FromRoute] int partyId, CancellationToken cancellationToken) | ||||||
{ | ||||||
string developer = AuthenticationHelper.GetDeveloperUserName(httpContextAccessor.HttpContext); | ||||||
string refererHeader = Request.Headers["Referer"]; | ||||||
string layoutSetName = GetSelectedLayoutSetInEditorFromRefererHeader(refererHeader); | ||||||
Instance mockInstance = await previewService.GetMockInstance(org, app, developer, partyId, layoutSetName, cancellationToken); | ||||||
return Ok(mockInstance); | ||||||
} | ||||||
|
||||||
/// <summary> | ||||||
/// Endpoint to get active instances for apps with state/layout sets/multiple processes | ||||||
/// </summary> | ||||||
/// <returns>A list of a single mocked instance</returns> | ||||||
[HttpGet] | ||||||
[Route("instances/{partyId}/active")] | ||||||
public ActionResult<List<Instance>> ActiveInstancesForAppsWithLayoutSets(string org, string app, [FromRoute] int partyId) | ||||||
{ | ||||||
// Simulate never having any active instances | ||||||
List<Instance> activeInstances = new(); | ||||||
return Ok(activeInstances); | ||||||
} | ||||||
|
||||||
/// <summary> | ||||||
/// Endpoint to validate an instance | ||||||
/// </summary> | ||||||
/// <returns>Ok</returns> | ||||||
[HttpGet] | ||||||
[Route("instances/{partyId}/{instanceGuId}/validate")] | ||||||
public ActionResult ValidateInstance() | ||||||
{ | ||||||
return Ok(); | ||||||
} | ||||||
|
||||||
/// <summary> | ||||||
/// Action for getting a mocked response for the next task connected to the instance | ||||||
/// </summary> | ||||||
/// <returns>The processState object on the global mockInstance object</returns> | ||||||
[HttpGet] | ||||||
[Route("instances/{partyId}/{instanceGuId}/process/next")] | ||||||
public async Task<ActionResult> ProcessNext(string org, string app, [FromRoute] int partyId, CancellationToken cancellationToken) | ||||||
{ | ||||||
string developer = AuthenticationHelper.GetDeveloperUserName(httpContextAccessor.HttpContext); | ||||||
string refererHeader = Request.Headers["Referer"]; | ||||||
string layoutSetName = GetSelectedLayoutSetInEditorFromRefererHeader(refererHeader); | ||||||
Instance mockInstance = await previewService.GetMockInstance(org, app, developer, partyId, layoutSetName, cancellationToken); | ||||||
return Ok(mockInstance.Process); | ||||||
} | ||||||
|
||||||
/// <summary> | ||||||
/// Action for mocking an end to the process in order to get receipt after "send inn" is pressed | ||||||
/// </summary> | ||||||
/// <returns>Process object where ended is set</returns> | ||||||
[HttpPut] | ||||||
[Route("instances/{partyId}/{instanceGuId}/process/next")] | ||||||
public async Task<ActionResult> UpdateProcessNext(string org, string app, [FromRoute] int partyId, [FromQuery] string lang, CancellationToken cancellationToken) | ||||||
{ | ||||||
string refererHeader = Request.Headers["Referer"]; | ||||||
string layoutSetName = GetSelectedLayoutSetInEditorFromRefererHeader(refererHeader); | ||||||
if (string.IsNullOrEmpty(layoutSetName)) | ||||||
{ | ||||||
string endProcess = """{"ended": "ended"}"""; | ||||||
return Ok(endProcess); | ||||||
} | ||||||
string developer = AuthenticationHelper.GetDeveloperUserName(httpContextAccessor.HttpContext); | ||||||
Instance mockInstance = await previewService.GetMockInstance(org, app, developer, partyId, layoutSetName, cancellationToken); | ||||||
return Ok(mockInstance.Process); | ||||||
} | ||||||
|
||||||
/// <summary> | ||||||
/// Action for getting options list for a given options list id for a given instance | ||||||
/// </summary> | ||||||
/// <param name="org">Unique identifier of the organisation responsible for the app.</param> | ||||||
/// <param name="app">Application identifier which is unique within an organisation.</param> | ||||||
/// <param name="optionListId">The id of the options list</param> | ||||||
/// <param name="language">The language for the options list</param> | ||||||
/// <param name="source">The source of the options list</param> | ||||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that observes if operation is cancelled.</param> | ||||||
/// <returns>The options list if it exists, otherwise nothing</returns> | ||||||
[HttpGet] | ||||||
[Route("instances/{partyId}/{instanceGuid}/options/{optionListId}")] | ||||||
public async Task<ActionResult<string>> GetOptionsForInstance(string org, string app, string optionListId, [FromQuery] string language, [FromQuery] string source, CancellationToken cancellationToken) | ||||||
{ | ||||||
try | ||||||
{ | ||||||
// TODO: Need code to get dynamic options list based on language and source? | ||||||
string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext); | ||||||
AltinnAppGitRepository altinnAppGitRepository = altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer); | ||||||
string options = await altinnAppGitRepository.GetOptionsList(optionListId, cancellationToken); | ||||||
return Ok(options); | ||||||
} | ||||||
catch (NotFoundException) | ||||||
{ | ||||||
// Return empty list since app-frontend don't handle a null result | ||||||
return Ok(new List<string>()); | ||||||
} | ||||||
} | ||||||
|
||||||
/// <summary> | ||||||
/// Action for getting data list for a given data list id for a given instance | ||||||
/// </summary> | ||||||
/// <param name="org">Unique identifier of the organisation responsible for the app.</param> | ||||||
/// <param name="app">Application identifier which is unique within an organisation.</param> | ||||||
/// <param name="dataListId">The id of the data list</param> | ||||||
/// <param name="language">The language for the data list</param> | ||||||
/// <param name="size">The number of items to return</param> | ||||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that observes if operation is cancelled.</param> | ||||||
/// <returns>The options list if it exists, otherwise nothing</returns> | ||||||
[HttpGet] | ||||||
[Route("instances/{partyId}/{instanceGuid}/datalists/{dataListId}")] | ||||||
public ActionResult<List<string>> GetDataListsForInstance(string org, string app, string dataListId, [FromQuery] string language, [FromQuery] string size, CancellationToken cancellationToken) | ||||||
{ | ||||||
// TODO: Should look into whether we can get some actual data here, or if we can make an "informed" mock based on the setup. | ||||||
// For now, we just return an empty list. | ||||||
return Ok(new List<string>()); | ||||||
} | ||||||
|
||||||
/// <summary> | ||||||
/// Action for updating data model with tag for attachment component // TODO: Figure out what actually happens here | ||||||
/// </summary> | ||||||
/// <param name="org">Unique identifier of the organisation responsible for the app.</param> | ||||||
/// <param name="app">Application identifier which is unique within an organisation.</param> | ||||||
/// <param name="currentPage">Current page in running app</param> | ||||||
/// <param name="layoutSetId">Current layout set in running app</param> | ||||||
/// <param name="dataTypeId">Connected datatype for that process task</param> | ||||||
/// <returns>The options list if it exists, otherwise nothing</returns> | ||||||
[HttpPost] | ||||||
[Route("instances/{partyId}/{instanceGuid}/pages/order")] | ||||||
public IActionResult UpdateAttachmentWithTag(string org, string app, [FromQuery] string currentPage, [FromQuery] string layoutSetId, [FromQuery] string dataTypeId) | ||||||
{ | ||||||
return Ok(); | ||||||
} | ||||||
|
||||||
private static string GetSelectedLayoutSetInEditorFromRefererHeader(string refererHeader) | ||||||
{ | ||||||
Uri refererUri = new(refererHeader); | ||||||
string layoutSetName = HttpUtility.ParseQueryString(refererUri.Query)["selectedLayoutSet"]; | ||||||
|
||||||
return string.IsNullOrEmpty(layoutSetName) ? null : layoutSetName; | ||||||
} | ||||||
} | ||||||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This constant does not seem to be in use