Skip to content
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

VUU70: Add resource for application layouts #78

Merged
merged 75 commits into from
Nov 8, 2023

Conversation

pling-scottlogic
Copy link

@pling-scottlogic pling-scottlogic commented Oct 17, 2023

Description

Adds a resource to the layout server for operating on application layouts. These are top-level descriptions of all the individual layouts currently open for a given user, i.e. all open tabs. These are intended to represent the current visual state of the application for a given user, hence the use of username as primary key/ID.

Change List

  • Add a new resource (controller, service and repository) for application layouts
  • Add DTO and entity to model application layouts
  • Add static JSON file to store the default layout (plus simplified version for test purposes)
  • Add unit and integration tests

Screenshots

Example usage of POST endpoint. Note the username in request headers. The request body is the full application layout definition, which can take the structure of any valid JSON.

image

Closes #70

…tion

- Also amend DTOs to differentiate request/response data
# Conflicts:
#	layout-server/src/main/java/org/finos/vuu/layoutserver/controller/LayoutController.java
#	layout-server/src/main/java/org/finos/vuu/layoutserver/model/Layout.java
#	layout-server/src/main/java/org/finos/vuu/layoutserver/model/Metadata.java
- Introduce DefaultApplicationLayoutLoader to lazily load JSON from a static file
- Refactor service to use loader
- Add integration test case for failure to load default
- Rename static JSON files
- Move JsonNodeConverter to utils package
cfisher-scottlogic

This comment was marked as outdated.

- Rename header key from 'user' to 'username'
- Add integration tests for missing username
Copy link

@cfisher-scottlogic cfisher-scottlogic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left some comments but I don't think it's anything that needs changing, I'll leave that up to you.

Comment on lines +83 to +84
Map<String, String> definition = new HashMap<>();
definition.put("defKey", "defVal");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come we're using a stringified JSON object in some requests and a Map in others? My only concern with using a Map is that we're introducing a conversion from Map -> JsonNode which isn't present in the real implementation (unless it is?)

If it is a possible conversion present in the real implementation, I'm all for testing against the various possible types given as inputs, but I would prefer to see that as an explicit test for that purpose so the point of failure is clear, rather than potentially hidden behind a different test scenario.

Comment on lines +118 to +120
persistApplicationLayout(user, initialDefinition);

String newDefinition = "{\"new-key\": \"new-value\"}";
Copy link

@cfisher-scottlogic cfisher-scottlogic Oct 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it's worth adding an assertion here that the layout exists with this initial form, so we know that the second assertion is checking it's changed and not just currently there? Does that make sense?

assertThat(repository.findById(user).getUsername()).isEqualTo(user);
        
assertThat(repository.findById(user).getDefinition()).isEqualTo(objectMapper.readTree(newDefinition));

initialDefinition.put("initial-key", "initial-value");

persistApplicationLayout(user, initialDefinition);

Copy link

@cfisher-scottlogic cfisher-scottlogic Oct 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above, do you think it's worth checking assertThat(repository.findAll()).hasSize(1) prior to deletion so we know the thing has been "deleted"?


private static ApplicationLayoutRepository mockRepo;
private static ApplicationLayoutService service;
private static final DefaultApplicationLayoutLoader defaultLoader = new DefaultApplicationLayoutLoader();
Copy link

@cfisher-scottlogic cfisher-scottlogic Oct 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should DefaultApplicationLayoutLoader be mocked out?

Comment on lines +76 to +83
@Test
public void createApplicationLayout_invalidDefinition_throwsJsonException() {
String definition = "invalid JSON";

assertThrows(JsonProcessingException.class, () ->
service.persistApplicationLayout("user", objectMapper.readTree(definition))
);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we even need this test case? I think the controller would reject it before it even reaches this stage? Or is it safer to check for this scenario anyway, regardless of what the caller of this service is doing?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we were being super OOPy, I could see us having a FileReader base class and a DefaultApplicationLayoutReader sub class. Whether it's interfaces, abstract, or inheritance. But that's a bit overkill for this simple server. Would you agree?

Base automatically changed from VUU25-layout-server-with-tests to main November 3, 2023 15:25
Copy link

@cfisher-scottlogic cfisher-scottlogic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a few things I think have been resolved incorrectly, nothing major though

Comment on lines 39 to 41
return new ResponseEntity<>(errors.toString(),
org.springframework.http.HttpStatus.BAD_REQUEST);
org.springframework.http.HttpStatus.BAD_REQUEST);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be returning a generated error response

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am happy to change this to use generateResponse() but note that if we do that we produce the following ErrorResponse:
{ "timestamp": "2023-11-06T15:17:39.657+00:00", "status": 400, "error": "Bad Request", "message": "Validation failed for argument [0] in public org.finos.vuu.layoutserver.dto.response.LayoutResponseDto org.finos.vuu.layoutserver.controller.LayoutController.createLayout(org.finos.vuu.layoutserver.dto.request.LayoutRequestDto): [Field error in object 'layoutRequestDto' on field 'definition': rejected value [null]; codes [NotBlank.layoutRequestDto.definition,NotBlank.definition,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [layoutRequestDto.definition,definition]; arguments []; default message [definition]]; default message [Definition must not be blank]] ", "path": "/api/layouts" }

where the errorMessage is
Validation failed for argument [0] in public org.finos.vuu.layoutserver.dto.response.LayoutResponseDto org.finos.vuu.layoutserver.controller.LayoutController.createLayout(org.finos.vuu.layoutserver.dto.request.LayoutRequestDto): [Field error in object 'layoutRequestDto' on field 'definition': rejected value [null]; codes [NotBlank.layoutRequestDto.definition,NotBlank.definition,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [layoutRequestDto.definition,definition]; arguments []; default message [definition]]; default message [Definition must not be blank]]
In the other PR we were building the errorMessage inside the handler and the message was looking like this:
[definition: Definition must not be blank]
which is a lot more concise and I liked better but obviously is not ideal because we are not wrapping it in a ErrorResponse like the other 400 responses.
Maybe we can revisit the ErrorResponse constructor at some point to extract the error messages like we were doing in the handler and give us a more concise error message for this error.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

discussed offline: change generateResponse to extract error messages from excpetions

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to follow on from what we discussed earlier, I think one of the flaws of the overloading approach is that we could be creating some tight coupling between an 'error handler' and a 'generate response', so each error handler will end up needing it's own 'generate response' method. And in that case, we may as well not have the helper method, and handle each error message generation within the 'error handler'. Making 'generate response' the simple method (by just taking the error message rather than the exception) would resolve this, as we'd still only have one 'error handler', and a single 'generate response'.

Just something I thought of, but I'm happy to hear your thoughts.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment, all handlers but one share the same logic for extracting the error message from the exception and I like that that is shared (in generateResponse) rather than repeated inside each handler.
As, the only difference between the 2 versions of generateResponse is the way the errorMessages are extracted from the exception, another solution would be to get rid of generateResponse and just have a method that takes the exception and returns the errorMessages (with the same overloading approach) and then we can just return ErrorResponse inside the handlers.

@vferraro-scottlogic vferraro-scottlogic merged commit 64e0dcf into main Nov 8, 2023
7 checks passed
@cfisher-scottlogic cfisher-scottlogic deleted the VUU70-application-layout-resource branch November 13, 2023 09:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add resource for application layout to layout server
3 participants