diff --git a/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/CLI.kt b/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/CLI.kt index 83fa5ad..20a1f76 100644 --- a/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/CLI.kt +++ b/clients/cli/src/main/kotlin/com/jaytux/phoebench/clients/cli/CLI.kt @@ -110,7 +110,7 @@ object CLI { data class FileData(val file: InputStream, val parse: (String) -> T?) : IData { override fun toList(): List { val raw = file.bufferedReader().use { it.readText() }.split(',') - + val parsed = ArrayList(raw.size) val errors = mutableListOf() raw.forEach { @@ -201,6 +201,8 @@ object CLI { option("--min", "--m", "--minutes", help = "Set the time unit to minutes").flag().convert { TimeUnit.MINUTES }, option("--h", "--hour", help = "Set the time unit to hours").flag().convert { TimeUnit.HOURS } ).single() + val input by option("--input", help = "Set the input used to benchmark") + val hardware by option("--hardware", "--hw", help = "Set the hardware on which the benchmark was run") override fun run() = ProjectHandlers.newData(parent.finder, label, warmup, measurement, unit) } diff --git a/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/Client.kt b/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/Client.kt index efabfd7..c050cbd 100644 --- a/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/Client.kt +++ b/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/Client.kt @@ -76,7 +76,7 @@ class Client private constructor(private val _auth: AuthProvider) { println("Call to ${route.pattern} [$ctr] was cancelled") ErrorResponse(COROUTINE_CANCELLED).error() } catch (e: Exception) { - println("Call to ${route.pattern} [$ctr] ran into an exception") + println("Call to ${route.pattern} [$ctr] ran into an exception: ${e.message}") ErrorResponse(e.message ?: "Unknown error.").error() } } diff --git a/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/data/IProjectRepo.kt b/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/data/IProjectRepo.kt index 0c8b006..e576aa0 100644 --- a/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/data/IProjectRepo.kt +++ b/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/data/IProjectRepo.kt @@ -30,7 +30,7 @@ interface IProjectRepo { suspend fun deleteLabel(id: Uuid): Either suspend fun newEntry(label: Uuid, timestamp: Instant, warmups: List, measurements: List, - unit: TimeUnit): Either + unit: TimeUnit, input: String, hardware: String): Either suspend fun deleteEntry(id: Uuid): Either companion object { @@ -54,9 +54,9 @@ interface IProjectRepo { _client.callRoute(Routes.Label.delete, id).ignoreValue() override suspend fun newEntry(label: Uuid, timestamp: Instant, warmups: List, - measurements: List, unit: TimeUnit + measurements: List, unit: TimeUnit, input: String, hardware: String ): Either = - _client.callRoute(Routes.Entry.new, EntryRequest(label, timestamp, _projectId, warmups, measurements, unit)) + _client.callRoute(Routes.Entry.new, EntryRequest(label, timestamp, _projectId, warmups, measurements, unit, input, hardware)) override suspend fun deleteEntry(id: Uuid): Either = _client.callRoute(Routes.Entry.delete, id).ignoreValue() diff --git a/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/data/ProjectVM.kt b/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/data/ProjectVM.kt index 2cf48a4..fcdd27b 100644 --- a/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/data/ProjectVM.kt +++ b/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/data/ProjectVM.kt @@ -57,11 +57,11 @@ class ProjectVM( } data class Entry(val id: Uuid, val label: Label, val timeStamp: Instant, val warmups: List, - val measurements: List, val nativeUnit: TimeUnit) { + val measurements: List, val nativeUnit: TimeUnit, val input: String, val hardware: String) { companion object { fun fromResponse(it: EntryResponse, map: Map) = Entry( it.id, map[it.labelId] ?: Label.invalid, it.timestamp, - it.warmups, it.measurements, it.unit + it.warmups, it.measurements, it.unit, it.input, it.hardware ) } } @@ -72,6 +72,8 @@ class ProjectVM( private val _editable = mutableStateOf(false) private val _labels = mutableStateOf(mapOf()) private val _entries = mutableStateOf(listOf()) + private val _inputs = mutableStateOf(setOf()) + private val _hardware = mutableStateOf(setOf()) val name = _name.immutable() val owner = _owner.immutable() @@ -79,6 +81,8 @@ class ProjectVM( val editable = _editable.immutable() val labels = _labels.immutable() val entries = _entries.immutable() + val inputs = _inputs.immutable() + val hardware = _hardware.immutable() private var _job: Job? = null @@ -101,7 +105,15 @@ class ProjectVM( _public.value = it.isPublic _editable.value = it.isEditable _labels.value = it.usedLabels.associate { l -> l.id to Label.fromResponse(l) } - _entries.value = it.entries.map { e -> Entry.fromResponse(e, _labels.value) } + val localInputs = mutableSetOf() + val localHardware = mutableSetOf() + _entries.value = it.entries.map { e -> + localInputs += e.input + localHardware += e.hardware + Entry.fromResponse(e, _labels.value) + } + _inputs.value = localInputs + _hardware.value = localHardware } } } @@ -112,7 +124,11 @@ class ProjectVM( is ProjectEvent.EntryDeleted -> _entries.value = _entries.value.filter { it.id != event.id } is ProjectEvent.LabelChanged -> _labels.value += (event.label.id to Label.fromResponse(event.label)) is ProjectEvent.LabelDeleted -> _labels.value = _labels.value.filter { it.value.id != event.id } - is ProjectEvent.NewEntry -> _entries.value = _entries.value.insort(Entry.fromResponse(event.entry, _labels.value), Entry::timeStamp) + is ProjectEvent.NewEntry -> { + _entries.value = _entries.value.insort(Entry.fromResponse(event.entry, _labels.value), Entry::timeStamp) + _inputs.value += event.entry.input + _hardware.value += event.entry.hardware + } is ProjectEvent.NewLabel -> _labels.value += (event.label.id to Label.fromResponse(event.label)) is ProjectEvent.Updated -> { _name.value = event.changes.name @@ -124,7 +140,9 @@ class ProjectVM( fun update(name: String?, isPublic: Boolean?) { withScope { - _repo.update(name, isPublic).snackOr { refresh() } + _repo.update(name, isPublic).snackOr { + if(_job == null) refresh() + } } } @@ -142,7 +160,8 @@ class ProjectVM( fun mkLabel(name: String, color: Color) { withScope { _repo.newLabel(name, color).snackOr { - _labels.value += it.id to Label(it.id, it.name, it.color) + if(_job == null) + _labels.value += it.id to Label(it.id, it.name, it.color) } } } @@ -151,7 +170,8 @@ class ProjectVM( withScope { val old = _labels.value[id] ?: return@withScope _repo.updateLabel(id, name, color).snackOr { - _labels.value += id to Label(id, name ?: old.name, color?.hexString() ?: old.colorStr, color ?: old.uiColor) + if(_job == null) + _labels.value += id to Label(id, name ?: old.name, color?.hexString() ?: old.colorStr, color ?: old.uiColor) } } } @@ -159,15 +179,20 @@ class ProjectVM( fun deleteLabel(id: Uuid) { withScope { _repo.deleteLabel(id).snackOr { - _labels.value -= id + if(_job == null) + _labels.value -= id } } } - fun mkEntry(label: Label, warmups: List, measurements: List, unit: TimeUnit) { + fun mkEntry(label: Label, warmups: List, measurements: List, unit: TimeUnit, input: String, hardware: String) { withScope { - _repo.newEntry(label.id, Clock.System.now(), warmups, measurements, unit).snackOr { - _entries.value += Entry.fromResponse(it, _labels.value) + _repo.newEntry(label.id, Clock.System.now(), warmups, measurements, unit, input, hardware).snackOr { + if(_job == null) { + _entries.value += Entry.fromResponse(it, _labels.value) + _inputs.value += input + _hardware.value += hardware + } } } } @@ -175,7 +200,8 @@ class ProjectVM( fun deleteEntry(id: Uuid) { withScope { _repo.deleteEntry(id).snackOr { - _entries.value = _entries.value.filter { it.id != id } + if(_job == null) + _entries.value = _entries.value.filter { it.id != id } } } } diff --git a/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/ui/ProjectView.kt b/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/ui/ProjectView.kt index d9db40d..2883329 100644 --- a/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/ui/ProjectView.kt +++ b/clients/compose/src/commonMain/kotlin/com/jaytux/phoebench/clients/ui/ProjectView.kt @@ -13,13 +13,16 @@ import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.backhandler.BackHandler import androidx.compose.ui.draw.scale +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.text.toLowerCase import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.lifecycle.viewmodel.compose.viewModel @@ -27,6 +30,7 @@ import com.composables.icons.lucide.* import com.jaytux.phoebench.clients.darken import com.jaytux.phoebench.clients.data.ProjectVM import com.jaytux.phoebench.clients.data.mutableStateSetFrom +import com.jaytux.phoebench.clients.data.mutableStateSetOf import com.jaytux.phoebench.clients.dualLerp import com.jaytux.phoebench.clients.fmt import com.jaytux.phoebench.clients.fmtRange @@ -153,11 +157,14 @@ fun ProjectView(id: Uuid, forceBack: () -> Unit) { addOpen = false } - if(addingData) AddEntryDialog(labels, + val usedInputs by vm.inputs + val usedHardware by vm.hardware + + if(addingData) AddEntryDialog(labels, usedInputs, usedHardware, onCancel = { addingData = false; addOpen = false }, onAddLbl = { name, lbl -> vm.mkLabel(name, lbl) } - ) { label, warmups, measurements, unit -> - vm.mkEntry(label, warmups, measurements, unit) + ) { label, warmups, measurements, unit, input, hardware -> + vm.mkEntry(label, warmups, measurements, unit, input, hardware) addOpen = false } } @@ -227,26 +234,33 @@ fun AddLabelDialog(onCancel: () -> Unit, onAdd: (name: String, color: Color) -> } @Composable -fun QuickLabel(lbl: ProjectVM.Label) { +fun QuickLabel(lbl: ProjectVM.Label, hollow: Boolean = false) { Row(Modifier.height(IntrinsicSize.Min).padding(vertical = 3.dp), verticalAlignment = Alignment.Bottom) { - Box(Modifier.fillMaxHeight().aspectRatio(1f).background(lbl.uiColor)) {} + Box(Modifier.fillMaxHeight().aspectRatio(1f).background(lbl.uiColor)) { + if(hollow) { + Box(Modifier.fillMaxHeight(0.75f).aspectRatio(1f).align(Alignment.Center).background(Color.Gray.copy(alpha = 0.5f))) + } + } Spacer(Modifier.width(15.dp)) Text(lbl.name) Text(" ${lbl.colorStr}", style = MaterialTheme.typography.bodySmall, color = LocalContentColor.current.copy(alpha = 0.75f)) } } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun AddEntryDialog( - labels: Map, + labels: Map, usedInputs: Set, usedHardware: Set, onCancel: () -> Unit, onAddLbl: (name: String, color: Color) -> Unit, - onAdd: (label: ProjectVM.Label, warmups: List, measurements: List, unit: TimeUnit) -> Unit + onAdd: (label: ProjectVM.Label, warmups: List, measurements: List, unit: TimeUnit, input: String, hardware: String) -> Unit ) { Dialog(onDismissRequest = onCancel) { var label by remember { mutableStateOf(null) } var warmups by remember { mutableStateOf("") } var measurements by remember { mutableStateOf("") } var unit by remember { mutableStateOf(TimeUnit.SECONDS) } + var input by remember { mutableStateOf("") } + var hardware by remember { mutableStateOf("") } var addingLabel by remember { mutableStateOf(false) } var warmupParsed by remember { mutableStateOf>>(listOf().value()) } @@ -308,6 +322,39 @@ fun AddEntryDialog( } } + Row { + var inputFocused by remember { mutableStateOf(false) } + var hardwareFocused by remember { mutableStateOf(false) } + + Box(Modifier.weight(0.5f)) { + ExposedDropdownMenuBox(inputFocused, {}) { + OutlinedTextField(input, { input = it }, + Modifier.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable).onFocusChanged { inputFocused = it.isFocused }, + label = { Text("Input") } + ) + ExposedDropdownMenu(inputFocused, {}) { + usedInputs.filter { input.lowercase() in it.lowercase() }.sorted().forEach { inp -> + DropdownMenuItem(text = { Text(inp) }, onClick = { input = inp }) + } + } + } + } + Spacer(Modifier.width(5.dp)) + Box(Modifier.weight(0.5f)) { + ExposedDropdownMenuBox(hardwareFocused, { inputFocused = false }) { + OutlinedTextField(hardware, { hardware = it }, + Modifier.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable).onFocusChanged { hardwareFocused = it.isFocused }, + label = { Text("Hardware") } + ) + ExposedDropdownMenu(hardwareFocused, { hardwareFocused = false }) { + usedHardware.filter { hardware.lowercase() in it.lowercase() }.sorted().forEach { hw -> + DropdownMenuItem(text = { Text(hw) }, onClick = { hardware = hw }) + } + } + } + } + } + Spacer(Modifier.height(7.dp)) Text("Data (separate data points by commas)") Spacer(Modifier.height(5.dp)) @@ -326,8 +373,8 @@ fun AddEntryDialog( Spacer(Modifier.height(5.dp)) CancelConfirmXRow(onCancel, { - onAdd(label!!, warmupParsed.asValue()!!, measureParsed.asValue()!!, unit) - }, confirmText = "Create", canConfirm = label != null && warmupParsed.isValue() && measureParsed.isValue()) + onAdd(label!!, warmupParsed.asValue()!!, measureParsed.asValue()!!, unit, input, hardware) + }, confirmText = "Create", canConfirm = label != null && warmupParsed.isValue() && measureParsed.isValue() && input.isNotBlank() && hardware.isNotBlank()) } } @@ -356,17 +403,33 @@ fun ProjectPlotArea(vm: ProjectVM){ val labelFilter = remember(labels) { mutableStateSetFrom(labels.values) } val labelFilterKey by labelFilter.revision - LaunchedEffect(entries, displayWarmups, currentUnit, timeFilter, timeMin, timeMax, labelFilterKey) { + val allInputs by vm.inputs + val allHardware by vm.hardware + val enabledInputs = remember { mutableStateSetOf() } + val enabledHardware = remember { mutableStateSetOf() } + var filterInput by remember { mutableStateOf(null) } + var filterHardware by remember { mutableStateOf(null) } + + LaunchedEffect(entries, displayWarmups, currentUnit, timeFilter, timeMin, timeMax, labelFilterKey, filterInput, filterHardware) { var maxX = 0f var minY = 0f var maxY = 0f val timeRange = dualLerp(timeMin, timeMax, timeFilter.start, timeFilter.endInclusive) + enabledInputs.clear() + enabledHardware.clear() + renderableEntries = entries.mapNotNull { entry -> val use = if(displayWarmups) entry.warmups else entry.measurements - maxX = maxOf(maxX, use.size.toFloat()) if(entry.label !in labelFilter) return@mapNotNull null if(!(entry.timeStamp inRange timeRange)) return@mapNotNull null + if(filterInput != null && entry.input != filterInput) return@mapNotNull null + if(filterHardware != null && entry.hardware != filterHardware) return@mapNotNull null + + maxX = maxOf(maxX, use.size.toFloat()) + + enabledInputs.add(entry.input) + enabledHardware.add(entry.hardware) use.mapIndexed { idx, it -> val converted = entry.nativeUnit.convertTo(currentUnit, it) @@ -482,9 +545,11 @@ fun ProjectPlotArea(vm: ProjectVM){ Spacer(Modifier.width(20.dp)) - Box(Modifier.weight(0.33f).fillMaxHeight()) { - Column { + Row(Modifier.weight(0.33f).fillMaxHeight()) { + Column(Modifier.weight(0.5f)) { Text("Labels", style = MaterialTheme.typography.headlineSmall) + Text("Click a label to toggle its visibility.", fontStyle = FontStyle.Italic, + color = LocalContentColor.current.copy(alpha = 0.5f), style = MaterialTheme.typography.bodySmall) Spacer(Modifier.height(10.dp)) if(labels.isEmpty()) { Box(Modifier.fillMaxWidth().fillMaxHeight(0.25f)) { @@ -494,9 +559,18 @@ fun ProjectPlotArea(vm: ProjectVM){ else { LazyColumn(Modifier.padding(start = 5.dp)) { items(labels.toList().sortedBy { it.second.name }) { (_, lbl) -> - Box(Modifier.fillMaxWidth().clickable { labelFilter.toggle(lbl) }) { + Box(Modifier.fillMaxWidth().clickable { + if(labelFilter.size == labels.size) { + labelFilter.clear() + labelFilter.add(lbl) + } + else { + labelFilter.toggle(lbl) + if(labelFilter.isEmpty()) labelFilter.addAll(labels.values) + } + }) { Box { - QuickLabel(lbl) + QuickLabel(lbl, lbl !in labelFilter) if(lbl !in labelFilter) { Box(Modifier.matchParentSize()) { @@ -513,6 +587,46 @@ fun ProjectPlotArea(vm: ProjectVM){ } } } + Spacer(Modifier.width(15.dp)) + LazyColumn(Modifier.weight(0.5f)) { + item { Text("Inputs", style = MaterialTheme.typography.headlineSmall) } + item { + Text("Click an input to set or clear the filter.", fontStyle = FontStyle.Italic, + color = LocalContentColor.current.copy(0.5f), style = MaterialTheme.typography.bodySmall) + } + + item { Spacer(Modifier.height(10.dp)) } + + items((allInputs intersect enabledInputs.toSet()).toList()) { + Row(Modifier.fillMaxWidth().clickable { filterInput = if(filterInput == null) it else null }) { + Text(it, Modifier.padding(start = 5.dp)) + } + } + + items((allInputs - enabledInputs.toSet()).toList()) { + Text(it, Modifier.padding(start = 5.dp, top = 2.dp, bottom = 2.dp), color = LocalContentColor.current.copy(0.66f)) + } + + item { Spacer(Modifier.height(15.dp)) } + + item { Text("Hardware", style = MaterialTheme.typography.headlineSmall) } + item { + Text("Click an item to set or clear the filter.", fontStyle = FontStyle.Italic, + color = LocalContentColor.current.copy(0.5f), style = MaterialTheme.typography.bodySmall) + } + + item { Spacer(Modifier.height(10.dp)) } + + items((allHardware intersect enabledHardware.toSet()).toList()) { + Row(Modifier.fillMaxWidth().clickable { filterHardware = if(filterHardware == null) it else null }) { + Text(it, Modifier.padding(start = 5.dp)) + } + } + + items((allHardware - enabledHardware.toSet()).toList()) { + Text(it, Modifier.padding(start = 5.dp, top = 2.dp, bottom = 2.dp), color = LocalContentColor.current.copy(0.66f)) + } + } } } } diff --git a/common/src/commonMain/kotlin/com/jaytux/phoebench/common/Requests.kt b/common/src/commonMain/kotlin/com/jaytux/phoebench/common/Requests.kt index 0d06281..5d15eea 100644 --- a/common/src/commonMain/kotlin/com/jaytux/phoebench/common/Requests.kt +++ b/common/src/commonMain/kotlin/com/jaytux/phoebench/common/Requests.kt @@ -27,7 +27,7 @@ data class LabelRequest(val name: String, val color: String, val projectId: Uuid @Serializable @ToPartialize data class EntryRequest(val label: Uuid, val timestamp: Instant, val projectId: Uuid, val warmups: List, - val measurements: List, val unit: TimeUnit) + val measurements: List, val unit: TimeUnit, val input: String, val hardware: String) @Serializable data class LogoutRequest(val refresh: Uuid) diff --git a/common/src/commonMain/kotlin/com/jaytux/phoebench/common/Responses.kt b/common/src/commonMain/kotlin/com/jaytux/phoebench/common/Responses.kt index 7142d6a..abf1b73 100644 --- a/common/src/commonMain/kotlin/com/jaytux/phoebench/common/Responses.kt +++ b/common/src/commonMain/kotlin/com/jaytux/phoebench/common/Responses.kt @@ -54,7 +54,7 @@ data class LabelResponse(val id: Uuid, val name: String, val color: String) @Serializable data class EntryResponse(val id: Uuid, val labelId: Uuid, val timestamp: Instant, val warmups: List, - val measurements: List, val unit: TimeUnit) + val measurements: List, val unit: TimeUnit, val input: String, val hardware: String) @Serializable data class HandshakeResponse(val version: String = ProtocolVersion.VERSION) { diff --git a/server/src/main/kotlin/com/jaytux/phoebench/server/db/Entities.kt b/server/src/main/kotlin/com/jaytux/phoebench/server/db/Entities.kt index 1ae9826..15d0e00 100644 --- a/server/src/main/kotlin/com/jaytux/phoebench/server/db/Entities.kt +++ b/server/src/main/kotlin/com/jaytux/phoebench/server/db/Entities.kt @@ -65,6 +65,8 @@ class Entry(id: EntityID) : Entity(id) { var warmups by Entries.warmups var measurements by Entries.measurements var unit by Entries.unit + var input by Entries.input + var hardware by Entries.hardware var project by Project referencedOn Entries.projectId var label by Label referencedOn Entries.labelId diff --git a/server/src/main/kotlin/com/jaytux/phoebench/server/db/Tables.kt b/server/src/main/kotlin/com/jaytux/phoebench/server/db/Tables.kt index 43ef35c..85966b7 100644 --- a/server/src/main/kotlin/com/jaytux/phoebench/server/db/Tables.kt +++ b/server/src/main/kotlin/com/jaytux/phoebench/server/db/Tables.kt @@ -47,4 +47,6 @@ object Entries : UuidTable() { val warmups = list("warmups") val measurements = list("measurements") val unit = enumeration("unit") + val input = varchar("input", 255).default("") + val hardware = varchar("hardware", 255).default("") } \ No newline at end of file diff --git a/server/src/main/kotlin/com/jaytux/phoebench/server/handlers/ProjectHandler.kt b/server/src/main/kotlin/com/jaytux/phoebench/server/handlers/ProjectHandler.kt index 45eea7a..bebb3bf 100644 --- a/server/src/main/kotlin/com/jaytux/phoebench/server/handlers/ProjectHandler.kt +++ b/server/src/main/kotlin/com/jaytux/phoebench/server/handlers/ProjectHandler.kt @@ -56,7 +56,7 @@ object ProjectHandler { private fun Project.toResponse(user: User) = ProjectResponse( id.value, name, NamedID(owner.username, owner.id.value), isPublic, isEditableBy(user), labels.orderBy(Labels.label to SortOrder.ASC).map { LabelResponse(it.id.value, it.label, it.color) }, - entries.orderBy(Entries.timestamp to SortOrder.ASC).map { EntryResponse(it.id.value, it.label.id.value, it.timestamp, it.warmups, it.measurements, it.unit) }) + entries.orderBy(Entries.timestamp to SortOrder.ASC).map { EntryResponse(it.id.value, it.label.id.value, it.timestamp, it.warmups, it.measurements, it.unit, it.input, it.hardware) }) fun home(user: User, req: EmptyRequest) = transaction { val own = user.projects.orderBy(Projects.name to SortOrder.ASC).map { @@ -188,9 +188,11 @@ object ProjectHandler { timestamp = req.timestamp warmups = req.warmups unit = req.unit + input = req.input + hardware = req.hardware } - val response = EntryResponse(entry.id.value, entry.label.id.value, entry.timestamp, entry.warmups, entry.measurements, entry.unit) + val response = EntryResponse(entry.id.value, entry.label.id.value, entry.timestamp, entry.warmups, entry.measurements, entry.unit, entry.input, entry.hardware) ServerScope.launch { Buses.projectBus(proj.id.value).sendAll(ProjectEvent.NewEntry(response)) }