diff --git a/.gitignore b/.gitignore index 51c1bda4f..b95e3780d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,7 @@ Thumbs.db # Intellij # ###################### .idea -!.idea/runConfigurations +!.idea/runConfigurations/ *.iml **/build/ **/out/ diff --git a/.idea/runConfigurations/Build.xml b/.idea/runConfigurations/Build.xml new file mode 100644 index 000000000..bf87367c8 --- /dev/null +++ b/.idea/runConfigurations/Build.xml @@ -0,0 +1,21 @@ + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Debug.xml b/.idea/runConfigurations/Debug.xml new file mode 100644 index 000000000..cd0c090fa --- /dev/null +++ b/.idea/runConfigurations/Debug.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Open_Cypress.xml b/.idea/runConfigurations/Open_Cypress.xml new file mode 100644 index 000000000..335acf359 --- /dev/null +++ b/.idea/runConfigurations/Open_Cypress.xml @@ -0,0 +1,23 @@ + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Run_Tests.xml b/.idea/runConfigurations/Run_Tests.xml new file mode 100644 index 000000000..ce4cd7477 --- /dev/null +++ b/.idea/runConfigurations/Run_Tests.xml @@ -0,0 +1,23 @@ + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index f6b90f2dc..4451a9a0c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,6 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +import org.apache.tools.ant.taskdefs.condition.Os + plugins { id("java-library") id("com.bmuschko.docker-remote-api") @@ -50,8 +53,19 @@ allprojects { } } -tasks.named("build") { - dependsOn(":cogboard-app:test", ":cogboard-webapp:buildImage") +tasks { + named("build") { + dependsOn(":cogboard-app:test", ":cogboard-webapp:buildImage") + } + register("cypressInit", Exec::class) { + setWorkingDir("./functional/cypress-tests") + commandLine("npm${getCMD()}", "install") + } + register("cypressOpen", Exec::class) { + setWorkingDir("./functional/cypress-tests") + commandLine("npx${getCMD()}", "cypress", "open") + dependsOn("cypressInit") + } } detekt { @@ -68,4 +82,6 @@ apply(from = "gradle/prepareCogboardCompose.gradle.kts") // Uncomment lines below so you can print tasks execution order easily //gradle.taskGraph.whenReady { // this.allTasks.forEach { logger.error(it.path + " " + it.name) } -//} \ No newline at end of file +//} + +fun getCMD() = if (Os.isFamily(Os.FAMILY_WINDOWS)) ".cmd" else "" \ No newline at end of file diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/BoardsController.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/BoardsController.kt index 7ac89f0f6..cbcbb87c7 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/BoardsController.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/BoardsController.kt @@ -1,40 +1,44 @@ package com.cognifide.cogboard.config.controller import com.cognifide.cogboard.CogboardConstants -import com.cognifide.cogboard.config.helper.EntityCleanupHelper import com.cognifide.cogboard.config.service.BoardsConfigService -import com.cognifide.cogboard.storage.ContentRepository import io.vertx.core.AbstractVerticle import io.vertx.core.json.JsonObject class BoardsController(private val factory: ControllerFactory = ControllerFactory()) : AbstractVerticle() { private lateinit var boardsConfigService: BoardsConfigService - private lateinit var sender: ConfirmationSender override fun start() { - val contentRepository = ContentRepository() - sender = ConfirmationSender(vertx) - boardsConfigService = BoardsConfigService( - contentRepository, - EntityCleanupHelper(vertx) - ) - + boardsConfigService = BoardsConfigService(vertx) factory.create(CogboardConstants.Event.BOARDS_CONFIG, vertx, prepareConfig()) } private fun prepareConfig() = mapOf String>( + "save" to { body -> save(body) }, "update" to { body -> update(body) }, + "delete" to { body -> delete(body) }, "get" to { _ -> get() } ) - private fun update(body: JsonObject): String { + private fun save(body: JsonObject): String { boardsConfigService.saveBoardsConfig(body) - sender.sendOk() - return JsonObject() - .put("message", "OK") - .toString() + return OK_MESSAGE + } + + private fun update(body: JsonObject): String { + boardsConfigService.updateBoard(body) + return OK_MESSAGE + } + + private fun delete(body: JsonObject): String { + boardsConfigService.deleteBoard(body.getString("id")) + return OK_MESSAGE } private fun get() = boardsConfigService.loadBoardsConfig().toString() + + companion object { + const val OK_MESSAGE = "{\"message\":\"OK\"}" + } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/ConfirmationSender.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/ConfirmationSender.kt deleted file mode 100644 index 5b1e013f8..000000000 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/ConfirmationSender.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.cognifide.cogboard.config.controller - -import com.cognifide.cogboard.CogboardConstants.Event -import com.cognifide.cogboard.CogboardConstants.EventType -import com.cognifide.cogboard.CogboardConstants.Props -import io.vertx.core.Vertx -import io.vertx.core.json.JsonObject - -class ConfirmationSender(private val vertx: Vertx) { - - fun sendOk() { - vertx.eventBus().send(Event.SEND_MESSAGE_TO_WEBSOCKET, JsonObject().message(OK_MESSAGE)) - } - - private fun JsonObject.message(message: String): JsonObject { - return this - .put(Props.EVENT_TYPE, EventType.CONFIG_SAVED) - .put("message", message) - } - - companion object { - private const val OK_MESSAGE = "Configuration saved" - } -} diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/WidgetsController.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/WidgetsController.kt index f66ab489c..3c3f7e734 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/WidgetsController.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/controller/WidgetsController.kt @@ -2,10 +2,8 @@ package com.cognifide.cogboard.config.controller import com.cognifide.cogboard.CogboardConstants.Event import com.cognifide.cogboard.CogboardConstants.Props -import com.cognifide.cogboard.config.helper.EntityCleanupHelper import com.cognifide.cogboard.config.service.BoardsConfigService import com.cognifide.cogboard.config.service.WidgetRuntimeService -import com.cognifide.cogboard.storage.ContentRepository import io.vertx.core.AbstractVerticle import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject @@ -14,16 +12,12 @@ class WidgetsController : AbstractVerticle() { private lateinit var boardsConfigService: BoardsConfigService private lateinit var widgetRuntimeService: WidgetRuntimeService + override fun start() { - val contentRepository = ContentRepository() - boardsConfigService = BoardsConfigService( - contentRepository, - EntityCleanupHelper(vertx)) + boardsConfigService = BoardsConfigService(vertx) val allWidgets = boardsConfigService.getAllWidgets() - widgetRuntimeService = - WidgetRuntimeService(vertx, contentRepository) - .init(allWidgets) + widgetRuntimeService = WidgetRuntimeService(vertx).init(allWidgets) listenOnWidgetUpdate() listenOnWidgetContentUpdate() diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/handler/HandlerUtil.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/handler/HandlerUtil.kt index 4b48f6268..f007d8c91 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/handler/HandlerUtil.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/handler/HandlerUtil.kt @@ -14,6 +14,6 @@ class HandlerUtil { .end(body) } - val SESSION_REFRESHERS = setOf(HttpMethod.POST, HttpMethod.DELETE) + private val SESSION_REFRESHERS = setOf(HttpMethod.POST, HttpMethod.DELETE) } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/model/Board.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/model/Board.kt index 311ca2349..1a5ab3c03 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/model/Board.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/model/Board.kt @@ -11,7 +11,6 @@ data class Board( val autoSwitch: Boolean = false, val switchInterval: Int, val widgets: List, - val theme: String, val type: String = "WidgetBoard", @JsonIgnore val dynamicFields: MutableMap = mutableMapOf() diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/model/Widgets.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/model/Widgets.kt index 5c9af8eff..2e4074a0f 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/model/Widgets.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/model/Widgets.kt @@ -1,6 +1,5 @@ package com.cognifide.cogboard.config.model data class Widgets( - val widgetsById: Map, - val allWidgets: List + val widgetsById: Map ) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/BoardsConfigHelper.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/BoardsConfigHelper.kt new file mode 100644 index 000000000..d49714af2 --- /dev/null +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/BoardsConfigHelper.kt @@ -0,0 +1,34 @@ +package com.cognifide.cogboard.config.service + +import com.cognifide.cogboard.CogboardConstants.Props +import io.vertx.core.json.JsonArray +import io.vertx.core.json.JsonObject + +class BoardsConfigHelper { + companion object { + fun removeBoard(config: JsonObject, id: String): JsonObject { + val copy = config.copy() + copy.allBoards()?.remove(id) + copy.boardsById()?.remove(id) + return copy + } + + fun updateBoard(config: JsonObject, board: JsonObject): JsonObject { + val id = board.getString(Props.ID) + val copy = config.copy() + val prevWidgets = copy.boardsById()?.getJsonObject(id)?.getJsonArray(Props.WIDGETS) ?: JsonArray() + if (!copy.allBoards()?.contains(id)!!) { + copy.allBoards()?.add(id) + } + if (board.widgetsNotPresent()) { + board.put(Props.WIDGETS, prevWidgets) + } + copy.boardsById()?.put(id, board) + return copy + } + } +} + +private fun JsonObject.allBoards(): JsonArray? = this.getJsonObject(Props.BOARDS).getJsonArray(Props.BOARDS_ALL) +private fun JsonObject.boardsById(): JsonObject? = this.getJsonObject(Props.BOARDS).getJsonObject(Props.BOARDS_BY_ID) +private fun JsonObject.widgetsNotPresent() = !this.containsKey(Props.WIDGETS) diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/BoardsConfigService.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/BoardsConfigService.kt index ce6f1c7e4..7f81ff6da 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/BoardsConfigService.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/BoardsConfigService.kt @@ -1,23 +1,28 @@ package com.cognifide.cogboard.config.service +import com.cognifide.cogboard.CogboardConstants import com.cognifide.cogboard.CogboardConstants.Props import com.cognifide.cogboard.config.helper.EntityCleanupHelper import com.cognifide.cogboard.storage.ContentRepository import com.cognifide.cogboard.storage.Storage import com.cognifide.cogboard.storage.VolumeStorageFactory.boards import com.cognifide.cogboard.widget.WidgetIndex +import io.vertx.core.Vertx import io.vertx.core.json.JsonObject class BoardsConfigService( - private val contentRepository: ContentRepository = ContentRepository(), - private val entityCleanupHelper: EntityCleanupHelper? = null, + private val vertx: Vertx? = null, + private val contentRepository: ContentRepository = ContentRepository.DEFAULT, private val storage: Storage = boards() ) { + private var entityCleanupHelper: EntityCleanupHelper? = if (vertx == null) null else EntityCleanupHelper(vertx) + fun saveBoardsConfig(boardsConfig: JsonObject) { val cleanBoardsConfig = executeForWidgets(boardsConfig, this::resetContentNode) handleDeletedEntities(cleanBoardsConfig) storage.saveConfig(cleanBoardsConfig) + sendOk() } fun loadBoardsConfig(): JsonObject { @@ -33,13 +38,25 @@ class BoardsConfigService( contentRepository.save(widgetId, content) } + fun updateBoard(board: JsonObject) { + val config = storage.loadConfig() + val updatedConfig = BoardsConfigHelper.updateBoard(config, board) + saveBoardsConfig(updatedConfig) + } + + fun deleteBoard(id: String) { + val config = storage.loadConfig() + val updatedConfig = BoardsConfigHelper.removeBoard(config, id) + saveBoardsConfig(updatedConfig) + } + fun getContent(widgetId: String): JsonObject { return contentRepository.get(widgetId) } private fun getWidgetById(boardsConfig: JsonObject) = - boardsConfig.getJsonObject(Props.WIDGETS) - ?.getJsonObject(Props.WIDGETS_BY_ID) ?: JsonObject() + boardsConfig.getJsonObject(Props.WIDGETS) + ?.getJsonObject(Props.WIDGETS_BY_ID) ?: JsonObject() private fun resetContentNode(widgetId: String, widget: JsonObject) { widget.put(Props.CONTENT, JsonObject()) @@ -55,8 +72,8 @@ class BoardsConfigService( ): JsonObject { val widgetsById = getWidgetById(boardsConfig) widgetsById.fieldNames() - .map { it to widgetsById.getJsonObject(it) } - .forEach { action(it.first, it.second) } + .map { it to widgetsById.getJsonObject(it) } + .forEach { action(it.first, it.second) } return boardsConfig } @@ -71,4 +88,14 @@ class BoardsConfigService( .remove(it) } } + + private fun sendOk() { + vertx?.eventBus()?.send(CogboardConstants.Event.SEND_MESSAGE_TO_WEBSOCKET, JsonObject().configSaved()) + } + + private fun JsonObject.configSaved(): JsonObject { + return this + .put(Props.EVENT_TYPE, CogboardConstants.EventType.CONFIG_SAVED) + .put("message", "Configuration saved") + } } diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/WidgetRuntimeService.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/WidgetRuntimeService.kt index 167122cc6..46abcd931 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/WidgetRuntimeService.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/config/service/WidgetRuntimeService.kt @@ -13,10 +13,9 @@ import io.vertx.core.logging.Logger import io.vertx.core.logging.LoggerFactory import java.util.stream.Collectors -class WidgetRuntimeService( - private val vertx: Vertx, - private val contentRepository: ContentRepository -) { +class WidgetRuntimeService(private val vertx: Vertx) { + + private val contentRepository: ContentRepository = ContentRepository.DEFAULT private val widgets = mutableMapOf() fun init(widgetsById: JsonObject): WidgetRuntimeService { diff --git a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/storage/ContentRepository.kt b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/storage/ContentRepository.kt index 284719677..1f66ca8b5 100644 --- a/cogboard-app/src/main/kotlin/com/cognifide/cogboard/storage/ContentRepository.kt +++ b/cogboard-app/src/main/kotlin/com/cognifide/cogboard/storage/ContentRepository.kt @@ -20,4 +20,8 @@ class ContentRepository(private val configRoot: String = "/data") { } private fun getFile(widgetId: String) = File("$configRoot/content/$widgetId.json") + + companion object { + val DEFAULT = ContentRepository() + } } diff --git a/cogboard-app/src/main/resources/cypressData/config.json b/cogboard-app/src/main/resources/cypressData/config.json index 5a076fb86..af09cfa5a 100644 --- a/cogboard-app/src/main/resources/cypressData/config.json +++ b/cogboard-app/src/main/resources/cypressData/config.json @@ -43,7 +43,6 @@ "isUpdating": false, "boardId": "board1" } - }, - "allWidgets": ["widget1", "widget2"] + } } } diff --git a/cogboard-app/src/main/resources/initData/config.json b/cogboard-app/src/main/resources/initData/config.json index 8c3d65e31..c4949e02f 100644 --- a/cogboard-app/src/main/resources/initData/config.json +++ b/cogboard-app/src/main/resources/initData/config.json @@ -155,86 +155,6 @@ ] }, "widgets": { - "allWidgets": [ - "widget9", - "widget10", - "widget11", - "widget12", - "widget13", - "widget14", - "widget15", - "widget16", - "widget17", - "widget18", - "widget19", - "widget20", - "widget21", - "widget22", - "widget23", - "widget24", - "widget25", - "widget26", - "widget27", - "widget29", - "widget31", - "widget33", - "widget35", - "widget36", - "widget38", - "widget40", - "widget41", - "widget42", - "widget43", - "widget44", - "widget45", - "widget46", - "widget47", - "widget48", - "widget49", - "widget50", - "widget51", - "widget52", - "widget53", - "widget54", - "widget55", - "widget56", - "widget58", - "widget59", - "widget61", - "widget62", - "widget63", - "widget64", - "widget65", - "widget66", - "widget67", - "widget68", - "widget69", - "widget70", - "widget71", - "widget72", - "widget73", - "widget74", - "widget75", - "widget76", - "widget77", - "widget78", - "widget79", - "widget80", - "widget81", - "widget82", - "widget83", - "widget84", - "widget85", - "widget86", - "widget87", - "widget88", - "widget89", - "widget90", - "widget91", - "widget92", - "widget93", - "widget94" - ], "widgetsById": { "widget9": { "id": "widget9", diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/config/service/BoardsConfigHelperTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/config/service/BoardsConfigHelperTest.kt new file mode 100644 index 000000000..f03a4a5a2 --- /dev/null +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/config/service/BoardsConfigHelperTest.kt @@ -0,0 +1,106 @@ +package com.cognifide.cogboard.config.service + +import com.cognifide.cogboard.TestHelper.Companion.readConfigFromResource +import io.vertx.core.json.JsonObject +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class BoardsConfigHelperTest { + + private lateinit var config: JsonObject + private lateinit var board3: JsonObject + + @BeforeEach + fun beforeEach() { + config = readConfigFromResource("/com/cognifide/cogboard/config/config-with-2-boards.json") + board3 = readConfigFromResource("/com/cognifide/cogboard/config/board-3.json") + // initial check + assertEquals(2, config.getJsonObject("boards").getJsonArray("allBoards").size(), "initial size") + assertTrue(config.getJsonObject("boards").getJsonArray("allBoards").contains("board-1")) + assertTrue(config.getJsonObject("boards").getJsonArray("allBoards").contains("board-2")) + assertNotNull(config.getJsonObject("boards").getJsonObject("boardsById").getJsonObject("board-1")) + assertNotNull(config.getJsonObject("boards").getJsonObject("boardsById").getJsonObject("board-2")) + } + + @Test + fun `Expect board will be deleted from config`() { + val updatedConfig = BoardsConfigHelper.removeBoard(config, "board-2") + + assertEquals( + 1, + updatedConfig.getJsonObject("boards").getJsonArray("allBoards").size(), + "allBoards size" + ) + assertFalse( + updatedConfig.getJsonObject("boards").getJsonArray("allBoards").contains("board-2"), + "allBoards should not contain board-2 string" + ) + assertNull( + updatedConfig.getJsonObject("boards").getJsonObject("boardsById").getJsonObject("board-2"), + "boardsById should not contain board-2 object" + ) + } + + @Test + fun `Expect board will be added to config`() { + val updatedConfig = BoardsConfigHelper.updateBoard(config, board3) + + assertEquals( + 3, + updatedConfig.getJsonObject("boards").getJsonArray("allBoards").size(), + "allBoards size" + ) + assertTrue( + updatedConfig.getJsonObject("boards").getJsonArray("allBoards").contains("board-3"), + "allBoards should contain board-3 string" + ) + assertNotNull( + updatedConfig.getJsonObject("boards").getJsonObject("boardsById").getJsonObject("board-3"), + "boardsById should contain board-3 object" + ) + } + + @Test + fun `Expect board will be updated`() { + board3.put("id", "board-2") + val updatedConfig = BoardsConfigHelper.updateBoard(config, board3) + + assertEquals( + 2, + updatedConfig.getJsonObject("boards").getJsonArray("allBoards").size(), + "allBoards size" + ) + assertTrue( + updatedConfig.getJsonObject("boards").getJsonArray("allBoards").contains("board-2"), + "allBoards should contain board-2 string" + ) + assertNotNull( + updatedConfig.getJsonObject("boards").getJsonObject("boardsById").getJsonObject("board-2"), + "boardsById should contain board-2 object" + ) + assertEquals( + "TestBoard 3", + updatedConfig.getJsonObject("boards").getJsonObject("boardsById").getJsonObject("board-2") + .getString("title") + ) + } + + @Test + fun `Expect widgets will not be deleted after board props updated`() { + board3.put("id", "board-2") + val updatedConfig = BoardsConfigHelper.updateBoard(config, board3) + + assertEquals( + 1, + updatedConfig.getJsonObject("boards").getJsonObject("boardsById").getJsonObject("board-2") + .getJsonArray("widgets").size(), + "board-2 still contains widgets" + ) + } +} + diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/config/service/BoardsConfigServiceTest.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/config/service/BoardsConfigServiceTest.kt index 8836dc4bc..7dcba6715 100644 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/config/service/BoardsConfigServiceTest.kt +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/config/service/BoardsConfigServiceTest.kt @@ -13,7 +13,7 @@ internal class BoardsConfigServiceTest { private val boardPath = BoardsConfigServiceTest::class.java.getResource("/board").path private fun initService(configFilePath: String = "$boardPath/config-on-backend.json"): BoardsConfigService { - return BoardsConfigService(ContentRepository(boardPath), storage = boards(configFilePath)) + return BoardsConfigService(contentRepository = ContentRepository(boardPath), storage = boards(configFilePath)) } @Test diff --git a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/WidgetTestBase.kt b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/WidgetTestBase.kt index 1cbed7d04..3ff51757a 100644 --- a/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/WidgetTestBase.kt +++ b/cogboard-app/src/test/kotlin/com/cognifide/cogboard/widget/type/WidgetTestBase.kt @@ -46,7 +46,7 @@ abstract class WidgetTestBase { fun initService(): BoardsConfigService { val boardPath = JenkinsJobWidgetTest::class.java.getResource("/board").path val contentRepository = ContentRepository(boardPath) - return BoardsConfigService(contentRepository) + return BoardsConfigService(contentRepository = contentRepository) } fun captureWhatIsSent(eventBus: EventBus, captor: ArgumentCaptor): Pair { diff --git a/cogboard-app/src/test/resources/board/config-from-frontend.json b/cogboard-app/src/test/resources/board/config-from-frontend.json index 8aefe42e5..0f838e942 100644 --- a/cogboard-app/src/test/resources/board/config-from-frontend.json +++ b/cogboard-app/src/test/resources/board/config-from-frontend.json @@ -48,7 +48,6 @@ "isUpdating": false, "boardId": "board1" } - }, - "allWidgets": ["newWidget,widgetThatIsNotEdited"] + } } } diff --git a/cogboard-app/src/test/resources/board/config-on-backend.json b/cogboard-app/src/test/resources/board/config-on-backend.json index 3df4ece64..b254763ce 100644 --- a/cogboard-app/src/test/resources/board/config-on-backend.json +++ b/cogboard-app/src/test/resources/board/config-on-backend.json @@ -48,7 +48,6 @@ "isUpdating": false, "boardId": "board1" } - }, - "allWidgets": ["widgetThatWillBeReplaced,widgetThatIsNotEdited"] + } } } diff --git a/cogboard-app/src/test/resources/com/cognifide/cogboard/config/board-3.json b/cogboard-app/src/test/resources/com/cognifide/cogboard/config/board-3.json new file mode 100644 index 000000000..4fd16d752 --- /dev/null +++ b/cogboard-app/src/test/resources/com/cognifide/cogboard/config/board-3.json @@ -0,0 +1,9 @@ +{ + "autoSwitch": false, + "switchInterval": 360, + "id": "board-3", + "theme": "default", + "columns": 8, + "title": "TestBoard 3", + "type": "WidgetBoard" +} diff --git a/cogboard-app/src/test/resources/com/cognifide/cogboard/config/config-with-2-boards.json b/cogboard-app/src/test/resources/com/cognifide/cogboard/config/config-with-2-boards.json new file mode 100644 index 000000000..68499fd25 --- /dev/null +++ b/cogboard-app/src/test/resources/com/cognifide/cogboard/config/config-with-2-boards.json @@ -0,0 +1,69 @@ +{ + "boards": { + "boardsById": { + "board-1": { + "autoSwitch": false, + "switchInterval": 360, + "id": "board-1", + "theme": "default", + "widgets": ["widget1"], + "columns": 8, + "title": "TestBoard 1", + "type": "WidgetBoard" + }, + "board-2": { + "autoSwitch": false, + "switchInterval": 360, + "id": "board-2", + "theme": "default", + "widgets": ["widget2"], + "columns": 8, + "title": "TestBoard 2", + "type": "WidgetBoard" + } + }, + "allBoards": ["board-1", "board-2"] + }, + "widgets": { + "widgetsById": { + "widget1": { + "id": "widget1", + "title": "", + "config": { + "columns": 4, + "goNewLine": false, + "rows": 0.5 + }, + "type": "TextWidget", + "disabled": false, + "content": {}, + "status": "UNKNOWN", + "isUpdating": false, + "boardId": "board-21b6c72f-a4ac-4c62-adc4-8a2ec8c5a71d", + "text": "CogBoard", + "textSize": "h4", + "isVertical": false, + "expandContent": false + }, + "widget2": { + "id": "widget2", + "title": "", + "config": { + "columns": 4, + "goNewLine": false, + "rows": 0.5 + }, + "type": "TextWidget", + "disabled": false, + "content": {}, + "status": "UNKNOWN", + "isUpdating": false, + "boardId": "board-21b6c72f-a4ac-4c62-adc4-8a2ec8c5a71d", + "text": "CogBoard", + "textSize": "h4", + "isVertical": false, + "expandContent": false + } + } + } +} diff --git a/cogboard-webapp/src/actions/actionCreators.js b/cogboard-webapp/src/actions/actionCreators.js index eeb14828d..9d0cfcc88 100644 --- a/cogboard-webapp/src/actions/actionCreators.js +++ b/cogboard-webapp/src/actions/actionCreators.js @@ -79,14 +79,9 @@ export const updateWidget = data => ({ payload: data }); -export const addBoard = data => ({ +export const addBoard = newBoard => ({ type: ADD_BOARD, - payload: { - id: `board-${v4()}`, - theme: 'default', - widgets: [], - ...data - } + payload: newBoard }); export const deleteBoard = id => ({ diff --git a/cogboard-webapp/src/actions/helpers.js b/cogboard-webapp/src/actions/helpers.js index 6a370c092..30117dc84 100644 --- a/cogboard-webapp/src/actions/helpers.js +++ b/cogboard-webapp/src/actions/helpers.js @@ -1,27 +1,23 @@ import { dataChanged, setLogoutReasonMessage } from './actionCreators'; import { logout } from './thunks'; import { isAuthenticated, setToken } from '../utils/auth'; -import { mergeRight, assocPath } from 'ramda'; +import { assocPath } from 'ramda'; import { checkResponseStatus } from '../utils/fetch'; export const fetchData = ( url, { method = 'GET', data = {}, token = '' } = {} ) => { - const baseConfig = { method }; - - const configMap = { - GET: baseConfig, - DELETE: baseConfig, - POST: mergeRight(baseConfig, { - body: JSON.stringify(data), - headers: { - 'Content-Type': 'application/json' - } - }) - }; - - const config = method in configMap ? configMap[method] : configMap['POST']; + const config = + method === 'POST' + ? { + method, + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json' + } + } + : { method }; const authenticationConfig = token ? assocPath(['headers', 'Authorization'], token, config) @@ -69,11 +65,11 @@ const mapFormValuesToWidgetData = values => { export const createNewWidgetData = ({ values, - allWidgets, + allWidgetNames, currentBoardId }) => ({ boardId: currentBoardId, - id: createWidgetId(allWidgets), + id: createWidgetId(allWidgetNames), ...mapFormValuesToWidgetData(values) }); diff --git a/cogboard-webapp/src/actions/thunks.js b/cogboard-webapp/src/actions/thunks.js index 7224d78d0..b43dfd3c6 100644 --- a/cogboard-webapp/src/actions/thunks.js +++ b/cogboard-webapp/src/actions/thunks.js @@ -49,6 +49,7 @@ import { getGuestName } from '../utils/auth'; import { newVersionButtonsCreator } from '../components/NewVersionButtons/helpers'; +import { v4 } from 'uuid'; export const fetchInitialData = () => dispatch => { dispatch(requestData()); @@ -71,7 +72,6 @@ export const saveDataThunk = () => (dispatch, getState) => { const state = getState(); const { boards } = state; const widgets = { - allWidgets: state.widgets.allWidgets, widgetsById: state.widgets.widgetsById }; const data = { boards, widgets }; @@ -83,6 +83,16 @@ export const saveDataThunk = () => (dispatch, getState) => { ); }; +const boardApiCall = (board, action) => dispatch => { + const token = getToken(); + const url = `${URL.BOARD}/${board.id}`; + + return fetchData(url, { method: action, data: board, token }).then( + () => dispatch(saveDataSuccess()), + console.error + ); +}; + export const login = (credentials, loginAsGuest) => dispatch => { if (loginAsGuest) { const guestName = credentials.username; @@ -123,6 +133,7 @@ const deleteBoardWithWidgetsThunk = id => (dispatch, getState) => { const { currentBoard } = ui; dispatch(deleteBoard(id)); + dispatch(boardApiCall({ id: id }, 'DELETE')); const [firstBoardId] = getState().boards.allBoards; @@ -134,13 +145,34 @@ const deleteBoardWithWidgetsThunk = id => (dispatch, getState) => { dispatch(deleteMultipleWidgets(widgets)); }; +const addBoardThunk = data => dispatch => { + const newBoard = { + id: `board-${v4()}`, + theme: 'default', + widgets: [], + ...data + }; + + dispatch(addBoard(newBoard)); + dispatch(boardApiCall(newBoard, 'POST')); +}; + +const editBoardThunk = data => dispatch => { + dispatch(editBoard(data)); + dispatch(boardApiCall(data, 'POST')); +}; + const makeWidgetUpdaterThunk = ( beforeUpdateActionCreator, widgetDataCreator ) => data => (dispatch, getState) => { - const allWidgets = getState().widgets.allWidgets; + const allWidgetNames = []; + // eslint-disable-next-line array-callback-return + Object.entries(getState().widgets.widgetsById).map(item => { + allWidgetNames.push(item[0]); + }); const token = getToken(); - const widgetData = widgetDataCreator({ ...data, allWidgets }); + const widgetData = widgetDataCreator({ ...data, allWidgetNames }); const { id } = widgetData; const { generalData, serverData } = mapDataToState(widgetData); @@ -322,8 +354,8 @@ export const removeWidget = withAuthentication( ); export const reorderWidgets = withDataChanged(reorderWidgetsThunk); export const reorderBoard = withDataChanged(reorderBoards); -export const addNewBoard = withDataChanged(addBoard); -export const saveBoard = withDataChanged(editBoard); +export const addNewBoard = withDataChanged(addBoardThunk); +export const saveBoard = withDataChanged(editBoardThunk); export const deleteBoardWithWidgets = withDataChanged( deleteBoardWithWidgetsThunk ); diff --git a/cogboard-webapp/src/constants/index.js b/cogboard-webapp/src/constants/index.js index 277842657..fad3504a4 100644 --- a/cogboard-webapp/src/constants/index.js +++ b/cogboard-webapp/src/constants/index.js @@ -21,6 +21,7 @@ export const URL = { LOAD_INFO: '/api/version', LOAD_DATA: '/api/config', SAVE_DATA: '/api/config/save', + BOARD: '/api/board', UPDATE_WIDGET: '/api/widget/update', DELETE_WIDGET: '/api/widget/delete', LOGIN: '/api/login', diff --git a/cogboard-webapp/src/reducers/widgets/allWidgets.js b/cogboard-webapp/src/reducers/widgets/allWidgets.js deleted file mode 100644 index 3950a4093..000000000 --- a/cogboard-webapp/src/reducers/widgets/allWidgets.js +++ /dev/null @@ -1,51 +0,0 @@ -import { - RECEIVE_DATA, - ADD_WIDGET, - DELETE_WIDGET, - DELETE_MULTIPLE_WIDGETS -} from '../../actions/types'; - -const receiveData = (state, { payload }) => { - const { - widgets: { allWidgets } - } = payload; - - return [...state, ...allWidgets]; -}; - -const addWidget = (state, { payload }) => { - const { id } = payload; - - return [...state, id]; -}; - -const deleteWidget = (state, { payload }) => { - const { id } = payload; - - return state.filter(widgetId => widgetId !== id); -}; - -const deleteMultipleWidgets = (state, { payload }) => { - const { widgetIds } = payload; - - return state.filter(widgetId => !widgetIds.includes(widgetId)); -}; - -const allWidgets = (state = [], action) => { - const { type } = action; - - switch (type) { - case RECEIVE_DATA: - return receiveData(state, action); - case ADD_WIDGET: - return addWidget(state, action); - case DELETE_WIDGET: - return deleteWidget(state, action); - case DELETE_MULTIPLE_WIDGETS: - return deleteMultipleWidgets(state, action); - default: - return state; - } -}; - -export default allWidgets; diff --git a/cogboard-webapp/src/reducers/widgets/index.js b/cogboard-webapp/src/reducers/widgets/index.js index 32a5b14b7..06d145c87 100644 --- a/cogboard-webapp/src/reducers/widgets/index.js +++ b/cogboard-webapp/src/reducers/widgets/index.js @@ -1,12 +1,10 @@ import { combineReducers } from 'redux'; import widgetsById from './widgetsById'; -import allWidgets from './allWidgets'; import widgetTypes from './widgetTypes'; const widgets = combineReducers({ widgetsById, - allWidgets, widgetTypes }); diff --git a/functional/cypress-tests/cypress/fixtures/Dashboard.js b/functional/cypress-tests/cypress/fixtures/Dashboard.js index c02c0d636..274f53187 100644 --- a/functional/cypress-tests/cypress/fixtures/Dashboard.js +++ b/functional/cypress-tests/cypress/fixtures/Dashboard.js @@ -6,11 +6,6 @@ export const columnEdgeValues = ['3', '4', '20', '21']; export const switchIntervalEdgeValues = ['2', '3']; -export const dashboardNames = [ - dashboardNameGen(), - 'Long Dashboard Name Input. It has exactly 51 chars.' -]; - export const dashboardTypes = { widgets: 'Widget Board', iframe: 'Iframe Board' diff --git a/functional/cypress-tests/cypress/fixtures/reorderingConfig.json b/functional/cypress-tests/cypress/fixtures/reorderingConfig.json index 2e667f7ed..b90c52582 100644 --- a/functional/cypress-tests/cypress/fixtures/reorderingConfig.json +++ b/functional/cypress-tests/cypress/fixtures/reorderingConfig.json @@ -78,7 +78,6 @@ "isUpdating": false, "schedulePeriod": 120 } - }, - "allWidgets": ["widget_1", "widget_2", "widget_3"] + } } } diff --git a/functional/cypress-tests/cypress/integration/dashboards.js b/functional/cypress-tests/cypress/integration/dashboards.js index da27e76f2..825308645 100644 --- a/functional/cypress-tests/cypress/integration/dashboards.js +++ b/functional/cypress-tests/cypress/integration/dashboards.js @@ -1,10 +1,15 @@ import { dashboardNameGen, columnEdgeValues, - switchIntervalEdgeValues, - dashboardNames + switchIntervalEdgeValues } from '../fixtures/Dashboard'; -import { addIframeDashboard, addWidgetsDashboard } from '../support/dashboard'; +import { + addIframeDashboard, + addWidgetsDashboard, + deleteDashboard +} from '../support/dashboard'; + +let dashboardName; describe('Basic Dashboard CRUD', () => { const newTitle = dashboardNameGen('Edit'); @@ -12,46 +17,39 @@ describe('Basic Dashboard CRUD', () => { beforeEach(() => { cy.visit('/'); cy.login(); + dashboardName = dashboardNameGen(); }); - it('Logged user can add new widgets dashboard', () => { - addWidgetsDashboard(); - }); - - it('Logged user can choose widgets dashboard', () => { - addWidgetsDashboard().canBeSelected(); + afterEach(() => { + deleteDashboard(dashboardName); }); - it('Logged user can add new iframe dashboard', () => { - addIframeDashboard(); + it('Logged user can add Dashboard for Widgets', () => { + addWidgetsDashboard(dashboardName).canBeSelected(); }); - it('Logged user can choose iframe dashboard', () => { - addIframeDashboard() + it('Logged user can add Dashboard for iFrame', () => { + addIframeDashboard(dashboardName) .canBeSelected() .assertIframeExists(); }); it('Anonymous user can choose dashboard', () => { - const board = addWidgetsDashboard(); + const board = addWidgetsDashboard(dashboardName); cy.closeDrawer(); cy.logout(); cy.get('[data-cy="navbar-show-drawer-button"]').click(); board.canBeSelected(); + cy.login(); }); it('Logged user can edit dashboard', () => { - addWidgetsDashboard() + addWidgetsDashboard(dashboardName) .openEditDialog() .setTitle(newTitle) .confirmChanges() .assertCardTitle(newTitle); - }); - - it('Logged user can remove dashboard', () => { - addWidgetsDashboard() - .delete() - .assertNotVisible(); + dashboardName = newTitle; }); }); @@ -59,32 +57,27 @@ describe('Dashboard Input Validation', () => { beforeEach(() => { cy.visit('/'); cy.login(); + dashboardName = dashboardNameGen(); }); - it('Name input do not accept empty strings', () => { + it('Board title cannot be empty strings', () => { addWidgetsDashboard(' ') .expectConfigToBeInvalid() .assertErrorMessageVisible('This field is required'); }); - dashboardNames.forEach(value => { - it(`Name input accepts strings not exceeding 50 characters. Tested value: ${value.length}`, () => { - const board = addWidgetsDashboard(value); - if (value.length > 50) { - board - .expectConfigToBeInvalid() - .assertErrorMessageVisible( - 'Title length must be less or equal to 50.' - ); - } else { - board.expectConfigToBeValid(); - } - }); + it('Board title cannot be more than 50 chars', () => { + dashboardName = 'Long Dashboard Name Input. It has exactly 51 chars.'; + addWidgetsDashboard(dashboardName) + .expectConfigToBeInvalid() + .assertErrorMessageVisible('Title length must be less or equal to 50.'); }); columnEdgeValues.forEach(value => { - it(`Columns input accepts only values from 4 to 20. Tested value: ${value}`, () => { - const board = addWidgetsDashboard(undefined, value); + it(`Board Columns ${value} is ${ + value < 4 || value > 20 ? 'IN' : '' + }VALID`, () => { + const board = addWidgetsDashboard(dashboardName, value); if (value < 4) { board .expectConfigToBeInvalid() @@ -95,13 +88,16 @@ describe('Dashboard Input Validation', () => { .assertErrorMessageVisible('Columns number cannot be more than 20.'); } else { board.expectConfigToBeValid(); + deleteDashboard(dashboardName); } }); }); switchIntervalEdgeValues.forEach(value => { - it(`Switch interval input accepts only values grater than 2. Tested value: ${value}`, () => { - const board = addWidgetsDashboard(undefined, undefined, value); + it(`Board Switch Interval ${value} is ${ + value < 3 ? 'IN' : '' + }VALID`, () => { + const board = addWidgetsDashboard(dashboardName, undefined, value); if (value < 3) { board .expectConfigToBeInvalid() @@ -110,6 +106,7 @@ describe('Dashboard Input Validation', () => { ); } else { board.expectConfigToBeValid(); + deleteDashboard(dashboardName); } }); }); @@ -119,55 +116,57 @@ describe('Dashboard switcher', () => { beforeEach(() => { cy.visit('/'); cy.login(); + dashboardName = dashboardNameGen(); }); it('Manual switching works', () => { - let prevDashboardName, nextDashboardName; - - addWidgetsDashboard() - .select() - .assertTitle(); - - prevDashboardName = cy - .get('[data-cy="previous-board-button"]') - .invoke('attr', 'title') - .then(title => { - prevDashboardName = title.toString(); - cy.get('[data-cy="previous-board-button"]').click(); - cy.contains( - '[data-cy="navbar-title-header"]', - `${prevDashboardName}` - ).should('is.visible'); - }) - .toString(); - cy.get('[data-cy="next-board-button"]') - .invoke('attr', 'title') - .then(title => { - nextDashboardName = title.toString(); - cy.get('[data-cy="next-board-button"]').click(); - cy.contains( - '[data-cy="navbar-title-header"]', - `${nextDashboardName}` - ).should('is.visible'); - }); + const prevDashboardName = 'PREV_' + dashboardName; + const middleDashboardName = 'MIDDLE_' + dashboardName; + const nextDashboardName = 'NEXT_' + dashboardName; + + addWidgetsDashboard(prevDashboardName).select(); + const middle = addWidgetsDashboard(middleDashboardName).select(); + addWidgetsDashboard(nextDashboardName).select(); + + cy.get('[data-cy="navbar-show-drawer-button"]').click(); + middle.select(); + + cy.get('[data-cy="previous-board-button"]').click(); + cy.contains( + '[data-cy="navbar-title-header"]', + `${prevDashboardName}` + ).should('is.visible'); + + cy.get('[data-cy="navbar-show-drawer-button"]').click(); + middle.select(); + + cy.get('[data-cy="next-board-button"]').click(); + cy.contains( + '[data-cy="navbar-title-header"]', + `${nextDashboardName}` + ).should('is.visible'); + + deleteDashboard(prevDashboardName); + deleteDashboard(middleDashboardName); + deleteDashboard(nextDashboardName); }); it('Automatic switching works', () => { - let nextDashboardName; - - addWidgetsDashboard(undefined, undefined, '3') - .select() - .assertTitle(); - - cy.get('[data-cy="next-board-button"]') - .invoke('attr', 'title') - .then(title => { - nextDashboardName = title.toString(); - cy.get('[data-cy="auto-switch-board-button"]').click(); - cy.contains( - '[data-cy="navbar-title-header"]', - `${nextDashboardName}` - ).should('is.visible'); - }); + const nextDashboardName = 'NEXT_' + dashboardName; + + const first = addWidgetsDashboard(dashboardName, undefined, '3').select(); + addWidgetsDashboard(nextDashboardName).select(); + + cy.get('[data-cy="navbar-show-drawer-button"]').click(); + first.select(); + + cy.get('[data-cy="auto-switch-board-button"]').click(); + cy.contains( + '[data-cy="navbar-title-header"]', + `${nextDashboardName}` + ).should('is.visible'); + + deleteDashboard(dashboardName); + deleteDashboard(nextDashboardName); }); }); diff --git a/functional/cypress-tests/cypress/integration/reordering_widgets.js b/functional/cypress-tests/cypress/integration/reordering_widgets.js index a8581a7ee..8cccf82c1 100644 --- a/functional/cypress-tests/cypress/integration/reordering_widgets.js +++ b/functional/cypress-tests/cypress/integration/reordering_widgets.js @@ -1,21 +1,20 @@ import { dashboardNameGen } from '../fixtures/Dashboard'; import { createWidget } from '../support/widget'; import Widgets from '../fixtures/Widgets'; -import { addWidgetsDashboard } from '../support/dashboard'; +import { addWidgetsDashboard, deleteDashboard } from '../support/dashboard'; describe('Reordering widgets', () => { const firstWidgetsTitle = 'First Widget'; const secondWidgetsTitle = 'Second Widget'; const thirdWidgetsTitle = 'Third Widget'; - let first, second, third, dashboard; + let first, second, third, dashboardName; before(() => { cy.visit('/'); cy.login(); - dashboard = addWidgetsDashboard( - dashboardNameGen('ReorderWidgetsTest') - ).select(); + dashboardName = dashboardNameGen('ReorderWidgetsTest') + addWidgetsDashboard(dashboardName).select(); first = createCheckboxWidget(firstWidgetsTitle); second = createCheckboxWidget(secondWidgetsTitle); third = createCheckboxWidget(thirdWidgetsTitle); @@ -54,7 +53,7 @@ describe('Reordering widgets', () => { cy.login(); cy.get('[data-cy="navbar-show-drawer-button"]').click(); - dashboard.delete(); + deleteDashboard(dashboardName) }); function createCheckboxWidget(title) { diff --git a/functional/cypress-tests/cypress/integration/saving.js b/functional/cypress-tests/cypress/integration/saving.js index 4563233c4..3e4612557 100644 --- a/functional/cypress-tests/cypress/integration/saving.js +++ b/functional/cypress-tests/cypress/integration/saving.js @@ -10,18 +10,9 @@ describe('Dashboard Persistence', () => { cy.login(); }); - it('Not saved dashboard is not displayed after refresh', () => { - const name = dashboardNameGen(); - cy.addDashboard(name); - cy.visit('/'); - cy.get('[data-cy="navbar-show-drawer-button"]').click(); - cy.contains('[data-cy="board-card"]', name).should('not.visible'); - }); - it('Saved dashboard is displayed after refresh', () => { const name = dashboardNameGen(); cy.addDashboard(name); - cy.saveState(); cy.visit('/'); cy.get('[data-cy="navbar-show-drawer-button"]').click(); cy.contains('[data-cy="board-card"]', name) @@ -32,7 +23,6 @@ describe('Dashboard Persistence', () => { .scrollIntoView() .click(); cy.get('[data-cy="confirmation-dialog-ok"]').click(); - cy.saveState(); }); }); diff --git a/functional/cypress-tests/cypress/support/dashboard.js b/functional/cypress-tests/cypress/support/dashboard.js index d46f039bc..173c2740c 100644 --- a/functional/cypress-tests/cypress/support/dashboard.js +++ b/functional/cypress-tests/cypress/support/dashboard.js @@ -95,3 +95,15 @@ export function addWidgetsDashboard(name, columns, switchInterval) { export function addIframeDashboard(name, columns, switchInterval) { return new Dashboard(name, dashboardTypes.iframe, columns, switchInterval); } + +export function deleteDashboard(name) { + cy.get('[data-cy="navbar-show-drawer-button"]').then(el => { + return el.click(); + }); + cy.contains('[data-cy="board-card"]', name) + .find('[data-cy="board-card-delete-button"]') + .scrollIntoView() + .click(); + cy.get('[data-cy="confirmation-dialog-ok"]').click(); + return this; +} diff --git a/functional/cypress-tests/cypress/support/helpers.js b/functional/cypress-tests/cypress/support/helpers.js index cdf0e70c8..b463510ec 100644 --- a/functional/cypress-tests/cypress/support/helpers.js +++ b/functional/cypress-tests/cypress/support/helpers.js @@ -1,15 +1,11 @@ export const renewConfig = (config, newConfig) => { const { - boards: { allBoards }, - widgets: { allWidgets } + boards: { allBoards } } = config; const filteredBoards = allBoards.filter( id => !newConfig.boards.allBoards.includes(id) ); - const filteredWidgets = allWidgets.filter( - id => !newConfig.widgets.allWidgets.includes(id) - ); const result = { ...config, @@ -26,8 +22,7 @@ export const renewConfig = (config, newConfig) => { widgetsById: { ...config.widgets.widgetsById, ...newConfig.widgets.widgetsById - }, - allWidgets: [...filteredWidgets, ...newConfig.widgets.allWidgets] + } } }; return result; diff --git a/knotx/conf/openapi.yaml b/knotx/conf/openapi.yaml index 4d386e24a..f04d96eb8 100644 --- a/knotx/conf/openapi.yaml +++ b/knotx/conf/openapi.yaml @@ -47,6 +47,28 @@ paths: responses: default: description: Boards Save Handler + /api/board/{id}: + parameters: + - name: id + in: path + description: board add, update, delete + required: true + schema: + type: string + post: + operationId: boards-update-handler + security: + - cogboardAuth: [] + responses: + default: + description: Board Add/Update Handler + delete: + operationId: boards-delete-handler + security: + - cogboardAuth: [] + responses: + default: + description: Board Delete Handler /api/login: post: operationId: login-handler diff --git a/knotx/conf/routes/operations.conf b/knotx/conf/routes/operations.conf index 4c1adc495..aff63a164 100644 --- a/knotx/conf/routes/operations.conf +++ b/knotx/conf/routes/operations.conf @@ -153,6 +153,20 @@ routingOperations = ${routingOperations} [ } { operationId = boards-save-handler + handlers = [ + { + name = config-api-handler + config { + address = cogboard.config.boards + method = save + payload = body + refresh = false + } + } + ] + } + { + operationId = boards-update-handler handlers = [ { name = config-api-handler @@ -165,6 +179,20 @@ routingOperations = ${routingOperations} [ } ] } + { + operationId = boards-delete-handler + handlers = [ + { + name = config-api-handler + config { + address = cogboard.config.boards + method = delete + payload = params + refresh = false + } + } + ] + } { operationId = boards-get-handler handlers = [