Skip to content

Commit 5530797

Browse files
authored
Merge pull request #74 from SimpleTimeTracking/68-activity-report-show-uncovered-time-also-as-rounded
resolves #68 show uncovered time also as "rounded" in activity report
2 parents 2847597 + 346376e commit 5530797

File tree

13 files changed

+107
-90
lines changed

13 files changed

+107
-90
lines changed

build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ dependencies {
8282
implementation(kotlin("stdlib-jdk8"))
8383

8484
testImplementation("commons-io:commons-io:2.8.0")
85-
testImplementation("org.mockito:mockito-core:3.12.4")
85+
testImplementation("org.mockito:mockito-core:4.5.1")
86+
testImplementation("org.mockito.kotlin:mockito-kotlin:4.1.0")
8687
testImplementation("org.assertj:assertj-core:3.18.1")
8788
testImplementation("junit:junit-dep:4.11")
8889
}

src/main/kotlin/org/stt/cli/CLIApplication.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ import org.stt.config.ConfigServiceFacade
88
import org.stt.persistence.BackupCreator
99
import org.stt.persistence.stt.STTPersistenceModule
1010
import org.stt.text.TextModule
11+
import org.stt.time.TimeUtilModule
1112

1213
import javax.inject.Singleton
1314

1415
@Singleton
15-
@Component(modules = [STTPersistenceModule::class, ConfigModule::class, BaseModule::class, TextModule::class, CommandModule::class])
16+
@Component(modules = [STTPersistenceModule::class, ConfigModule::class, BaseModule::class, TextModule::class, CommandModule::class, TimeUtilModule::class])
1617
interface CLIApplication {
1718
fun backupCreator(): BackupCreator
1819

src/main/kotlin/org/stt/cli/ReportPrinter.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import org.stt.reporting.WorkingtimeItemProvider
1414
import org.stt.text.ItemCategorizer
1515
import org.stt.text.ItemCategorizer.ItemCategory
1616
import org.stt.time.DateTimes
17+
import org.stt.time.DurationRounder
1718
import org.stt.time.until
1819
import java.io.PrintStream
1920
import java.time.Duration
@@ -28,7 +29,8 @@ class ReportPrinter @Inject
2829
constructor(private val queries: TimeTrackingItemQueries,
2930
private val configuration: CliConfig,
3031
private val workingtimeItemProvider: WorkingtimeItemProvider,
31-
private val categorizer: ItemCategorizer) {
32+
private val categorizer: ItemCategorizer,
33+
private val rounder: DurationRounder) {
3234

3335
fun report(args: MutableCollection<String>, printTo: PrintStream) {
3436
var searchString: String? = null
@@ -133,7 +135,7 @@ constructor(private val queries: TimeTrackingItemQueries,
133135
criteria.withStartBetween(reportStart!! until reportEnd)
134136

135137
queries.queryItems(criteria).use { itemsToConsider ->
136-
val reporter = SummingReportGenerator(itemsToConsider, categorizer )
138+
val reporter = SummingReportGenerator(itemsToConsider, categorizer, rounder )
137139
val report = reporter.createReport()
138140

139141
if (DateTimes.isToday(reportStart)) {
@@ -153,7 +155,7 @@ constructor(private val queries: TimeTrackingItemQueries,
153155

154156
var worktimeDuration = Duration.ZERO
155157
var breakTimeDuration = Duration.ZERO
156-
for ((duration, comment) in reportingItems) {
158+
for ((duration, _, comment) in reportingItems) {
157159
var prefix = " "
158160
if (ItemCategory.BREAK == categorizer.getCategory(comment)) {
159161
prefix = "*"

src/main/kotlin/org/stt/gui/jfx/ReportController.kt

Lines changed: 33 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,15 @@ internal constructor(private val localization: ResourceBundle,
7777
@FXML
7878
private lateinit var endOfReport: Label
7979
@FXML
80-
private lateinit var uncoveredTime: Label
80+
private lateinit var uncoveredDuration: Label
8181
@FXML
82-
private lateinit var breakTime: Label
82+
private lateinit var breakDuration: Label
8383
@FXML
84-
private lateinit var nonEffectiveTime: Label
84+
private lateinit var nonEffectiveDuration: Label
8585
@FXML
86-
private lateinit var roundedDurationTime: Label
86+
private lateinit var totalDuration: Label
8787
@FXML
88-
private lateinit var effectiveDurationTime: Label
88+
private lateinit var effectiveDuration: Label
8989

9090
private lateinit var datePicker: DatePicker
9191

@@ -118,44 +118,39 @@ internal constructor(private val localization: ResourceBundle,
118118

119119
val reportModel = createReportModel()
120120
eventBus.subscribe(OnItemChangeListener(reportModel))
121-
122-
123-
124121
val reportListModel = createReportingItemsListModel(reportModel)
125122

126-
// 1. rounded Duration
127-
roundedDurationTime
128-
.textProperty()
129-
.bind(STTBindings
130-
.formattedDuration(createBindingForRoundedDurationTime(reportListModel)))
131-
132123
// 3. uncovered duration
133-
val uncoveredTimeBinding = createBindingForUncoveredTime(reportModel)
134-
uncoveredTime.textProperty().bind(STTBindings
135-
.formattedDuration(uncoveredTimeBinding))
124+
val uncoveredDurationBinding = createBindingForUncoveredDuration(reportModel)
125+
uncoveredDuration.textProperty().bind(STTBindings
126+
.formattedDuration(uncoveredDurationBinding))
136127

137-
val uncoveredTimeTextFillBinding = When(
138-
uncoveredTimeBinding.isEqualTo(Duration.ZERO)).then(
128+
val uncoveredDurationTextFillBinding = When(
129+
uncoveredDurationBinding.isEqualTo(Duration.ZERO)).then(
139130
Color.BLACK).otherwise(Color.RED)
140-
uncoveredTime.textFillProperty().bind(uncoveredTimeTextFillBinding)
141-
131+
uncoveredDuration.textFillProperty().bind(uncoveredDurationTextFillBinding)
142132

143133
// 4. break duration
144-
val breakTimeBinding = createBindingForBreakTime(reportListModel)
145-
146-
breakTime.textProperty().bind(STTBindings.formattedDuration(breakTimeBinding))
147-
134+
val breakDurationBinding = createBindingForBreakDuration(reportListModel)
148135

149-
breakTimeBinding.value.plus(uncoveredTimeBinding.value)
136+
breakDuration.textProperty().bind(STTBindings.formattedDuration(breakDurationBinding))
150137

151138
// 2. non Duration
152-
nonEffectiveTime
139+
val nonEffectiveDurationBinding = createPlusBinding(uncoveredDurationBinding, breakDurationBinding)
140+
nonEffectiveDuration
153141
.textProperty()
154142
.bind(STTBindings
155-
.formattedDuration(createBindingForNonEffectiveTime(uncoveredTimeBinding, breakTimeBinding)))
143+
.formattedDuration(nonEffectiveDurationBinding))
156144

157145
// 5. effective duration
158-
effectiveDurationTime.textProperty().bind(STTBindings.formattedDuration(createBindingForEffectiveDurationTime(reportListModel)))
146+
val effectiveDurationBinding = createBindingForEffectiveDuration(reportListModel)
147+
effectiveDuration.textProperty().bind(STTBindings.formattedDuration(effectiveDurationBinding))
148+
149+
// 1. total duration
150+
totalDuration
151+
.textProperty()
152+
.bind(STTBindings
153+
.formattedDuration(createPlusBinding(effectiveDurationBinding, nonEffectiveDurationBinding)))
159154

160155
// start time
161156
val startBinding = createBindingForStartOfReport(reportModel)
@@ -221,7 +216,7 @@ internal constructor(private val localization: ResourceBundle,
221216
else
222217
null
223218
}, datePicker.valueProperty())
224-
return ReportBinding(datePicker.valueProperty(), nextDay, itemCategorizer, timeTrackingItemQueries)
219+
return ReportBinding(datePicker.valueProperty(), nextDay, itemCategorizer, rounder, timeTrackingItemQueries)
225220
}
226221

227222
private fun createReportingItemsListModel(
@@ -239,26 +234,16 @@ internal constructor(private val localization: ResourceBundle,
239234
}, report)
240235
}
241236

242-
private fun createBindingForNonEffectiveTime(uncoveredTime: ObservableValue<Duration>, breakTime: ObservableValue<Duration> ): ObjectBinding<Duration> {
243-
244-
return Bindings.createObjectBinding({uncoveredTime.value.plus(breakTime.value)}, uncoveredTime, breakTime)
245-
}
246-
247-
248-
private fun createBindingForRoundedDurationTime(
249-
items: ObservableList<ReportListItem>): ObjectBinding<Duration> {
250-
return Bindings.createObjectBinding(Callable {
251-
items.map { it.roundedDuration }
252-
.foldRight(Duration.ZERO) { obj, duration -> obj.plus(duration) }
253-
}, items)
237+
private fun createPlusBinding(operand1: ObservableValue<Duration>, operand2: ObservableValue<Duration> ): ObjectBinding<Duration> {
238+
return Bindings.createObjectBinding({operand1.value.plus(operand2.value)}, operand1, operand2)
254239
}
255240

256-
private fun createBindingForUncoveredTime(
241+
private fun createBindingForUncoveredDuration(
257242
reportModel: ObservableValue<Report>): ObjectBinding<Duration> {
258-
return Bindings.createObjectBinding(Callable { reportModel.value.uncoveredDuration }, reportModel)
243+
return Bindings.createObjectBinding(Callable { reportModel.value.roundedUncoveredDuration }, reportModel)
259244
}
260245

261-
private fun createBindingForEffectiveDurationTime(
246+
private fun createBindingForEffectiveDuration(
262247
items: ObservableList<ReportListItem>): ObjectBinding<Duration> {
263248
return Bindings.createObjectBinding(Callable {
264249
items
@@ -268,7 +253,7 @@ internal constructor(private val localization: ResourceBundle,
268253
}, items)
269254
}
270255

271-
private fun createBindingForBreakTime(
256+
private fun createBindingForBreakDuration(
272257
items: ObservableList<ReportListItem>): ObjectBinding<Duration> {
273258
return Bindings.createObjectBinding(Callable {
274259
items
@@ -321,7 +306,7 @@ internal constructor(private val localization: ResourceBundle,
321306
column.setCellFactory { param ->
322307
val tableCell = TableColumn.DEFAULT_CELL_FACTORY.call(param) as TableCell<ReportListItem, String>
323308
tableCell.setOnMouseClicked { event ->
324-
val item = tableCell.tableRow.item as? ReportListItem ?: return@setOnMouseClicked
309+
val item = tableCell.tableRow.item ?: return@setOnMouseClicked
325310
clickHandler.accept(item, event)
326311
}
327312
tableCell
@@ -401,7 +386,7 @@ internal constructor(private val localization: ResourceBundle,
401386
graphic = textFlow
402387

403388
setOnMouseClicked {
404-
val item = this@ActivityTableCell.tableRow.item as? ReportListItem ?: return@setOnMouseClicked
389+
val item = this@ActivityTableCell.tableRow.item ?: return@setOnMouseClicked
405390
setClipboard(item.comment)
406391
}
407392
}

src/main/kotlin/org/stt/gui/jfx/binding/ReportBinding.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ import org.stt.query.TimeTrackingItemQueries
1212
import org.stt.reporting.SummingReportGenerator
1313
import org.stt.reporting.SummingReportGenerator.Report
1414
import org.stt.text.ItemCategorizer
15+
import org.stt.time.DurationRounder
1516
import org.stt.time.until
1617
import java.time.Duration
1718
import java.time.LocalDate
1819

1920
class ReportBinding(val reportStart: ObservableValue<LocalDate>,
2021
val reportEnd: ObservableValue<LocalDate>,
2122
val itemCategorizer: ItemCategorizer,
23+
val rounder: DurationRounder,
2224
val queries: TimeTrackingItemQueries) : ObjectBinding<Report>() {
2325

2426
init {
@@ -30,12 +32,12 @@ class ReportBinding(val reportStart: ObservableValue<LocalDate>,
3032
null && reportEnd.value != null) {
3133
createSummaryReportFor()
3234
} else {
33-
Report(emptyList(), null, null, Duration.ZERO)
35+
Report(emptyList(), null, null, Duration.ZERO, Duration.ZERO)
3436
}
3537

3638
private fun createSummaryReportFor(): Report {
3739
val criteria = Criteria()
3840
criteria.withStartBetween(reportStart.value.atStartOfDay() until reportEnd.value.atStartOfDay())
39-
queries.queryItems(criteria).use { items -> return SummingReportGenerator(items, itemCategorizer).createReport() }
41+
queries.queryItems(criteria).use { items -> return SummingReportGenerator(items, itemCategorizer, rounder).createReport() }
4042
}
4143
}

src/main/kotlin/org/stt/model/ReportingItem.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ package org.stt.model
33

44
import java.time.Duration
55

6-
data class ReportingItem(val duration: Duration, val comment: String, val isBreak: Boolean) {
6+
data class ReportingItem(val duration: Duration, val roundedDuration: Duration, val comment: String, val isBreak: Boolean) {
77
override fun toString() = "$duration $comment ${if (isBreak) "(break)" else ""}"
88
}

src/main/kotlin/org/stt/reporting/SummingReportGenerator.kt

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.stt.reporting
33
import org.stt.model.ReportingItem
44
import org.stt.model.TimeTrackingItem
55
import org.stt.text.ItemCategorizer
6+
import org.stt.time.DurationRounder
67
import java.time.Duration
78
import java.time.LocalDateTime
89
import java.util.*
@@ -22,16 +23,16 @@ import java.util.stream.Stream
2223
*/
2324
class SummingReportGenerator(
2425
private val itemsToRead: Stream<TimeTrackingItem>,
25-
private val itemCategorizer: ItemCategorizer
26+
private val itemCategorizer: ItemCategorizer,
27+
private val rounder: DurationRounder
2628
) {
2729

2830
fun createReport(): Report {
2931
var startOfReport: LocalDateTime? = null
3032
var endOfReport: LocalDateTime? = null
31-
val reportList = LinkedList<ReportingItem>()
3233

3334

34-
val collectingMap = HashMap<String, Duration>()
35+
val reportItems = HashMap<String, ReportingItem>()
3536
var uncoveredDuration = Duration.ZERO
3637
var lastItem: TimeTrackingItem? = null
3738
itemsToRead.use { items ->
@@ -64,26 +65,35 @@ class SummingReportGenerator(
6465
if (duration.isNegative) {
6566
duration = Duration.ZERO
6667
}
68+
// assemble
6769
val comment = item.activity
68-
if (collectingMap.containsKey(comment)) {
69-
val oldDuration = collectingMap[comment]
70-
collectingMap[comment] = oldDuration!!.plus(duration)
71-
} else {
72-
collectingMap[comment] = duration
70+
71+
72+
val currentItem = reportItems.getOrPut(comment) {
73+
ReportingItem(
74+
Duration.ZERO,
75+
Duration.ZERO,
76+
comment,
77+
ItemCategorizer.ItemCategory.BREAK == itemCategorizer.getCategory(comment)
78+
)
7379
}
80+
// overwrite currentItem in the collection and update values
81+
val newDuration = currentItem.duration.plus(duration)
82+
reportItems.put(
83+
comment, currentItem.copy(
84+
duration = newDuration,
85+
roundedDuration = rounder.roundDuration(newDuration)
86+
)
87+
)
7488
}
7589

7690
}
7791

78-
for ((comment, duration) in collectingMap) {
79-
val isBreak = ItemCategorizer.ItemCategory.BREAK == itemCategorizer.getCategory(comment)
80-
reportList.add(ReportingItem(duration, comment, isBreak))
81-
}
82-
83-
reportList.sortWith(comparing<ReportingItem, String> { it.comment })
92+
val reportList = LinkedList(reportItems.values.toList())
93+
reportList.sortWith(comparing { it.comment })
8494
return Report(
8595
reportList, startOfReport, endOfReport,
86-
uncoveredDuration
96+
uncoveredDuration, rounder.roundDuration(uncoveredDuration)
8797
)
8898
}
8999

@@ -92,6 +102,7 @@ class SummingReportGenerator(
92102
val reportingItems: List<ReportingItem>,
93103
val start: LocalDateTime?,
94104
val end: LocalDateTime?,
95-
val uncoveredDuration: Duration
105+
val uncoveredDuration: Duration,
106+
val roundedUncoveredDuration: Duration
96107
)
97108
}

src/main/kotlin/org/stt/time/DurationRounder.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import javax.inject.Singleton
77
@Singleton
88
class DurationRounder {
99

10-
private var intervalMillis: Long = 0
11-
private var tieBreakMillis: Long = 0
10+
private var intervalMillis: Long = 1
11+
private var tieBreakMillis: Long = 1
1212

1313
fun setInterval(interval: Duration) {
1414
intervalMillis = interval.toMillis()

src/main/resources/org/stt/gui/jfx/ReportPanel.fxml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,21 +43,21 @@
4343
<Font name="System Bold" size="12.0" />
4444
</font>
4545
</Label>
46-
<Label fx:id="roundedDurationTime" text="roundedDurationTime" GridPane.columnIndex="1" GridPane.columnSpan="3" />
46+
<Label fx:id="totalDuration" text="totalDuration" GridPane.columnIndex="1" GridPane.columnSpan="3" />
4747
<!-- -->
4848
<Label text="%report.nonEffectiveTime" GridPane.rowIndex="1">
4949
<font>
5050
<Font name="System Bold" size="12.0" />
5151
</font>
5252
</Label>
53-
<Label fx:id="nonEffectiveTime" text="nonEffectiveTime" GridPane.columnIndex="2" GridPane.columnSpan="2" GridPane.rowIndex="1" />
53+
<Label fx:id="nonEffectiveDuration" text="nonEffectiveDuration" GridPane.columnIndex="2" GridPane.columnSpan="2" GridPane.rowIndex="1" />
5454
<!-- -->
5555
<Label text="%report.uncoveredTime" GridPane.rowIndex="2">
5656
<font>
5757
<Font name="System Bold" size="12.0" />
5858
</font>
5959
</Label>
60-
<Label fx:id="uncoveredTime" text="uncoveredTime" GridPane.columnIndex="3" GridPane.rowIndex="2" />
60+
<Label fx:id="uncoveredDuration" text="uncoveredDuration" GridPane.columnIndex="3" GridPane.rowIndex="2" />
6161
<!-- -->
6262

6363

@@ -66,14 +66,14 @@
6666
<Font name="System Bold" size="12.0" />
6767
</font>
6868
</Label>
69-
<Label fx:id="breakTime" text="breakTime" GridPane.columnIndex="3" GridPane.rowIndex="3" />
69+
<Label fx:id="breakDuration" text="breakDuration" GridPane.columnIndex="3" GridPane.rowIndex="3" />
7070

7171
<Label text="%report.effectiveDurationTime" GridPane.rowIndex="4">
7272
<font>
7373
<Font name="System Bold" size="12.0" />
7474
</font>
7575
</Label>
76-
<Label fx:id="effectiveDurationTime" text="effectiveDurationTime" GridPane.columnIndex="2" GridPane.columnSpan="2" GridPane.rowIndex="4" />
76+
<Label fx:id="effectiveDuration" text="effectiveDuration" GridPane.columnIndex="2" GridPane.columnSpan="2" GridPane.rowIndex="4" />
7777
<!-- -->
7878
<Label text="%report.startOfReport" GridPane.rowIndex="6">
7979
<font>

0 commit comments

Comments
 (0)