diff --git a/material/rest-api-ui-ktor-quiz-collector/build.gradle.kts b/material/rest-api-ui-ktor-quiz-collector/build.gradle.kts index 80972bb9..3a397a79 100644 --- a/material/rest-api-ui-ktor-quiz-collector/build.gradle.kts +++ b/material/rest-api-ui-ktor-quiz-collector/build.gradle.kts @@ -5,8 +5,8 @@ plugins { id("org.jetbrains.kotlin.plugin.serialization") version "2.0.20" } -group = "example.com" -version = "0.0.1" +group = "com.worldline.training" +version = "1.0.0" application { mainClass.set("io.ktor.server.cio.EngineMain") diff --git a/material/rest-api-ui-ktor-quiz-collector/quiz-collector-bruno/reset.bru b/material/rest-api-ui-ktor-quiz-collector/quiz-collector-bruno/reset.bru new file mode 100644 index 00000000..51b73b8f --- /dev/null +++ b/material/rest-api-ui-ktor-quiz-collector/quiz-collector-bruno/reset.bru @@ -0,0 +1,11 @@ +meta { + name: reset + type: http + seq: 7 +} + +get { + url: http://localhost:8080/reset + body: none + auth: none +} diff --git a/material/rest-api-ui-ktor-quiz-collector/quiz-collector-bruno/respond 1.bru b/material/rest-api-ui-ktor-quiz-collector/quiz-collector-bruno/respond 1.bru index b42005a2..d2a3284f 100644 --- a/material/rest-api-ui-ktor-quiz-collector/quiz-collector-bruno/respond 1.bru +++ b/material/rest-api-ui-ktor-quiz-collector/quiz-collector-bruno/respond 1.bru @@ -12,15 +12,149 @@ post { body:json { { - "responses": [ - { - "question": "What is the primary goal of Kotlin Multiplatform?", - "answer": "Muzukashi" - }, - { - "question": "How does Kotlin Multiplatform facilitate code sharing between platforms?", - "answer": "By sharing business logic and adapting UI" + "responses": [ + { + "question": "What is the primary goal of Kotlin Multiplatform?", + "answer": "To build only Android applications", + "answerId": 1, + "id": 1, + "correctAnswerId": 4 + }, + { + "question": "How does Kotlin Multiplatform facilitate code sharing between platforms?", + "answer": "By writing separate code for each platform", + "answerId": 1, + "id": 2, + "correctAnswerId": 3 + }, + { + "question": "Which platforms does Kotlin Multiplatform support?", + "answer": "Only web applications", + "answerId": 1, + "id": 3, + "correctAnswerId": 3 + }, + { + "question": "What is a common use case for Kotlin Multiplatform?", + "answer": "Building a desktop-only application", + "answerId": 1, + "id": 4, + "correctAnswerId": 4 + }, + { + "question": "Which naming of KMP is deprecated?", + "answer": "Kodee multiplatform", + "answerId": 1, + "id": 5, + "correctAnswerId": 4 + }, + { + "question": "How does Kotlin Multiplatform handle platform-specific implementations?", + "answer": "By automatically translating code", + "answerId": 1, + "id": 6, + "correctAnswerId": 4 + }, + { + "question": "At which Google I/O, Google announced first-class support for Kotlin on Android?", + "answer": "2017", + "answerId": 1, + "id": 7, + "correctAnswerId": 1 + }, + { + "question": "What is the name of the Kotlin mascot?", + "answer": "Kotee", + "answerId": 1, + "id": 8, + "correctAnswerId": 4 + }, + { + "question": "The international yearly Kotlin conference is called...", + "answer": "KotlinKonf", + "answerId": 1, + "id": 9, + "correctAnswerId": 3 + }, + { + "question": "Where will be located the next international yearly Kotlin conference?", + "answer": "Lille, France", + "answerId": 1, + "id": 10, + "correctAnswerId": 3 + }, + { + "question": "What is the primary goal of Kotlin Multiplatform?", + "answer": "To build only Android applications", + "answerId": 1, + "id": 1, + "correctAnswerId": 4 + }, + { + "question": "How does Kotlin Multiplatform facilitate code sharing between platforms?", + "answer": "By writing separate code for each platform", + "answerId": 1, + "id": 2, + "correctAnswerId": 3 + }, + { + "question": "Which platforms does Kotlin Multiplatform support?", + "answer": "Only web applications", + "answerId": 1, + "id": 3, + "correctAnswerId": 3 + }, + { + "question": "What is a common use case for Kotlin Multiplatform?", + "answer": "Building a desktop-only application", + "answerId": 1, + "id": 4, + "correctAnswerId": 4 + }, + { + "question": "Which naming of KMP is deprecated?", + "answer": "Kodee multiplatform", + "answerId": 1, + "id": 5, + "correctAnswerId": 4 + }, + { + "question": "How does Kotlin Multiplatform handle platform-specific implementations?", + "answer": "By automatically translating code", + "answerId": 1, + "id": 6, + "correctAnswerId": 4 + }, + { + "question": "At which Google I/O, Google announced first-class support for Kotlin on Android?", + "answer": "2017", + "answerId": 1, + "id": 7, + "correctAnswerId": 1 + }, + { + "question": "What is the name of the Kotlin mascot?", + "answer": "Kotee", + "answerId": 1, + "id": 8, + "correctAnswerId": 4 + }, + { + "question": "The international yearly Kotlin conference is called...", + "answer": "KotlinKonf", + "answerId": 1, + "id": 9, + "correctAnswerId": 3 + }, + { + "question": "Where will be located the next international yearly Kotlin conference?", + "answer": "Lille, France", + "answerId": 1, + "id": 10, + "correctAnswerId": 3 + } + ], + "score": 1, + "nickname": "user-424" } - ] - } } diff --git a/material/rest-api-ui-ktor-quiz-collector/quiz-collector-bruno/respond 2.bru b/material/rest-api-ui-ktor-quiz-collector/quiz-collector-bruno/respond 2.bru index b1c0ad3d..4e2ecbb0 100644 --- a/material/rest-api-ui-ktor-quiz-collector/quiz-collector-bruno/respond 2.bru +++ b/material/rest-api-ui-ktor-quiz-collector/quiz-collector-bruno/respond 2.bru @@ -12,47 +12,149 @@ post { body:json { { - "responses": [ - { - "question": "What is the primary goal of Kotlin Multiplatform?", - "answer": "To share code between multiple platforms" - }, - { - "question": "How does Kotlin Multiplatform facilitate code sharing between platforms?", - "answer": "By sharing business logic and adapting UI" - }, - { - "question": "Which platforms does Kotlin Multiplatform support?", - "answer": "Android, iOS, and web" - }, - { - "question": "What is a common use case for Kotlin Multiplatform?", - "answer": "Developing a cross-platform app" - }, - { - "question": "What is a shared code module in Kotlin Multiplatform called?", - "answer": "Shared module" - }, - { - "question": "How does Kotlin Multiplatform handle platform-specific implementations?", - "answer": "Through expect and actual declarations" - }, - { - "question": "What languages can be interoperable with Kotlin Multiplatform?", - "answer": "Java, JavaScript, Swift" - }, - { - "question": "What tooling supports Kotlin Multiplatform development?", - "answer": "IntelliJ IDEA, Android Studio" - }, - { - "question": "What is the benefit of using Kotlin Multiplatform for mobile development?", - "answer": "Code reuse and sharing" - }, - { - "question": "How does Kotlin Multiplatform differ from Kotlin Native and Kotlin/JS?", - "answer": "Kotlin Multiplatform allows sharing code between different platforms using common modules." + "responses": [ + { + "question": "What is the primary goal of Kotlin Multiplatform?", + "answer": "To build only Android applications", + "answerId": 1, + "id": 1, + "correctAnswerId": 4 + }, + { + "question": "How does Kotlin Multiplatform facilitate code sharing between platforms?", + "answer": "By writing separate code for each platform", + "answerId": 1, + "id": 2, + "correctAnswerId": 3 + }, + { + "question": "Which platforms does Kotlin Multiplatform support?", + "answer": "Only web applications", + "answerId": 1, + "id": 3, + "correctAnswerId": 3 + }, + { + "question": "What is a common use case for Kotlin Multiplatform?", + "answer": "Building a desktop-only application", + "answerId": 1, + "id": 4, + "correctAnswerId": 4 + }, + { + "question": "Which naming of KMP is deprecated?", + "answer": "Kodee multiplatform", + "answerId": 1, + "id": 5, + "correctAnswerId": 4 + }, + { + "question": "How does Kotlin Multiplatform handle platform-specific implementations?", + "answer": "By automatically translating code", + "answerId": 1, + "id": 6, + "correctAnswerId": 4 + }, + { + "question": "At which Google I/O, Google announced first-class support for Kotlin on Android?", + "answer": "2017", + "answerId": 1, + "id": 7, + "correctAnswerId": 1 + }, + { + "question": "What is the name of the Kotlin mascot?", + "answer": "Kotee", + "answerId": 1, + "id": 8, + "correctAnswerId": 4 + }, + { + "question": "The international yearly Kotlin conference is called...", + "answer": "KotlinKonf", + "answerId": 1, + "id": 9, + "correctAnswerId": 3 + }, + { + "question": "Where will be located the next international yearly Kotlin conference?", + "answer": "Lille, France", + "answerId": 1, + "id": 10, + "correctAnswerId": 3 + }, + { + "question": "What is the primary goal of Kotlin Multiplatform?", + "answer": "To build only Android applications", + "answerId": 1, + "id": 1, + "correctAnswerId": 4 + }, + { + "question": "How does Kotlin Multiplatform facilitate code sharing between platforms?", + "answer": "By writing separate code for each platform", + "answerId": 1, + "id": 2, + "correctAnswerId": 3 + }, + { + "question": "Which platforms does Kotlin Multiplatform support?", + "answer": "Only web applications", + "answerId": 1, + "id": 3, + "correctAnswerId": 3 + }, + { + "question": "What is a common use case for Kotlin Multiplatform?", + "answer": "Building a desktop-only application", + "answerId": 1, + "id": 4, + "correctAnswerId": 4 + }, + { + "question": "Which naming of KMP is deprecated?", + "answer": "Kodee multiplatform", + "answerId": 1, + "id": 5, + "correctAnswerId": 4 + }, + { + "question": "How does Kotlin Multiplatform handle platform-specific implementations?", + "answer": "By automatically translating code", + "answerId": 1, + "id": 6, + "correctAnswerId": 4 + }, + { + "question": "At which Google I/O, Google announced first-class support for Kotlin on Android?", + "answer": "2017", + "answerId": 1, + "id": 7, + "correctAnswerId": 1 + }, + { + "question": "What is the name of the Kotlin mascot?", + "answer": "Kotee", + "answerId": 1, + "id": 8, + "correctAnswerId": 4 + }, + { + "question": "The international yearly Kotlin conference is called...", + "answer": "KotlinKonf", + "answerId": 1, + "id": 9, + "correctAnswerId": 3 + }, + { + "question": "Where will be located the next international yearly Kotlin conference?", + "answer": "Lille, France", + "answerId": 1, + "id": 10, + "correctAnswerId": 3 + } + ], + "score": 1, + "nickname": "user-100" } - ] - } } diff --git a/material/rest-api-ui-ktor-quiz-collector/settings.gradle.kts b/material/rest-api-ui-ktor-quiz-collector/settings.gradle.kts index fb145df6..3a13f879 100644 --- a/material/rest-api-ui-ktor-quiz-collector/settings.gradle.kts +++ b/material/rest-api-ui-ktor-quiz-collector/settings.gradle.kts @@ -1 +1 @@ -rootProject.name = "com.worldline.training.quiz-collector" +rootProject.name = "com.worldline.training.ktor_quiz_collector" diff --git a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/Application.kt b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/Application.kt new file mode 100644 index 00000000..ff3d200a --- /dev/null +++ b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/Application.kt @@ -0,0 +1,26 @@ +package com.worldline.training.ktor_quiz_collector + +import com.worldline.training.ktor_quiz_collector.plugins.configureHTTP +import com.worldline.training.ktor_quiz_collector.plugins.configureRouting +import com.worldline.training.ktor_quiz_collector.plugins.configureSerialization +import com.worldline.training.ktor_quiz_collector.plugins.configureTemplating +import com.worldline.training.ktor_quiz_collector.routes.configureMiscRoutes +import com.worldline.training.ktor_quiz_collector.routes.configureQuizCollector +import com.worldline.training.ktor_quiz_collector.utils.addSampleValues +import io.ktor.server.application.* + +fun main(args: Array) { + io.ktor.server.cio.EngineMain.main(args) +} + +fun Application.module() { +// (1..10).forEach { _ -> +// addSampleValues() +// } + configureHTTP() + configureTemplating() + configureSerialization() + configureRouting() + configureMiscRoutes() + configureQuizCollector() +} diff --git a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/plugins/HTTP.kt b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/plugins/HTTP.kt similarity index 92% rename from material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/plugins/HTTP.kt rename to material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/plugins/HTTP.kt index ee847ca4..3bb1152e 100644 --- a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/plugins/HTTP.kt +++ b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/plugins/HTTP.kt @@ -1,4 +1,4 @@ -package example.com.plugins +package com.worldline.training.ktor_quiz_collector.plugins import io.ktor.http.* import io.ktor.server.application.* diff --git a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/plugins/Routing.kt b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/plugins/Routing.kt similarity index 93% rename from material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/plugins/Routing.kt rename to material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/plugins/Routing.kt index 3a5b552f..225f2b2c 100644 --- a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/plugins/Routing.kt +++ b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/plugins/Routing.kt @@ -1,4 +1,4 @@ -package example.com.plugins +package com.worldline.training.ktor_quiz_collector.plugins import io.github.smiley4.ktorswaggerui.SwaggerUI import io.ktor.http.* diff --git a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/plugins/Serialization.kt b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/plugins/Serialization.kt similarity index 75% rename from material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/plugins/Serialization.kt rename to material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/plugins/Serialization.kt index 51d19f54..2b11990b 100644 --- a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/plugins/Serialization.kt +++ b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/plugins/Serialization.kt @@ -1,4 +1,4 @@ -package example.com.plugins +package com.worldline.training.ktor_quiz_collector.plugins import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* @@ -12,7 +12,7 @@ fun Application.configureSerialization() { } routing { get("/json/kotlinx-serialization") { - call.respond(mapOf("hello" to "world")) - } + call.respond(mapOf("hello" to "world")) + } } } diff --git a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/plugins/Templating.kt b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/plugins/Templating.kt similarity index 87% rename from material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/plugins/Templating.kt rename to material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/plugins/Templating.kt index f11fdc1b..e9b221e1 100644 --- a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/plugins/Templating.kt +++ b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/plugins/Templating.kt @@ -1,4 +1,4 @@ -package example.com.plugins +package com.worldline.training.ktor_quiz_collector.plugins import io.ktor.http.* import io.ktor.server.application.* @@ -6,6 +6,7 @@ import io.ktor.server.html.* import io.ktor.server.response.* import io.ktor.server.routing.* import kotlinx.css.* +import kotlinx.css.properties.boxShadow import kotlinx.html.* fun Application.configureTemplating() { @@ -33,7 +34,7 @@ fun Application.configureTemplating() { } } } - + get("/html-css-dsl") { call.respondHtml { head { @@ -50,5 +51,5 @@ fun Application.configureTemplating() { } suspend inline fun ApplicationCall.respondCss(builder: CSSBuilder.() -> Unit) { - this.respondText(CSSBuilder().apply(builder).toString(), ContentType.Text.CSS) + this.respondText(CSSBuilder().apply(builder).toString(), ContentType.Text.CSS) } diff --git a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/routes/MiscRoutes.kt b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/routes/MiscRoutes.kt new file mode 100644 index 00000000..b5a51982 --- /dev/null +++ b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/routes/MiscRoutes.kt @@ -0,0 +1,79 @@ +package com.worldline.training.ktor_quiz_collector.routes + +import com.worldline.training.ktor_quiz_collector.plugins.respondCss +import com.worldline.training.ktor_quiz_collector.utils.getCorrectStats +import com.worldline.training.ktor_quiz_collector.utils.quizResponses +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.http.content.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import kotlinx.css.* +import kotlinx.css.properties.boxShadow + +fun Application.configureMiscRoutes() { + routing { + staticResources("/static", "static") + + get("/styles-graph.css") { + call.respondCss { + body { + backgroundColor = Color.lightCyan + margin(0.px) + padding(0.px) + } + h1 { + textAlign = TextAlign.center + backgroundColor = Color.lightGreen + marginTop = 0.px + padding(1.5.rem) + } + p { + textAlign = TextAlign.center + } + rule("div") { + display = Display.flex + flexWrap = FlexWrap.wrap + justifyContent = JustifyContent.center + gap = Gap("1rem") + } + svg { + border = "1px solid black" + boxShadow(Color.black, 2.px, 2.px, 2.px) + } + } + } + + get("/favicon.ico") { + val image = call.resolveResource("static/favicon.ico") + if (image != null) { + call.respond(image) + } else { + call.respond(HttpStatusCode.NotFound) + } + } + + get("/responses") { + call.respond(quizResponses.map { quizResponse -> + quizResponse.responses.fold(mutableMapOf()) { acc, element -> + acc[element.question] = element.answer + acc + } + }) + } + + get("/correct-stats") { + call.respond(getCorrectStats()) + } + + get("/table2") { + call.respond(quizResponses) + } + + get("/table") { + val result = quizResponses.flatMap { it.responses }.groupBy { it.question } + .mapValues { it.value.map { value -> value.answer } } + call.respond(result) + } + } +} \ No newline at end of file diff --git a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/routes/QuizCollectorRoutes.kt b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/routes/QuizCollectorRoutes.kt new file mode 100644 index 00000000..0162000f --- /dev/null +++ b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/routes/QuizCollectorRoutes.kt @@ -0,0 +1,100 @@ +package com.worldline.training.ktor_quiz_collector.routes + +import com.worldline.training.ktor_quiz_collector.utils.* +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.html.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import kotlinx.html.* +import org.jetbrains.kotlinx.dataframe.api.dataFrameOf +import org.jetbrains.kotlinx.kandy.dsl.plot +import org.jetbrains.kotlinx.kandy.letsplot.export.toHTML +import org.jetbrains.kotlinx.kandy.letsplot.export.toSVG +import org.jetbrains.kotlinx.kandy.letsplot.layers.bars + + +fun Application.configureQuizCollector() { + routing { + post("/respond") { + val quizResponse = call.receive() + quizResponses.add(quizResponse) + call.respond(CollectResponse(quizResponse.score)) + } + + get("/raw-responses") { + call.respond(quizResponses) + } + + get("/reset") { + quizResponses.clear() + call.respond(HttpStatusCode.OK) + } + + get("/parts/graphs") { + call.respondHtml { + body { + div { + unsafe { +generateCorrectQuestionsPlot().toSVG() } + unsafe { +generateLeaderBoardPlot().toSVG() } + } + } + } + } + + get("/") { + call.respondHtml { + head { + title = "Quiz Collector" + link(rel = "stylesheet", href = "/styles-graph.css", type = "text/css") + script { + src = "/static/auto-reload.js" + } + } + body { + h1 { +"Ktor + Kandy Quiz Collector 📊" } + p { + +"Pages are generated on the server side with Kotlinx.HTML and Kotlinx.CSS" + } + p { + +"The plots are generated by Kandy and displayed as SVG" + } + div { + id = "graphs" + unsafe { +generateCorrectQuestionsPlot().toSVG() } + unsafe { +generateLeaderBoardPlot().toSVG() } + } + } + } + } + + get("/no-polling") { + call.respondHtml { + head { + title = "Quiz Collector" + link(rel = "stylesheet", href = "/styles-graph.css", type = "text/css") + } + body { + h1 { +"Ktor + Kandy Quiz Collector 📊" } + div { + unsafe { +generateCorrectQuestionsPlot().toSVG() } + unsafe { +generateLeaderBoardPlot().toSVG() } + } + } + } + } + get("/ui/correct") { + val correctStats = getCorrectStats() + val statsDataFrame = dataFrameOf("question" to correctStats.map { it.question }, + "correct" to correctStats.map { it.correct }) + val html = statsDataFrame.plot { + bars { + y("question") + x("correct") + } + }.toHTML(false) + call.respondText(html, ContentType.Text.Html) + } + } +} \ No newline at end of file diff --git a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/utils/DataUtils.kt b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/utils/DataUtils.kt new file mode 100644 index 00000000..f8750eef --- /dev/null +++ b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/utils/DataUtils.kt @@ -0,0 +1,93 @@ +package com.worldline.training.ktor_quiz_collector.utils + +import kotlinx.serialization.Serializable +import org.jetbrains.kotlinx.dataframe.api.dataFrameOf +import org.jetbrains.kotlinx.dataframe.api.sortByDesc +import org.jetbrains.kotlinx.dataframe.api.toDataFrame +import org.jetbrains.kotlinx.kandy.dsl.continuous +import org.jetbrains.kotlinx.kandy.dsl.plot +import org.jetbrains.kotlinx.kandy.ir.Plot +import org.jetbrains.kotlinx.kandy.letsplot.feature.layout +import org.jetbrains.kotlinx.kandy.letsplot.layers.bars +import org.jetbrains.kotlinx.kandy.letsplot.layers.barsH +import org.jetbrains.kotlinx.kandy.letsplot.scales.categoricalColorHue +import org.jetbrains.kotlinx.kandy.letsplot.x +import org.jetbrains.kotlinx.kandy.letsplot.y +import org.jetbrains.kotlinx.kandy.util.color.Color + +@Serializable +data class QuestionStats(val question: String, val correct: Int) + +fun getCorrectStats() = + com.worldline.training.ktor_quiz_collector.utils.quizResponses.flatMap { it.responses }.groupBy { it.question } + .mapValues { it.value.count { qr -> qr.correctAnswerId == qr.answerId } } + .map { + com.worldline.training.ktor_quiz_collector.utils.QuestionStats( + it.key.take(10) + "..." + it.key.takeLast( + 10 + ), it.value + ) + } + +fun generateCorrectQuestionsPlot(): Plot { + val correctStats = com.worldline.training.ktor_quiz_collector.utils.getCorrectStats() + val statsDataFrame = dataFrameOf("question" to correctStats.map { it.question }, + "correct" to correctStats.map { it.correct }) + val responseCount = if (quizResponses.isEmpty()) 0 else quizResponses[0].responses.size + return statsDataFrame.plot { + layout { + title = "Correct Answers" + xAxisLabel = "Total correct answers" + yAxisLabel = "Question" + } + y("question") + barsH { + x.constant(responseCount) + width = 0.5 + fillColor = Color.GREY + alpha = 0.3 + } + barsH { + y("question") + x("correct") + fillColor("question") { + scale = categoricalColorHue() + } + } + } +} + +fun generateLeaderBoardPlot(): Plot { + val userScores = mapOf("user" to quizResponses.map { it.nickname }, + "score" to quizResponses.map { it.score }) + val rowCount = quizResponses.count() + val maxScore = quizResponses.maxOfOrNull { it.score } ?: 0 + val df = if (rowCount > 0) userScores.toDataFrame().sortByDesc("score") else userScores.toDataFrame() + return plot(df) { + layout { + title = "Leaderboard" + xAxisLabel = "Nickame" + yAxisLabel = "Score" + size = 1200 to 600 + } + x("user") + bars { + y.constant(maxScore) + width = 0.5 + fillColor = Color.GREY + alpha = 0.3 + } + bars { + y("score") { + scale = continuous(0..maxScore) + } + x("user") + if (rowCount > 2) { + fillColor("user") { + scale = categoricalColorHue() + } + } + + } + } +} \ No newline at end of file diff --git a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/utils/Models.kt b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/utils/Models.kt new file mode 100644 index 00000000..469d7568 --- /dev/null +++ b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/utils/Models.kt @@ -0,0 +1,20 @@ +package com.worldline.training.ktor_quiz_collector.utils + +import kotlinx.serialization.Serializable + +@Serializable +data class QuestionResponse( + val question: String, + val answer: String, + val id: Long, + val answerId: Long, + val correctAnswerId: Long +) + +@Serializable +data class QuizResponse(val score: Int, val nickname: String, val responses: List) + +@Serializable +data class CollectResponse(val score: Int) + +val quizResponses = mutableListOf() \ No newline at end of file diff --git a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/utils/SampleValues.kt b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/utils/SampleValues.kt new file mode 100644 index 00000000..5e910dad --- /dev/null +++ b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/com/worldline/training/ktor_quiz_collector/utils/SampleValues.kt @@ -0,0 +1,399 @@ +package com.worldline.training.ktor_quiz_collector.utils + +import io.ktor.server.application.* +import kotlinx.serialization.json.Json + +val json1 = """ + { + "responses": [ + { + "question": "What is the primary goal of Kotlin Multiplatform?", + "answer": "To share code between multiple platforms", + "answerId": 1, + "id": 1, + "correctAnswerId": 1 + }, + { + "question": "How does Kotlin Multiplatform facilitate code sharing between platforms?", + "answer": "By sharing business logic and adapting UI", + "answerId": 1, + "id": 2, + "correctAnswerId": 1 + }, + { + "question": "Which platforms does Kotlin Multiplatform support?", + "answer": "Android, iOS, desktop and web", + "answerId": 4, + "id": 3, + "correctAnswerId": 4 + }, + { + "question": "What is a common use case for Kotlin Multiplatform?", + "answer": "Developing a cross-platform app", + "answerId": 4, + "id": 4, + "correctAnswerId": 4 + }, + { + "question": "Which naming of KMP is deprecated?", + "answer": "Kotlin Multiplatform Mobile (KMM)", + "answerId": 4, + "id": 5, + "correctAnswerId": 4 + }, + { + "question": "How does Kotlin Multiplatform handle platform-specific implementations?", + "answer": "Through expect and actual declarations", + "answerId": 3, + "id": 6, + "correctAnswerId": 3 + }, + { + "question": "At which Google I/O, Google announced first-class support for Kotlin on Android?", + "answer": "2017", + "answerId": 3, + "id": 7, + "correctAnswerId": 3 + }, + { + "question": "What is the name of the Kotlin mascot?", + "answer": "Kodee", + "answerId": 4, + "id": 8, + "correctAnswerId": 4 + }, + { + "question": "The international yearly Kotlin conference is called...", + "answer": "KotlinConf", + "answerId": 4, + "id": 9, + "correctAnswerId": 4 + }, + { + "question": "Where will be located the next international yearly Kotlin conference?", + "answer": "Copenhagen, Denmark", + "answerId": 4, + "id": 10, + "correctAnswerId": 4 + } + ], + "score": 10, + "nickname": "user-650" + } +""".trimIndent() + +val json2 = """ + { + "responses": [ + { + "question": "What is the primary goal of Kotlin Multiplatform?", + "answer": "To exclusively compile code to JavaScript", + "answerId": 1, + "id": 1, + "correctAnswerId": 3 + }, + { + "question": "How does Kotlin Multiplatform facilitate code sharing between platforms?", + "answer": "By sharing business logic and adapting UI", + "answerId": 2, + "id": 2, + "correctAnswerId": 2 + }, + { + "question": "Which platforms does Kotlin Multiplatform support?", + "answer": "Android, iOS, desktop and web", + "answerId": 3, + "id": 3, + "correctAnswerId": 3 + }, + { + "question": "What is a common use case for Kotlin Multiplatform?", + "answer": "Developing a cross-platform app", + "answerId": 2, + "id": 4, + "correctAnswerId": 2 + }, + { + "question": "Which naming of KMP is deprecated?", + "answer": "Kodee multiplatform", + "answerId": 3, + "id": 5, + "correctAnswerId": 2 + }, + { + "question": "How does Kotlin Multiplatform handle platform-specific implementations?", + "answer": "By restricting to a single platform", + "answerId": 4, + "id": 6, + "correctAnswerId": 1 + }, + { + "question": "At which Google I/O, Google announced first-class support for Kotlin on Android?", + "answer": "2017", + "answerId": 4, + "id": 7, + "correctAnswerId": 4 + }, + { + "question": "What is the name of the Kotlin mascot?", + "answer": "Hadee", + "answerId": 2, + "id": 8, + "correctAnswerId": 3 + }, + { + "question": "The international yearly Kotlin conference is called...", + "answer": "KConf", + "answerId": 1, + "id": 9, + "correctAnswerId": 4 + }, + { + "question": "Where will be located the next international yearly Kotlin conference?", + "answer": "Amsterdam, Netherlands", + "answerId": 3, + "id": 10, + "correctAnswerId": 1 + } + ], + "score": 4, + "nickname": "user-140" + } +""".trimIndent() + + +val json3 = """ + { + "responses": [ + { + "question": "What is the primary goal of Kotlin Multiplatform?", + "answer": "To create iOS-only applications", + "answerId": 1, + "id": 1, + "correctAnswerId": 2 + }, + { + "question": "How does Kotlin Multiplatform facilitate code sharing between platforms?", + "answer": "By sharing business logic and adapting UI", + "answerId": 1, + "id": 2, + "correctAnswerId": 1 + }, + { + "question": "Which platforms does Kotlin Multiplatform support?", + "answer": "Android, iOS, desktop and web", + "answerId": 1, + "id": 3, + "correctAnswerId": 1 + }, + { + "question": "What is a common use case for Kotlin Multiplatform?", + "answer": "Creating a server-side application", + "answerId": 1, + "id": 4, + "correctAnswerId": 2 + }, + { + "question": "Which naming of KMP is deprecated?", + "answer": "Kodee multiplatform", + "answerId": 1, + "id": 5, + "correctAnswerId": 3 + }, + { + "question": "How does Kotlin Multiplatform handle platform-specific implementations?", + "answer": "By excluding platform-specific features", + "answerId": 1, + "id": 6, + "correctAnswerId": 4 + }, + { + "question": "At which Google I/O, Google announced first-class support for Kotlin on Android?", + "answer": "2017", + "answerId": 1, + "id": 7, + "correctAnswerId": 1 + }, + { + "question": "What is the name of the Kotlin mascot?", + "answer": "Kotee", + "answerId": 1, + "id": 8, + "correctAnswerId": 2 + }, + { + "question": "The international yearly Kotlin conference is called...", + "answer": "KodeeConf", + "answerId": 1, + "id": 9, + "correctAnswerId": 3 + }, + { + "question": "Where will be located the next international yearly Kotlin conference?", + "answer": "Lille, France", + "answerId": 1, + "id": 10, + "correctAnswerId": 3 + } + ], + "score": 3, + "nickname": "user-717" + } +""".trimIndent() + +val json4 = """ + { + "responses": [ + { + "question": "What is the primary goal of Kotlin Multiplatform?", + "answer": "To build only Android applications", + "answerId": 1, + "id": 1, + "correctAnswerId": 4 + }, + { + "question": "How does Kotlin Multiplatform facilitate code sharing between platforms?", + "answer": "By writing separate code for each platform", + "answerId": 1, + "id": 2, + "correctAnswerId": 3 + }, + { + "question": "Which platforms does Kotlin Multiplatform support?", + "answer": "Only web applications", + "answerId": 1, + "id": 3, + "correctAnswerId": 3 + }, + { + "question": "What is a common use case for Kotlin Multiplatform?", + "answer": "Building a desktop-only application", + "answerId": 1, + "id": 4, + "correctAnswerId": 4 + }, + { + "question": "Which naming of KMP is deprecated?", + "answer": "Kodee multiplatform", + "answerId": 1, + "id": 5, + "correctAnswerId": 4 + }, + { + "question": "How does Kotlin Multiplatform handle platform-specific implementations?", + "answer": "By automatically translating code", + "answerId": 1, + "id": 6, + "correctAnswerId": 4 + }, + { + "question": "At which Google I/O, Google announced first-class support for Kotlin on Android?", + "answer": "2017", + "answerId": 1, + "id": 7, + "correctAnswerId": 1 + }, + { + "question": "What is the name of the Kotlin mascot?", + "answer": "Kotee", + "answerId": 1, + "id": 8, + "correctAnswerId": 4 + }, + { + "question": "The international yearly Kotlin conference is called...", + "answer": "KotlinKonf", + "answerId": 1, + "id": 9, + "correctAnswerId": 3 + }, + { + "question": "Where will be located the next international yearly Kotlin conference?", + "answer": "Lille, France", + "answerId": 1, + "id": 10, + "correctAnswerId": 3 + }, + { + "question": "What is the primary goal of Kotlin Multiplatform?", + "answer": "To build only Android applications", + "answerId": 1, + "id": 1, + "correctAnswerId": 4 + }, + { + "question": "How does Kotlin Multiplatform facilitate code sharing between platforms?", + "answer": "By writing separate code for each platform", + "answerId": 1, + "id": 2, + "correctAnswerId": 3 + }, + { + "question": "Which platforms does Kotlin Multiplatform support?", + "answer": "Only web applications", + "answerId": 1, + "id": 3, + "correctAnswerId": 3 + }, + { + "question": "What is a common use case for Kotlin Multiplatform?", + "answer": "Building a desktop-only application", + "answerId": 1, + "id": 4, + "correctAnswerId": 4 + }, + { + "question": "Which naming of KMP is deprecated?", + "answer": "Kodee multiplatform", + "answerId": 1, + "id": 5, + "correctAnswerId": 4 + }, + { + "question": "How does Kotlin Multiplatform handle platform-specific implementations?", + "answer": "By automatically translating code", + "answerId": 1, + "id": 6, + "correctAnswerId": 4 + }, + { + "question": "At which Google I/O, Google announced first-class support for Kotlin on Android?", + "answer": "2017", + "answerId": 1, + "id": 7, + "correctAnswerId": 1 + }, + { + "question": "What is the name of the Kotlin mascot?", + "answer": "Kotee", + "answerId": 1, + "id": 8, + "correctAnswerId": 4 + }, + { + "question": "The international yearly Kotlin conference is called...", + "answer": "KotlinKonf", + "answerId": 1, + "id": 9, + "correctAnswerId": 3 + }, + { + "question": "Where will be located the next international yearly Kotlin conference?", + "answer": "Lille, France", + "answerId": 1, + "id": 10, + "correctAnswerId": 3 + } + ], + "score": 1, + "nickname": "user-424" + } +""".trimIndent() + +fun Application.addSampleValues() { + val data = listOf(json1, json2, json3, json4).map { json -> + val q = Json.decodeFromString(json) + QuizResponse(q.score, "user ${(1..1000).random()}", q.responses) + } + quizResponses.addAll(data) +} \ No newline at end of file diff --git a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/Application.kt b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/Application.kt deleted file mode 100644 index cc8544e7..00000000 --- a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/Application.kt +++ /dev/null @@ -1,20 +0,0 @@ -package example.com - -import example.com.plugins.configureHTTP -import example.com.plugins.configureRouting -import example.com.plugins.configureSerialization -import example.com.plugins.configureTemplating -import io.ktor.server.application.* - -fun main(args: Array) { - io.ktor.server.cio.EngineMain.main(args) -} - -fun Application.module() { - quizResponses.addAll(sampleResponses) - configureHTTP() - configureTemplating() - configureSerialization() - configureRouting() - configureQuizCollector() -} diff --git a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/Models.kt b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/Models.kt deleted file mode 100644 index 5800b7b7..00000000 --- a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/Models.kt +++ /dev/null @@ -1,312 +0,0 @@ -package example.com - -import kotlinx.serialization.Serializable - -@Serializable -data class QuestionResponse( - val question: String, - val answer: String, - val id: Long, - val answerId: Long, - val correctAnswerId: Long -) - -@Serializable -data class QuizResponse(val score: Int, val nickname: String, val responses: List) - -@Serializable -data class CollectResponse(val score: Int) - -val quizResponses = mutableListOf() - -// sample data with correct and wrong answers -val sampleResponses = listOf( - QuizResponse( - 0, "user1", - listOf( - QuestionResponse( - question = "What is the primary goal of Kotlin Multiplatform?", - answer = "To share", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "How does Kotlin Multiplatform facilitate code sharing between platforms?", - "By sharing business logic and adapting UI", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - question = "Which platforms does Kotlin Multiplatform support?", answer = "Android, iOS, and web", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What is a common use case for Kotlin Multiplatform?", "Developing a cross-platform app", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What is a shared code module in Kotlin Multiplatform called?", "Shared module", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "How does Kotlin Multiplatform handle platform-specific implementations?", - "actual", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What languages can be interoperable with Kotlin Multiplatform?", - "Java, Swift", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What tooling supports Kotlin Multiplatform development?", - "IntelliJ IDEA", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What is the benefit of using Kotlin Multiplatform for mobile development?", - "and sharing", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "How does Kotlin Multiplatform differ from Kotlin Native and Kotlin/JS?", - "None", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ) - ) - ), - QuizResponse( - 10, "user2", - listOf( - QuestionResponse( - "What is the primary goal of Kotlin Multiplatform?", - "To share code between multiple platforms", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "How does Kotlin Multiplatform facilitate code sharing between platforms?", - "By sharing business logic and adapting UI", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "Which platforms does Kotlin Multiplatform support?", "Android, iOS, and web", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What is a common use case for Kotlin Multiplatform?", "Developing a cross-platform app", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What is a shared code module in Kotlin Multiplatform called?", "Shared module", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "How does Kotlin Multiplatform handle platform-specific implementations?", - "Through expect and actual declarations", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What languages can be interoperable with Kotlin Multiplatform?", - "Java, JavaScript, Swift", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What tooling supports Kotlin Multiplatform development?", - "IntelliJ IDEA, Android Studio", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What is the benefit of using Kotlin Multiplatform for mobile development?", - "Code reuse and sharing", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "How does Kotlin Multiplatform differ from Kotlin Native and Kotlin/JS?", - "Kotlin Multiplatform allows sharing code between different platforms using common modules.", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ) - ) - ), - QuizResponse( - 2, "user4", - listOf( - QuestionResponse( - "What is the primary goal of Kotlin Multiplatform?", - "To share code between multiple platforms", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "How does Kotlin Multiplatform facilitate code sharing between platforms?", - "By sharing business logic and adapting UI", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "Which platforms does Kotlin Multiplatform support?", "Android, iOS, and web", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What is a common use case for Kotlin Multiplatform?", "Developing a cross-platform app", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What is a shared code module in Kotlin Multiplatform called?", "Shared module", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "How does Kotlin Multiplatform handle platform-specific implementations?", - "Through expect and actual declarations", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What languages can be interoperable with Kotlin Multiplatform?", - "Java, JavaScript, Swift", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What tooling supports Kotlin Multiplatform development?", - "IntelliJ IDEA, Android Studio", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What is the benefit of using Kotlin Multiplatform for mobile development?", - "Code reuse and sharing", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "How does Kotlin Multiplatform differ from Kotlin Native and Kotlin/JS?", - "Kotlin Multiplatform allows sharing code between different platforms using common modules.", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ) - ) - ), - QuizResponse( - 2, "user3", - listOf( - QuestionResponse( - "What is the primary goal of Kotlin Multiplatform?", - "To share code between multiple platforms", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "How does Kotlin Multiplatform facilitate code sharing between platforms?", - "By sharing business logic and adapting UI", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "Which platforms does Kotlin Multiplatform support?", "Android, iOS, and web", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What is a common use case for Kotlin Multiplatform?", "Developing a cross-platform app", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What is a shared code module in Kotlin Multiplatform called?", "Shared module", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "How does Kotlin Multiplatform handle platform-specific implementations?", - "Through expect and actual declarations", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What languages can be interoperable with Kotlin Multiplatform?", - "Java, JavaScript, Swift", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What tooling supports Kotlin Multiplatform development?", - "IntelliJ IDEA, Android Studio", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "What is the benefit of using Kotlin Multiplatform for mobile development?", - "Code reuse and sharing", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ), - QuestionResponse( - "How does Kotlin Multiplatform differ from Kotlin Native and Kotlin/JS?", - "Kotlin Multiplatform allows sharing code between different platforms using common modules.", - id = (1..10).random().toLong(), - answerId = (1..4).random().toLong(), - correctAnswerId = (1..4).random().toLong() - ) - ) - ), -) \ No newline at end of file diff --git a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/QuizCollector.kt b/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/QuizCollector.kt deleted file mode 100644 index caa9e671..00000000 --- a/material/rest-api-ui-ktor-quiz-collector/src/main/kotlin/example/com/QuizCollector.kt +++ /dev/null @@ -1,106 +0,0 @@ -package example.com - -import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.html.* -import io.ktor.server.request.* -import io.ktor.server.response.* -import io.ktor.server.routing.* -import kotlinx.html.* -import kotlinx.serialization.Serializable -import org.jetbrains.kotlinx.dataframe.api.dataFrameOf -import org.jetbrains.kotlinx.kandy.dsl.plot -import org.jetbrains.kotlinx.kandy.letsplot.export.toHTML -import org.jetbrains.kotlinx.kandy.letsplot.export.toSVG -import org.jetbrains.kotlinx.kandy.letsplot.layers.bars -import org.jetbrains.kotlinx.kandy.letsplot.layers.barsH -import org.jetbrains.kotlinx.kandy.letsplot.scales.categoricalColorHue - -@Serializable -data class QuestionStats(val question: String, val correct: Int) - -fun Application.configureQuizCollector() { - fun getCorrectStats() = quizResponses.flatMap { it.responses }.groupBy { it.question } - .mapValues { it.value.count { qr -> qr.correctAnswerId == qr.answerId } } - .map { QuestionStats(it.key, it.value) } - - routing { - post("/respond") { - val quizResponse = call.receive() - quizResponses.add(quizResponse) - call.respond(CollectResponse(quizResponse.score)) - } - - get("/raw-responses") { - call.respond(quizResponses) - } - - get("/responses") { - call.respond(quizResponses.map { quizResponse -> - quizResponse.responses.fold(mutableMapOf()) { acc, element -> - acc[element.question] = element.answer - acc - } - }) - } - - get("/correct-stats") { - call.respond(getCorrectStats()) - } - - get("/table2") { - call.respond(quizResponses) - } - - get("/table") { - val result = quizResponses.flatMap { it.responses }.groupBy { it.question } - .mapValues { it.value.map { value -> value.answer } } - call.respond(result) - } - - get("/reset") { - quizResponses.clear() - call.respond(HttpStatusCode.OK) - } - - get("/") { - call.respondHtml { - head { - title = "Quiz Collector" - } - body { - h1 { +"Quiz Collector" } - div { - val correctStats = getCorrectStats() - val statsDataFrame = dataFrameOf("question" to correctStats.map { it.question }, - "correct" to correctStats.map { it.correct }) - val content = statsDataFrame.plot { - barsH { - y("question") - x("correct") - - fillColor("question") { - scale = categoricalColorHue() - } - } - - }.toSVG() - unsafe { +content } - } - } - } - } - get("/ui/correct") { - val correctStats = getCorrectStats() - val statsDataFrame = dataFrameOf("question" to correctStats.map { it.question }, - "correct" to correctStats.map { it.correct }) - val html = statsDataFrame.plot { - bars { - y("question") - x("correct") - } - }.toHTML(false) - call.respondText(html, ContentType.Text.Html) - } - } -} \ No newline at end of file diff --git a/material/rest-api-ui-ktor-quiz-collector/src/main/resources/application.yaml b/material/rest-api-ui-ktor-quiz-collector/src/main/resources/application.yaml index 5d3834a6..e9845d32 100644 --- a/material/rest-api-ui-ktor-quiz-collector/src/main/resources/application.yaml +++ b/material/rest-api-ui-ktor-quiz-collector/src/main/resources/application.yaml @@ -2,8 +2,9 @@ ktor: #development: true application: modules: - - example.com.ApplicationKt.module + - com.worldline.training.ktor_quiz_collector.ApplicationKt.module deployment: port: "$PORT:8080" watch: - - classes \ No newline at end of file + - classes + - resources \ No newline at end of file diff --git a/material/rest-api-ui-ktor-quiz-collector/src/main/resources/static/auto-reload.js b/material/rest-api-ui-ktor-quiz-collector/src/main/resources/static/auto-reload.js new file mode 100644 index 00000000..52252170 --- /dev/null +++ b/material/rest-api-ui-ktor-quiz-collector/src/main/resources/static/auto-reload.js @@ -0,0 +1,7 @@ +setInterval(() => { + fetch('/parts/graphs') + .then(response => response.text()) + .then(data => { + document.getElementById('graphs').innerHTML = data; + }); +}, 2000); \ No newline at end of file diff --git a/material/rest-api-ui-ktor-quiz-collector/src/main/resources/static/favicon.ico b/material/rest-api-ui-ktor-quiz-collector/src/main/resources/static/favicon.ico new file mode 100644 index 00000000..23702818 Binary files /dev/null and b/material/rest-api-ui-ktor-quiz-collector/src/main/resources/static/favicon.ico differ diff --git a/material/rest-api-ui-ktor-quiz-collector/src/test/kotlin/example/com/ApplicationTest.kt b/material/rest-api-ui-ktor-quiz-collector/src/test/kotlin/com/worldline/training/ktor_quiz_collector/ApplicationTest.kt similarity index 75% rename from material/rest-api-ui-ktor-quiz-collector/src/test/kotlin/example/com/ApplicationTest.kt rename to material/rest-api-ui-ktor-quiz-collector/src/test/kotlin/com/worldline/training/ktor_quiz_collector/ApplicationTest.kt index 1c76138f..d42a8cea 100644 --- a/material/rest-api-ui-ktor-quiz-collector/src/test/kotlin/example/com/ApplicationTest.kt +++ b/material/rest-api-ui-ktor-quiz-collector/src/test/kotlin/com/worldline/training/ktor_quiz_collector/ApplicationTest.kt @@ -1,6 +1,6 @@ -package example.com +package com.worldline.training.ktor_quiz_collector -import example.com.plugins.configureRouting +import com.worldline.training.ktor_quiz_collector.plugins.configureRouting import io.ktor.client.request.* import io.ktor.http.* import io.ktor.server.testing.*