diff --git a/.github/workflows/test-ui.yml b/.github/workflows/test-ui.yml index 464700feb..b29ad2b83 100644 --- a/.github/workflows/test-ui.yml +++ b/.github/workflows/test-ui.yml @@ -40,8 +40,8 @@ jobs: working-directory: ./vuu-ui browser: chrome build: npm run showcase:build - start: npm run showcase:preview - wait-on: "http://localhost:4173" + start: npm run showcase:preview, npm run layout-server + wait-on: http://localhost:4173, http://localhost:8081/api/swagger-ui/index.html - name: Run end-to-end tests in Edge uses: cypress-io/github-action@bd9dda317ed2d4fbffc808ba6cdcd27823b2a13b with: @@ -50,7 +50,7 @@ jobs: browser: edge build: npm run showcase:build start: npm run showcase:preview - wait-on: "http://localhost:4173" + wait-on: http://localhost:4173, http://localhost:8081/api/swagger-ui/index.html # ensure the vuu example still builds vuu-app-build: diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/ApplicationLayoutController.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/ApplicationLayoutController.java index 7db9388d5..0664a2aa0 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/ApplicationLayoutController.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/controller/ApplicationLayoutController.java @@ -2,9 +2,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.RequiredArgsConstructor; -import org.finos.vuu.layoutserver.dto.response.ApplicationLayoutDto; import org.finos.vuu.layoutserver.service.ApplicationLayoutService; -import org.modelmapper.ModelMapper; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -21,7 +19,6 @@ public class ApplicationLayoutController { private final ApplicationLayoutService service; - private final ModelMapper mapper; /** * Gets the persisted application layout for the requesting user. If the requesting user does not have an @@ -32,27 +29,29 @@ public class ApplicationLayoutController { */ @ResponseStatus(HttpStatus.OK) @GetMapping - public ApplicationLayoutDto getApplicationLayout(@RequestHeader("username") String username) { - return mapper.map(service.getApplicationLayout(username), ApplicationLayoutDto.class); + public ObjectNode getApplicationLayout(@RequestHeader("username") String username) { + return service.getApplicationLayout(username).getApplicationLayout(); } /** * Creates or updates the unique application layout for the requesting user. * - * @param layoutDefinition JSON representation of the application layout to be created - * @param username the user making the request + * @param applicationLayout JSON representation of all relevant data about the application layout to be created, + * containing top-level nodes for the layout itself, and (optionally) for associated settings + * @param username the user making the request */ @ResponseStatus(HttpStatus.CREATED) @PutMapping - public void persistApplicationLayout(@RequestHeader("username") String username, @RequestBody ObjectNode layoutDefinition) { - service.persistApplicationLayout(username, layoutDefinition); + public void persistApplicationLayout(@RequestHeader("username") String username, + @RequestBody ObjectNode applicationLayout) { + service.persistApplicationLayout(username, applicationLayout); } /** * Deletes the application layout for the requesting user. A 404 will be returned if there is no existing * application layout. * - * @param username the user making the request + * @param username the user making the request */ @ResponseStatus(HttpStatus.NO_CONTENT) @DeleteMapping diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/ApplicationLayoutDto.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/ApplicationLayoutDto.java deleted file mode 100644 index d04d48af5..000000000 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/dto/response/ApplicationLayoutDto.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.finos.vuu.layoutserver.dto.response; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import lombok.Data; - -@Data -public class ApplicationLayoutDto { - private String username; - private ObjectNode definition; -} diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/ApplicationLayout.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/ApplicationLayout.java index 3ec5631c9..e9cddc3e2 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/model/ApplicationLayout.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/model/ApplicationLayout.java @@ -21,5 +21,5 @@ public class ApplicationLayout { @Convert(converter = ObjectNodeConverter.class) @Column(columnDefinition = "JSON") - private ObjectNode definition; + private ObjectNode applicationLayout; } diff --git a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/ApplicationLayoutService.java b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/ApplicationLayoutService.java index 0727552be..af1956bf4 100644 --- a/layout-server/src/main/java/org/finos/vuu/layoutserver/service/ApplicationLayoutService.java +++ b/layout-server/src/main/java/org/finos/vuu/layoutserver/service/ApplicationLayoutService.java @@ -20,8 +20,8 @@ public class ApplicationLayoutService { private final ApplicationLayoutRepository repository; private final DefaultApplicationLayoutLoader defaultLoader; - public void persistApplicationLayout(String username, ObjectNode layoutDefinition) { - repository.save(new ApplicationLayout(username, layoutDefinition)); + public void persistApplicationLayout(String username, ObjectNode applicationLayout) { + repository.save(new ApplicationLayout(username, applicationLayout)); } public ApplicationLayout getApplicationLayout(String username) { diff --git a/layout-server/src/main/resources/defaultApplicationLayout.json b/layout-server/src/main/resources/defaultApplicationLayout.json index 871b11b44..f3cffc4a0 100644 --- a/layout-server/src/main/resources/defaultApplicationLayout.json +++ b/layout-server/src/main/resources/defaultApplicationLayout.json @@ -1,22 +1,29 @@ { - "id": "main-tabs", - "type": "Stack", - "props": { - "className": "vuuShell-mainTabs", - "TabstripProps": { - "allowAddTab": true, - "allowRenameTab": true, - "animateSelectionThumb": false, - "className": "vuuShellMainTabstrip", - "location": "main-tab" + "applicationLayout": { + "type": "Stack", + "id": "main-tabs", + "props": { + "className": "vuuShell-mainTabs", + "TabstripProps": { + "allowAddTab": true, + "allowCloseTab": true, + "allowRenameTab": true, + "animateSelectionThumb": false, + "location": "main-tab", + "tabClassName": "MainTab" + }, + "preserve": true, + "active": 0 }, - "preserve": true, - "active": 0 - }, - "children": [ - { - "type": "Placeholder", - "title": "Page 1" - } - ] -} + "children": [ + { + "props": { + "id": "tab1", + "title": "Tab 1", + "className": "vuuShell-Placeholder" + }, + "type": "Placeholder" + } + ] + } +} \ No newline at end of file diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/ApplicationLayoutControllerTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/ApplicationLayoutControllerTest.java index bdf1971b8..6c7a97662 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/ApplicationLayoutControllerTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/controller/ApplicationLayoutControllerTest.java @@ -1,49 +1,46 @@ package org.finos.vuu.layoutserver.controller; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; -import org.finos.vuu.layoutserver.dto.response.ApplicationLayoutDto; import org.finos.vuu.layoutserver.model.ApplicationLayout; import org.finos.vuu.layoutserver.service.ApplicationLayoutService; import org.finos.vuu.layoutserver.utils.ObjectNodeConverter; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import org.modelmapper.ModelMapper; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; class ApplicationLayoutControllerTest { private static ApplicationLayoutService mockService; private static ApplicationLayoutController controller; - private static final ModelMapper modelMapper = new ModelMapper(); private static final ObjectNodeConverter objectNodeConverter = new ObjectNodeConverter(); @BeforeEach public void setup() { mockService = Mockito.mock(ApplicationLayoutService.class); - controller = new ApplicationLayoutController(mockService, modelMapper); + controller = new ApplicationLayoutController(mockService); } @Test - public void getApplicationLayout_anyUsername_returnsLayoutFromService() throws JsonProcessingException { + public void getApplicationLayout_anyUsername_returnsLayoutFromService() { String user = "user"; ObjectNode definition = objectNodeConverter.convertToEntityAttribute("{\"id\":\"main-tabs\"}"); when(mockService.getApplicationLayout(user)) .thenReturn(new ApplicationLayout(user, definition)); - ApplicationLayoutDto response = controller.getApplicationLayout(user); + ObjectNode response = controller.getApplicationLayout(user); - assertThat(response.getUsername()).isEqualTo(user); - assertThat(response.getDefinition()).isEqualTo(definition); + assertThat(response).isEqualTo(definition); verify(mockService, times(1)).getApplicationLayout(user); } @Test - public void persistApplicationLayout_anyInput_callsService() throws JsonProcessingException { + public void persistApplicationLayout_anyInput_callsService() { String user = "user"; ObjectNode definition = objectNodeConverter.convertToEntityAttribute("{\"id\":\"main-tabs\"}"); diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/ApplicationLayoutIntegrationTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/ApplicationLayoutIntegrationTest.java index 0f862b852..7dfee983a 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/ApplicationLayoutIntegrationTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/integration/ApplicationLayoutIntegrationTest.java @@ -19,10 +19,14 @@ import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.iterableWithSize; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -49,9 +53,8 @@ public void getApplicationLayout_noLayoutExists_returns200WithDefaultLayout() th mockMvc.perform(get(BASE_URL).header("username", "new user")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.username", nullValue())) // Expecting application layout as defined in /test/resources/defaultApplicationLayout.json - .andExpect(jsonPath("$.definition.defaultLayoutKey", is("default-layout-value"))); + .andExpect(jsonPath("$.defaultLayoutKey", is("default-layout-value"))); } @Test @@ -85,8 +88,7 @@ public void getApplicationLayout_layoutExists_returns200WithPersistedLayout() th mockMvc.perform(get(BASE_URL).header("username", user)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.username", is(user))) - .andExpect(jsonPath("$.definition", is(definition))); + .andExpect(jsonPath("$", is(definition))); } @Test @@ -103,7 +105,7 @@ public void persistApplicationLayout_noLayoutExists_returns201AndPersistsLayout( ApplicationLayout persistedLayout = repository.findById(user).orElseThrow(); assertThat(persistedLayout.getUsername()).isEqualTo(user); - assertThat(persistedLayout.getDefinition()).isEqualTo(objectMapper.readTree(definition)); + assertThat(persistedLayout.getApplicationLayout()).isEqualTo(objectMapper.readTree(definition)); } @Test @@ -128,7 +130,7 @@ public void persistApplicationLayout_layoutExists_returns201AndOverwritesLayout( ApplicationLayout retrievedLayout = repository.findById(user).orElseThrow(); assertThat(retrievedLayout.getUsername()).isEqualTo(user); - assertThat(retrievedLayout.getDefinition()).isEqualTo(objectMapper.readTree(newDefinition)); + assertThat(retrievedLayout.getApplicationLayout()).isEqualTo(objectMapper.readTree(newDefinition)); } @Test diff --git a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/ApplicationLayoutServiceTest.java b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/ApplicationLayoutServiceTest.java index e5b8ecb20..3ed272f42 100644 --- a/layout-server/src/test/java/org/finos/vuu/layoutserver/service/ApplicationLayoutServiceTest.java +++ b/layout-server/src/test/java/org/finos/vuu/layoutserver/service/ApplicationLayoutServiceTest.java @@ -1,6 +1,5 @@ package org.finos.vuu.layoutserver.service; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; import org.finos.vuu.layoutserver.model.ApplicationLayout; import org.finos.vuu.layoutserver.repository.ApplicationLayoutRepository; @@ -36,20 +35,21 @@ public void setup() { } @Test - public void getApplicationLayout_noLayout_returnsDefault() throws JsonProcessingException { + public void getApplicationLayout_noLayout_returnsDefault() { when(mockRepo.findById(anyString())).thenReturn(Optional.empty()); ApplicationLayout actualLayout = service.getApplicationLayout("new user"); // Expecting application layout as defined in /test/resources/defaultApplicationLayout.json - ObjectNode expectedDefinition = objectNodeConverter.convertToEntityAttribute("{\"defaultLayoutKey\":\"default-layout-value\"}"); + ObjectNode expectedDefinition = + objectNodeConverter.convertToEntityAttribute("{\"defaultLayoutKey\":\"default-layout-value\"}"); assertThat(actualLayout.getUsername()).isNull(); - assertThat(actualLayout.getDefinition()).isEqualTo(expectedDefinition); + assertThat(actualLayout.getApplicationLayout()).isEqualTo(expectedDefinition); } @Test - public void getApplicationLayout_layoutExists_returnsLayout() throws JsonProcessingException { + public void getApplicationLayout_layoutExists_returnsLayout() { String user = "user"; ObjectNode expectedDefinition = objectNodeConverter.convertToEntityAttribute("{\"id\":\"main-tabs\"}"); @@ -63,7 +63,7 @@ public void getApplicationLayout_layoutExists_returnsLayout() throws JsonProcess } @Test - public void createApplicationLayout_validDefinition_callsRepoSave() throws JsonProcessingException { + public void createApplicationLayout_validDefinition_callsRepoSave() { String user = "user"; ObjectNode definition = objectNodeConverter.convertToEntityAttribute("{\"id\":\"main-tabs\"}"); diff --git a/vuu-ui/cypress/e2e/layout-management/api/api.utils.ts b/vuu-ui/cypress/e2e/layout-management/api/api.utils.ts new file mode 100644 index 000000000..e20eb4de7 --- /dev/null +++ b/vuu-ui/cypress/e2e/layout-management/api/api.utils.ts @@ -0,0 +1,118 @@ +import { ApplicationJSON, LayoutJSON } from "@finos/vuu-layout"; +import { LayoutMetadata, LayoutMetadataDto } from "@finos/vuu-shell"; + +export type LayoutResponseDto = { + id: string; + definition: LayoutJSON; + metadata: LayoutMetadata; +}; + +const LAYOUT_API_BASE_URL = "http://localhost:8081/api"; + +export const APPLICATION_LAYOUT_URL = + LAYOUT_API_BASE_URL + "/application-layouts/"; +export const USER_LAYOUT_URL = + LAYOUT_API_BASE_URL + "/layouts/"; + +export const TEST_LAYOUT_ID_ALIAS = "TEST_LAYOUT_ID"; +export const DEFAULT_APPLICATION_LAYOUT_ALIAS = "DEFAULT_APPLICATION_LAYOUT"; +export const DEFAULT_APPLICATION_SETTINGS_ALIAS = "DEFAULT_APPLICATION_SETTINGS"; + +export const TEST_LAYOUT_JSON: LayoutJSON = { + type: "Row", +}; + +export const TEST_METADATA_DTO: LayoutMetadataDto = { + name: "Test Layout", + group: "Test Group", + screenshot: "Test Screenshot", + user: "Test User", +}; + +export const getLayout = (id: string): Cypress.Chainable => { + return cy + .request({ + method: "GET", + url: USER_LAYOUT_URL + id, + failOnStatusCode: false, + }) + .then((response) => { + return response; + }); +}; + +export const createLayout = ( + definition: LayoutJSON, + metadata: LayoutMetadataDto +): Cypress.Chainable => { + return cy + .request({ + method: "POST", + url: USER_LAYOUT_URL, + headers: { + "Content-Type": "application/json", + }, + body: { + metadata, + definition, + }, + }) + .then((response) => { + return response; + }); +}; + +export const deleteLayout = (id: string): Cypress.Chainable => { + return cy + .request({ + method: "DELETE", + url: USER_LAYOUT_URL + id, + failOnStatusCode: false, + }) + .then((response) => { + return response; + }); +}; + +export const getApplicationLayout = (username: string): Cypress.Chainable => { + return cy + .request({ + method: "GET", + url: APPLICATION_LAYOUT_URL, + headers: { username }, + }) + .then((response) => { + return response; + }); +}; + +export const deleteApplicationLayout = ( + username: string +): Cypress.Chainable => { + return cy + .request({ + method: "DELETE", + url: APPLICATION_LAYOUT_URL, + headers: { + username, + }, + }) + .then((response) => { + return response; + }); +}; + +export const persistApplicationLayout = ( + username: string, + applicationLayout: ApplicationJSON +): Cypress.Chainable => { + return cy.request({ + method: "PUT", + url: APPLICATION_LAYOUT_URL, + headers: { + "Content-Type": "application/json", + username, + }, + body: JSON.stringify(applicationLayout), + }); +}; diff --git a/vuu-ui/cypress/e2e/layout-management/api/application-layouts.cy.ts b/vuu-ui/cypress/e2e/layout-management/api/application-layouts.cy.ts new file mode 100644 index 000000000..91af92cc6 --- /dev/null +++ b/vuu-ui/cypress/e2e/layout-management/api/application-layouts.cy.ts @@ -0,0 +1,179 @@ +/// + +import { + APPLICATION_LAYOUT_URL, + DEFAULT_APPLICATION_LAYOUT_ALIAS, + DEFAULT_APPLICATION_SETTINGS_ALIAS, + deleteApplicationLayout, + getApplicationLayout, + persistApplicationLayout, +} from "./api.utils"; +import { ApplicationJSON, ApplicationSettings, LayoutJSON } from "@finos/vuu-layout"; + +describe("Application Layouts", () => { + const testUser = "Test User"; + + before(() => { + getApplicationLayout(testUser).then((response) => { + Cypress.env( + DEFAULT_APPLICATION_LAYOUT_ALIAS, + response.body.applicationLayout + ); + Cypress.env( + DEFAULT_APPLICATION_SETTINGS_ALIAS, + response.body.settings + ); + }); + }); + + context("GET /application-layouts", () => { + it("should return a 200 with the default application layout", () => { + getApplicationLayout(testUser).then((response) => { + expect(response.status).to.eq(200); + + expect(response.body).to.have.property("applicationLayout"); + + expect(JSON.stringify(response.body.applicationLayout)).to.equal( + JSON.stringify(Cypress.env(DEFAULT_APPLICATION_LAYOUT_ALIAS)) + ); + expect(JSON.stringify(response.body.settings)).to.equal( + JSON.stringify(Cypress.env(DEFAULT_APPLICATION_SETTINGS_ALIAS)) + ); + }); + }); + }); + + context("PUT /application-layouts", () => { + beforeEach(() => { + const requestBody: ApplicationJSON = { + applicationLayout: Cypress.env(DEFAULT_APPLICATION_LAYOUT_ALIAS), + settings: Cypress.env(DEFAULT_APPLICATION_SETTINGS_ALIAS), + } + + persistApplicationLayout( + testUser, + requestBody, + ); + }); + + afterEach(() => { + deleteApplicationLayout(testUser); + }); + + it("should update the application layout for the user", () => { + let originalApplicationLayout: LayoutJSON; + let originalApplicationSettings: ApplicationSettings; + + const requestBody: ApplicationJSON = { + applicationLayout: { + type: "Updated", + }, + settings: { + leftNav: { + activeTabIndex: 1, + expanded: false, + }, + }, + }; + + getApplicationLayout(testUser) + .then((response) => { + originalApplicationLayout = response.body.applicationLayout; + originalApplicationSettings = response.body.settings; + }) + .then(() => { + persistApplicationLayout(testUser, requestBody).then((response) => { + expect(response.body).to.be.empty; + expect(response.status).to.eq(201); + }); + }) + .then(() => { + getApplicationLayout(testUser).then((response) => { + expect(response.status).to.eq(200); + + expect(response.body).to.have.property("applicationLayout"); + expect(response.body).to.have.property("settings"); + + expect(JSON.stringify(response.body)).to.equal( + JSON.stringify(requestBody) + ); + + expect(response.body.applicationLayout).to.not.equal( + originalApplicationLayout + ); + expect(response.body.settings).to.not.equal( + originalApplicationSettings + ); + }); + }); + }); + + it("should send a request without settings and return a 201", () => { + const requestBody: ApplicationJSON = { + applicationLayout: Cypress.env(DEFAULT_APPLICATION_LAYOUT_ALIAS), + }; + + cy.request({ + method: "PUT", + url: APPLICATION_LAYOUT_URL, + headers: { + username: testUser, + }, + body: { + requestBody, + }, + }).then((response) => { + expect(response.status).to.eq(201); + expect(response.body).to.not.exist; + }); + }); + + it("should send a request with settings and return a 201 with the settings", () => { + const settings: ApplicationSettings = { + leftNav: { + activeTabIndex: 1, + expanded: false, + }, + }; + const requestBody: ApplicationJSON = { + applicationLayout: Cypress.env(DEFAULT_APPLICATION_LAYOUT_ALIAS), + settings, + }; + + persistApplicationLayout(testUser, requestBody) + .then((response) => { + expect(response.status).to.eq(201); + expect(response.body).to.be.empty; + }) + .then(() => { + getApplicationLayout(testUser).then((response) => { + expect(response.status).to.eq(200); + expect(response.body).to.have.property("settings"); + expect(JSON.stringify(response.body.settings)).to.equal( + JSON.stringify(settings) + ); + }); + }); + }); + }); + + context("DELETE /application-layouts", () => { + it("should return a 204 and the user should not have an application layout", () => { + persistApplicationLayout( + testUser, + Cypress.env(DEFAULT_APPLICATION_LAYOUT_ALIAS) + ); + + deleteApplicationLayout(testUser) + .then((response) => { + expect(response.status).to.eq(204); + expect(response.body).to.be.empty; + }) + .then(() => { + getApplicationLayout(testUser).then((response) => { + expect(response.status).to.eq(200); + }); + }); + }); + }); +}); diff --git a/vuu-ui/cypress/e2e/layout-management/api/user-layouts.cy.ts b/vuu-ui/cypress/e2e/layout-management/api/user-layouts.cy.ts new file mode 100644 index 000000000..5800b612a --- /dev/null +++ b/vuu-ui/cypress/e2e/layout-management/api/user-layouts.cy.ts @@ -0,0 +1,117 @@ +/// + +import { LayoutMetadata, LayoutMetadataDto } from "@finos/vuu-shell"; +import { + createLayout, + deleteLayout, + getLayout, + TEST_LAYOUT_ID_ALIAS, + TEST_LAYOUT_JSON, + TEST_METADATA_DTO, + USER_LAYOUT_URL, +} from "./api.utils"; + +describe("User Layouts", () => { + beforeEach(() => { + createLayout(TEST_LAYOUT_JSON, TEST_METADATA_DTO).then((response) => { + Cypress.env(TEST_LAYOUT_ID_ALIAS, response.body.id); + }); + }); + + afterEach(() => { + deleteLayout(Cypress.env(TEST_LAYOUT_ID_ALIAS)); + + Cypress.env(TEST_LAYOUT_ID_ALIAS, null); + }); + + context("POST /layouts", () => { + it("should return a 201 with the ID and definition of the created layout", () => { + createLayout(TEST_LAYOUT_JSON, TEST_METADATA_DTO) + .then((response) => { + expect(response.status).to.eq(201); + expect(response.body.id).to.exist; + expect(response.body.definition).to.contain(TEST_LAYOUT_JSON); + expect(response.body.metadata).to.contain(TEST_METADATA_DTO); + + return response; + }) + .then((response) => { + deleteLayout(response.body.id); + }); + }); + }); + + context("GET /layouts/:id", () => { + it("should return a 200 with the definition of the layout", () => { + getLayout(Cypress.env(TEST_LAYOUT_ID_ALIAS)).then((response) => { + expect(response.status).to.eq(200); + expect(response.body.id).to.exist; + expect(response.body.definition).to.contain(TEST_LAYOUT_JSON); + expect(response.body.metadata).to.contain(TEST_METADATA_DTO); + }); + }); + }); + + context("GET /layouts/metadata", () => { + it("should return a 200", () => { + cy.request({ + method: "GET", + url: USER_LAYOUT_URL + "metadata", + }).then((response) => { + expect(response.status).to.eq(200); + expect(response.body).to.have.lengthOf(1); + console.log(response.body); + + const { name, screenshot, user, group }: LayoutMetadataDto = response + .body[0] as LayoutMetadata; + + expect(name).to.equal(TEST_METADATA_DTO.name); + expect(screenshot).to.equal(TEST_METADATA_DTO.screenshot); + expect(user).to.equal(TEST_METADATA_DTO.user); + expect(group).to.equal(TEST_METADATA_DTO.group); + }); + }); + }); + + context("PUT /layouts/:id", () => { + it("should return a 204 and the layout should be updated", () => { + cy.request({ + method: "PUT", + url: + USER_LAYOUT_URL + Cypress.env(TEST_LAYOUT_ID_ALIAS), + body: { + metadata: TEST_METADATA_DTO, + definition: { ...TEST_LAYOUT_JSON, type: "Column" }, + }, + }) + .then((response) => { + expect(response.status).to.eq(204); + expect(response.body).to.not.exist; + }) + .then(() => { + getLayout(Cypress.env(TEST_LAYOUT_ID_ALIAS)).then((response) => { + expect(response.body.definition).to.have.property("type", "Column"); + }); + }); + }); + }); + + context("DELETE /layouts/:id", () => { + it("should return a 204 and layout should be deleted", () => { + cy.request({ + method: "DELETE", + url: + USER_LAYOUT_URL + Cypress.env(TEST_LAYOUT_ID_ALIAS), + }) + .then((response) => { + expect(response.status).to.eq(204); + expect(response.body).to.be.empty; + }) + .then(() => { + getLayout(Cypress.env(TEST_LAYOUT_ID_ALIAS)).then((response) => { + expect(response.status).to.eq(404); + }); + }); + }); + }); +}); diff --git a/vuu-ui/cypress/tests/PerformanceTester.tsx b/vuu-ui/cypress/performance/PerformanceTester.tsx similarity index 100% rename from vuu-ui/cypress/tests/PerformanceTester.tsx rename to vuu-ui/cypress/performance/PerformanceTester.tsx diff --git a/vuu-ui/cypress/tests/checkAccessibility.tsx b/vuu-ui/cypress/performance/checkAccessibility.tsx similarity index 100% rename from vuu-ui/cypress/tests/checkAccessibility.tsx rename to vuu-ui/cypress/performance/checkAccessibility.tsx diff --git a/vuu-ui/package-lock.json b/vuu-ui/package-lock.json index a66622ba8..9e3bb42df 100644 --- a/vuu-ui/package-lock.json +++ b/vuu-ui/package-lock.json @@ -13037,6 +13037,7 @@ }, "sample-apps/standalone-table": { "version": "0.0.26", + "extraneous": true, "license": "Apache-2.0", "dependencies": { "@finos/vuu-table": "0.0.26", diff --git a/vuu-ui/package.json b/vuu-ui/package.json index badd87b1d..d84e29b51 100644 --- a/vuu-ui/package.json +++ b/vuu-ui/package.json @@ -28,6 +28,7 @@ "launch:table": "node ./scripts/launch-table.mjs", "launch:demo:electron": "cd tools/electron && node ./node_modules/.bin/electron .", "launch:showcase": " cd showcase && node scripts/launch.mjs", + "layout-server": "cd ../layout-server && mvn spring-boot:run", "deploy:websocket-test": "node ./tools/deploy-websocket-test.mjs", "view-bundle": "node ./scripts/visualize-bundle.mjs", "type-defs": "node ./scripts/build-all-type-defs.mjs", diff --git a/vuu-ui/packages/vuu-layout/src/layout-reducer/layoutTypes.ts b/vuu-ui/packages/vuu-layout/src/layout-reducer/layoutTypes.ts index 30519415d..f69c720fe 100644 --- a/vuu-ui/packages/vuu-layout/src/layout-reducer/layoutTypes.ts +++ b/vuu-ui/packages/vuu-layout/src/layout-reducer/layoutTypes.ts @@ -35,7 +35,7 @@ export interface ApplicationSettings { export type ApplicationSetting = ValueOf; export interface ApplicationJSON { - layout: LayoutJSON; + applicationLayout: LayoutJSON; settings?: ApplicationSettings; } diff --git a/vuu-ui/packages/vuu-shell/src/layout-management/useLayoutManager.tsx b/vuu-ui/packages/vuu-shell/src/layout-management/useLayoutManager.tsx index 8b371efdb..d6af5b451 100644 --- a/vuu-ui/packages/vuu-shell/src/layout-management/useLayoutManager.tsx +++ b/vuu-ui/packages/vuu-shell/src/layout-management/useLayoutManager.tsx @@ -103,7 +103,7 @@ export const LayoutManagementProvider = ( setApplicationJSON( { ...applicationJSONRef.current, - layout, + applicationLayout: layout, }, rerender ); @@ -173,7 +173,7 @@ export const LayoutManagementProvider = ( const saveLayout = useCallback( (metadata: LayoutMetadataDto) => { const layoutToSave = resolveJSONPath( - applicationJSONRef.current.layout, + applicationJSONRef.current.applicationLayout, "#main-tabs.ACTIVE_CHILD" ); @@ -239,7 +239,7 @@ export const LayoutManagementProvider = ( getPersistenceManager() .loadLayout(id) .then((layoutJson) => { - const { layout: currentLayout } = applicationJSONRef.current; + const { applicationLayout: currentLayout } = applicationJSONRef.current; setApplicationLayout({ ...currentLayout, children: (currentLayout.children || []).concat(layoutJson), diff --git a/vuu-ui/packages/vuu-shell/src/persistence-management/RemotePersistenceManager.ts b/vuu-ui/packages/vuu-shell/src/persistence-management/RemotePersistenceManager.ts index e7c908dd3..4b175126a 100644 --- a/vuu-ui/packages/vuu-shell/src/persistence-management/RemotePersistenceManager.ts +++ b/vuu-ui/packages/vuu-shell/src/persistence-management/RemotePersistenceManager.ts @@ -13,7 +13,6 @@ const applicationLayoutsSaveLocation = "application-layouts"; export type CreateLayoutResponseDto = { metadata: LayoutMetadata }; export type GetLayoutResponseDto = { definition: LayoutJSON }; -export type GetApplicationResponseDto = { definition: ApplicationJSON }; export class RemotePersistenceManager implements PersistenceManager { username: string = getAuthDetailsFromCookies()[0]; @@ -170,7 +169,7 @@ export class RemotePersistenceManager implements PersistenceManager { if (!response.ok) { reject(new Error(response.statusText)); } - response.json().then((applicationJSON: GetApplicationResponseDto) => { + response.json().then((applicationJSON: ApplicationJSON) => { if (!applicationJSON) { reject( new Error( @@ -178,7 +177,7 @@ export class RemotePersistenceManager implements PersistenceManager { ) ); } - resolve(applicationJSON.definition); + resolve(applicationJSON); }); }) .catch((error: Error) => { diff --git a/vuu-ui/packages/vuu-shell/src/persistence-management/defaultApplicationJson.ts b/vuu-ui/packages/vuu-shell/src/persistence-management/defaultApplicationJson.ts index ba3b475a3..0d842ee23 100644 --- a/vuu-ui/packages/vuu-shell/src/persistence-management/defaultApplicationJson.ts +++ b/vuu-ui/packages/vuu-shell/src/persistence-management/defaultApplicationJson.ts @@ -16,7 +16,7 @@ export const warningLayout: LayoutJSON = { }; export const loadingApplicationJson: Readonly = { - layout: { + applicationLayout: { type: "Component", id: "loading-main", props: {}, @@ -24,7 +24,7 @@ export const loadingApplicationJson: Readonly = { }; export const defaultApplicationJson: ApplicationJSON = { - layout: { + applicationLayout: { type: "Stack", id: "main-tabs", props: { diff --git a/vuu-ui/packages/vuu-shell/src/shell.tsx b/vuu-ui/packages/vuu-shell/src/shell.tsx index c1502432b..dc0f069f4 100644 --- a/vuu-ui/packages/vuu-shell/src/shell.tsx +++ b/vuu-ui/packages/vuu-shell/src/shell.tsx @@ -149,7 +149,7 @@ export const Shell = ({ > { return ( { return ( { // ---------------------------------------------------------------------------------- return ( - +