Skip to content
This repository was archived by the owner on Sep 26, 2020. It is now read-only.

Commit ac8c769

Browse files
committed
Interactive progress updating.
1 parent cc5ef38 commit ac8c769

File tree

8 files changed

+113
-82
lines changed

8 files changed

+113
-82
lines changed

db-data-test-util/src/main/kotlin/edu/wpi/axon/dbdata/TestUtil.kt

+33-33
Original file line numberDiff line numberDiff line change
@@ -31,38 +31,38 @@ fun Random.nextTrainingScriptProgress(): TrainingScriptProgress =
3131
}
3232

3333
fun Random.nextJob() = Job(
34-
RandomStringUtils.randomAlphanumeric(10),
35-
nextTrainingScriptProgress(),
36-
RandomStringUtils.randomAlphanumeric(10),
37-
RandomStringUtils.randomAlphanumeric(10),
38-
nextDataset(),
39-
Optimizer.Adam(
40-
nextDouble(),
41-
nextDouble(),
42-
nextDouble(),
43-
nextDouble(),
44-
nextBoolean()
45-
),
46-
Loss.SparseCategoricalCrossentropy,
47-
setOf(
48-
RandomStringUtils.randomAlphanumeric(10),
49-
RandomStringUtils.randomAlphanumeric(10)
50-
),
51-
nextInt(),
52-
Model.Sequential(
53-
RandomStringUtils.randomAlphanumeric(10),
54-
(1..3).map { nextInt(128) },
55-
setOf(
56-
Layer.Dense(RandomStringUtils.randomAlphanumeric(10), null, 10).trainable(),
57-
Layer.Conv2D(
34+
name = RandomStringUtils.randomAlphanumeric(10),
35+
status = nextTrainingScriptProgress(),
36+
userOldModelPath = RandomStringUtils.randomAlphanumeric(10),
37+
userNewModelName = RandomStringUtils.randomAlphanumeric(10),
38+
userDataset = nextDataset(),
39+
userOptimizer = Optimizer.Adam(
40+
nextDouble(),
41+
nextDouble(),
42+
nextDouble(),
43+
nextDouble(),
44+
nextBoolean()
45+
),
46+
userLoss = Loss.SparseCategoricalCrossentropy,
47+
userMetrics = setOf(
5848
RandomStringUtils.randomAlphanumeric(10),
59-
null,
60-
9,
61-
SerializableTuple2II(3, 3),
62-
Activation.SoftMax
63-
).trainable(),
64-
Layer.AveragePooling2D(RandomStringUtils.randomAlphanumeric(10), null).untrainable()
65-
)
66-
),
67-
nextBoolean()
49+
RandomStringUtils.randomAlphanumeric(10)
50+
),
51+
userEpochs = nextInt(),
52+
userModel = Model.Sequential(
53+
RandomStringUtils.randomAlphanumeric(10),
54+
(1..3).map { nextInt(128) },
55+
setOf(
56+
Layer.Dense(RandomStringUtils.randomAlphanumeric(10), null, 10).trainable(),
57+
Layer.Conv2D(
58+
RandomStringUtils.randomAlphanumeric(10),
59+
null,
60+
9,
61+
SerializableTuple2II(3, 3),
62+
Activation.SoftMax
63+
).trainable(),
64+
Layer.AveragePooling2D(RandomStringUtils.randomAlphanumeric(10), null).untrainable()
65+
)
66+
),
67+
generateDebugComments = nextBoolean()
6868
)

db/src/main/kotlin/edu/wpi/axon/db/JobDb.kt

+52-30
Original file line numberDiff line numberDiff line change
@@ -51,42 +51,62 @@ internal object Jobs : IntIdTable() {
5151
}
5252
}
5353

54+
typealias JobCallback = (Job) -> Unit
55+
5456
class JobDb(private val database: Database) {
57+
58+
private val observers = mutableListOf<JobCallback>()
59+
5560
init {
5661
transaction(database) {
5762
SchemaUtils.create(Jobs)
5863
}
5964
}
6065

61-
fun create(job: Job): Int? = transaction(database) {
62-
Jobs.insertAndGetId { row ->
63-
row[name] = job.name
64-
row[status] = job.status.serialize()
65-
row[userOldModelPath] = job.userOldModelPath
66-
row[userNewModelName] = job.userNewModelName
67-
row[userDataset] = job.userDataset.serialize()
68-
row[userOptimizer] = job.userOptimizer.serialize()
69-
row[userLoss] = job.userLoss.serialize()
70-
row[userMetrics] = klaxon.toJsonString(job.userMetrics)
71-
row[userEpochs] = job.userEpochs
72-
row[userModel] = job.userModel.serialize()
73-
row[generateDebugComments] = job.generateDebugComments
74-
}.value
66+
fun subscribe(onUpdate: JobCallback) {
67+
observers.add(onUpdate)
68+
}
69+
70+
fun create(job: Job): Job {
71+
val newId = transaction(database) {
72+
Jobs.insertAndGetId { row ->
73+
row[name] = job.name
74+
row[status] = job.status.serialize()
75+
row[userOldModelPath] = job.userOldModelPath
76+
row[userNewModelName] = job.userNewModelName
77+
row[userDataset] = job.userDataset.serialize()
78+
row[userOptimizer] = job.userOptimizer.serialize()
79+
row[userLoss] = job.userLoss.serialize()
80+
row[userMetrics] = klaxon.toJsonString(job.userMetrics)
81+
row[userEpochs] = job.userEpochs
82+
row[userModel] = job.userModel.serialize()
83+
row[generateDebugComments] = job.generateDebugComments
84+
}.value
85+
}
86+
87+
val newJob = job.copy(id = newId)
88+
observers.forEach { it(newJob) }
89+
90+
return newJob
7591
}
7692

77-
fun update(job: Job): Int? = transaction(database) {
78-
Jobs.update({ Jobs.id eq job.id }) {
79-
it[name] = job.name
80-
it[status] = job.status.serialize()
81-
it[userOldModelPath] = job.userOldModelPath
82-
it[userNewModelName] = job.userNewModelName
83-
it[userDataset] = job.userDataset.serialize()
84-
it[userOptimizer] = job.userOptimizer.serialize()
85-
it[userLoss] = job.userLoss.serialize()
86-
it[userMetrics] = klaxon.toJsonString(job.userMetrics)
87-
it[userEpochs] = job.userEpochs
88-
it[generateDebugComments] = job.generateDebugComments
93+
fun update(job: Job) {
94+
transaction(database) {
95+
Jobs.update({ Jobs.id eq job.id }) {
96+
it[name] = job.name
97+
it[status] = job.status.serialize()
98+
it[userOldModelPath] = job.userOldModelPath
99+
it[userNewModelName] = job.userNewModelName
100+
it[userDataset] = job.userDataset.serialize()
101+
it[userOptimizer] = job.userOptimizer.serialize()
102+
it[userLoss] = job.userLoss.serialize()
103+
it[userMetrics] = klaxon.toJsonString(job.userMetrics)
104+
it[userEpochs] = job.userEpochs
105+
it[generateDebugComments] = job.generateDebugComments
106+
}
89107
}
108+
109+
observers.forEach { it(job) }
90110
}
91111

92112
fun count(): Int = transaction(database) {
@@ -111,9 +131,11 @@ class JobDb(private val database: Database) {
111131
.firstOrNull()
112132
}
113133

114-
fun remove(id: Int): Int? = transaction(database) {
115-
Jobs.deleteWhere { Jobs.id eq id }
116-
}
134+
fun remove(job: Job) {
135+
transaction(database) {
136+
Jobs.deleteWhere { Jobs.id eq job.id }
137+
}
117138

118-
fun remove(job: Job): Int? = remove(job.id)
139+
observers.forEach { it(job) }
140+
}
119141
}

db/src/test/kotlin/edu/wpi/axon/db/JobDbTest.kt

+8-11
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package edu.wpi.axon.db
33
import edu.wpi.axon.dbdata.nextJob
44
import io.kotlintest.matchers.collections.shouldContainExactly
55
import io.kotlintest.matchers.nulls.shouldBeNull
6-
import io.kotlintest.matchers.nulls.shouldNotBeNull
76
import io.kotlintest.shouldBe
87
import java.io.File
98
import java.nio.file.Paths
@@ -21,23 +20,21 @@ internal class JobDbTest {
2120
val db = createDb(tempDir)
2221
val job = Random.nextJob()
2322

24-
val id = db.create(job)
25-
id.shouldNotBeNull()
23+
val newJob = db.create(job)
2624

2725
transaction {
2826
Jobs.select { Jobs.name eq job.name }
2927
.map { Jobs.toDomain(it) }
30-
.shouldContainExactly(job.copy(id = id))
28+
.shouldContainExactly(newJob)
3129
}
3230
}
3331

3432
@Test
3533
fun `update test`(@TempDir tempDir: File) {
3634
val db = createDb(tempDir)
37-
val id = db.create(Random.nextJob())
38-
val job = db.getById(id!!)
35+
val job = db.create(Random.nextJob())
3936

40-
job!!.name = "Test"
37+
job.name = "Test"
4138
db.update(job)
4239

4340
transaction {
@@ -52,9 +49,9 @@ internal class JobDbTest {
5249
val db = createDb(tempDir)
5350
val job = Random.nextJob()
5451

55-
val id = db.create(job)!!
52+
val newJob = db.create(job)
5653

57-
db.findByName(job.name).shouldBe(job.copy(id = id))
54+
db.findByName(job.name).shouldBe(newJob)
5855
}
5956

6057
@Test
@@ -73,9 +70,9 @@ internal class JobDbTest {
7370
val db = createDb(tempDir)
7471
val job = Random.nextJob()
7572

76-
val id = db.create(job)!!
73+
val newJob = db.create(job)
7774

78-
db.remove(id).shouldBe(id)
75+
db.remove(newJob)
7976
db.findByName(job.name).shouldBeNull()
8077
}
8178

ui-vaadin/src/main/kotlin/edu/wpi/axon/ui/FrontendKoinModules.kt

-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import edu.wpi.axon.aws.findAxonS3Bucket
66
import edu.wpi.axon.aws.preferences.LocalPreferencesManager
77
import edu.wpi.axon.aws.preferences.PreferencesManager
88
import edu.wpi.axon.db.JobDb
9-
import edu.wpi.axon.ui.service.JobProvider
109
import java.nio.file.Paths
1110
import org.jetbrains.exposed.sql.Database
1211
import org.koin.core.qualifier.named
@@ -24,8 +23,6 @@ fun defaultFrontendModule() = module {
2423
)
2524
}
2625

27-
single { JobProvider(get()) }
28-
2926
single {
3027
val boundBucketName = get<String?>(named(axonBucketName))
3128
if (boundBucketName != null) {

ui-vaadin/src/main/kotlin/edu/wpi/axon/ui/MainLayout.kt

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import com.vaadin.flow.component.UI
1515
import com.vaadin.flow.component.applayout.AppLayout
1616
import com.vaadin.flow.component.icon.VaadinIcon
1717
import com.vaadin.flow.component.orderedlayout.FlexComponent
18+
import com.vaadin.flow.component.page.Push
1819
import com.vaadin.flow.router.RouterLink
1920
import com.vaadin.flow.server.PWA
2021
import com.vaadin.flow.theme.Theme
@@ -27,6 +28,7 @@ import kotlin.reflect.KClass
2728
/**
2829
* The main layout of the application.
2930
*/
31+
@Push
3032
@Theme(value = Lumo::class)
3133
@PWA(name = "Axon", shortName = "axon")
3234
class MainLayout : AppLayout() {

ui-vaadin/src/main/kotlin/edu/wpi/axon/ui/WebAppListener.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class WebAppListener : ServletContextListener, KoinComponent {
6565
// TODO: Encapsulate this somewhere better
6666
fun loadModel(modelName: String): Pair<Model, String> {
6767
val localModelPath =
68-
Paths.get("/home/salmon/Documents/Axon/training/src/test/resources/edu/wpi/axon/training/$modelName")
68+
Paths.get("/Users/austinshalit/git/Axon/training/src/test/resources/edu/wpi/axon/training/$modelName")
6969
.toString()
7070
val layers =
7171
ModelLoaderFactory().createModeLoader(localModelPath).load(File(localModelPath))

ui-vaadin/src/main/kotlin/edu/wpi/axon/ui/service/JobProvider.kt

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
11
package edu.wpi.axon.ui.service
22

3+
import com.vaadin.flow.component.UI
34
import com.vaadin.flow.data.provider.AbstractBackEndDataProvider
45
import com.vaadin.flow.data.provider.Query
56
import edu.wpi.axon.db.JobDb
67
import edu.wpi.axon.dbdata.Job
8+
import edu.wpi.axon.ui.view.HasNotifications
79
import java.util.stream.Stream
10+
import org.koin.core.KoinComponent
11+
import org.koin.core.inject
812

9-
class JobProvider(
10-
private val jobs: JobDb
11-
) : AbstractBackEndDataProvider<Job, Void>() {
13+
class JobProvider : AbstractBackEndDataProvider<Job, Void>(), KoinComponent, HasNotifications {
14+
15+
private val jobs: JobDb by inject()
16+
17+
init {
18+
val ui = UI.getCurrent()
19+
jobs.subscribe {
20+
ui.access {
21+
refreshItem(it)
22+
}
23+
}
24+
}
1225

1326
override fun sizeInBackEnd(query: Query<Job, Void>?): Int {
1427
return jobs.count()

ui-vaadin/src/main/kotlin/edu/wpi/axon/ui/view/jobs/JobsView.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import org.koin.core.inject
3434
class JobsView : KComposite(), HasUrlParameter<Int>, AfterNavigationObserver, EntityView<Job>,
3535
KoinComponent {
3636

37-
private val dataProvider by inject<JobProvider>()
37+
private val dataProvider = JobProvider()
3838
private val jobDb by inject<JobDb>()
3939

4040
override val entityName: String

0 commit comments

Comments
 (0)