Skip to content

Commit

Permalink
Merge pull request #2571 from bardsoftware/tkt-2514-make-default-colu…
Browse files Browse the repository at this point in the history
…mns-not-calculated

Calculated columns improvements
  • Loading branch information
dbarashev authored Oct 7, 2024
2 parents 7e41341 + 99cb16d commit 5b3cd6b
Show file tree
Hide file tree
Showing 14 changed files with 194 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,12 @@ enum class CustomPropertyClass(val iD: String, val javaClass: Class<*>) {

class CustomPropertyEvent {
val type: Int
var definition: CustomPropertyDefinition
var definition: CustomPropertyDefinition? = null
private set
var oldValue: CustomPropertyDefinition? = null
private set

constructor(type: Int, definition: CustomPropertyDefinition) {
constructor(type: Int, definition: CustomPropertyDefinition?) {
this.type = type
this.definition = definition
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ CREATE TABLE TaskDates(
uid VARCHAR(128) PRIMARY KEY REFERENCES TaskName,
start_date DATE NOT NULL,
duration_days INT NOT NULL DEFAULT 1,
end_date DATE,
earliest_start_date DATE
);

Expand All @@ -42,7 +43,8 @@ CREATE TABLE TaskTextProperties(
CREATE TABLE TaskCostProperties(
uid VARCHAR(128) REFERENCES TaskName PRIMARY KEY,
is_cost_calculated BOOLEAN,
cost_manual_value NUMERIC
cost_manual_value NUMERIC,
cost NUMERIC
);

CREATE TABLE TaskClassProperties(
Expand All @@ -58,10 +60,12 @@ SELECT TaskName.uid,
num,
name,
start_date,
end_date,
duration_days AS duration,
earliest_start_date,
is_cost_calculated,
cost_manual_value,
cost,
is_milestone,
is_project_task,
MAX(TIP.prop_value) FILTER (WHERE TIP.prop_name = 'completion') AS completion,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright 2024 BarD Software s.r.o., Dmitry Barashev.
*
* This file is part of GanttProject, an opensource project management tool.
*
* GanttProject is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GanttProject is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GanttProject. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sourceforge.ganttproject.customProperty

import biz.ganttproject.customproperty.CustomPropertyClass
import biz.ganttproject.customproperty.CustomPropertyEvent
import biz.ganttproject.customproperty.CustomPropertyListener
import net.sourceforge.ganttproject.task.CustomColumnsManager
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

class CustomPropertyImportTest {
@Test
fun testImportDuplicatedProperties() {
run {
val target = CustomColumnsManager()
target.createDefinition(CustomPropertyClass.TEXT, "col1", null)
target.createDefinition(CustomPropertyClass.TEXT, "col2", null)

val source = CustomColumnsManager()
source.createDefinition(CustomPropertyClass.TEXT, "col1", null)
source.createDefinition(CustomPropertyClass.TEXT, "col3", null)

target.importData(source)
val definitions = target.definitions
assertEquals(3, definitions.size)
}
run {
val target = CustomColumnsManager()
target.createDefinition(CustomPropertyClass.TEXT, "col1", null)
target.createDefinition(CustomPropertyClass.TEXT, "col2", null)

val source = CustomColumnsManager()
source.createDefinition(CustomPropertyClass.DATE, "col1", null)
source.createDefinition(CustomPropertyClass.TEXT, "col3", null)

target.importData(source)
val definitions = target.definitions
assertEquals(4, definitions.size)
}
}

@Test
fun `import preserves property id`() {
val target = CustomColumnsManager()
target.createDefinition("col1", CustomPropertyClass.TEXT.iD, "col1", null)
target.createDefinition("col3", CustomPropertyClass.TEXT.iD, "col3", null)

val source = CustomColumnsManager()
source.createDefinition("col2", CustomPropertyClass.TEXT.iD, "col2", null)
source.createDefinition("col3", CustomPropertyClass.TEXT.iD, "Column3", null)

target.importData(source)
val definitions = target.definitions
assertEquals(4, definitions.size)

assertEquals(setOf("col1", "col2", "col3", "tpc0"), definitions.map { it.id }.toSet())
}

@Test
fun `import triggers only one event`() {
val target = CustomColumnsManager()
var eventCount = 0
target.addListener(object: CustomPropertyListener {
override fun customPropertyChange(event: CustomPropertyEvent) {
eventCount++
}
})

val source = CustomColumnsManager()
source.createDefinition(CustomPropertyClass.DATE, "col1", null)
source.createDefinition(CustomPropertyClass.TEXT, "col3", null)

target.importData(source)
assertEquals(1, eventCount)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,6 @@ private static RecordGroup createTaskRecordGroup(final TaskManager taskManager,
return new TaskRecords(taskManager, resourceManager, timeUnitStack);
}

protected static void createCustomProperties(Collection<String> customFields, CustomPropertyManager customPropertyManager) {
for (String name : customFields) {
customPropertyManager.createDefinition(name, CustomPropertyClass.TEXT.getID(), name, null);
}
}

private static RecordGroup createResourceRecordGroup(HumanResourceManager resourceManager, RoleManager roleManager) {
return resourceManager == null ? null : new ResourceRecords(resourceManager, roleManager);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import com.google.common.xml.XmlEscapers
import net.sourceforge.ganttproject.GPLogger
import net.sourceforge.ganttproject.gui.zoom.ZoomManager
import net.sourceforge.ganttproject.task.CostStub
import net.sourceforge.ganttproject.task.CustomColumnsManager
import net.sourceforge.ganttproject.task.Task
import net.sourceforge.ganttproject.task.TaskManager
import net.sourceforge.ganttproject.task.TaskView
Expand All @@ -59,13 +60,15 @@ class TaskLoader(private val taskManager: TaskManager, private val treeCollapseV
private val mapXmlGantt = mutableMapOf<XmlTask, Task>()

fun loadTaskCustomPropertyDefinitions(xmlProject: XmlProject) {
val bufferCustomPropertyManager = CustomColumnsManager()
xmlProject.tasks.taskproperties?.filter { it.type == "custom" }?.forEach { xmlTaskProperty ->
val def = taskManager.customPropertyManager.createDefinition(
val def = bufferCustomPropertyManager.createDefinition(
xmlTaskProperty.id, xmlTaskProperty.valuetype, xmlTaskProperty.name, xmlTaskProperty.defaultvalue)
xmlTaskProperty.simpleSelect?.let {
def.calculationMethod = SimpleSelect(propertyId = xmlTaskProperty.id, selectExpression = StringEscapeUtils.unescapeXml(it.select), resultClass = def.propertyClass.javaClass)
}
}
taskManager.customPropertyManager.importData(bufferCustomPropertyManager)
}

fun loadTask(parent: XmlTask?, child: XmlTask): Task {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@ fun buildInsertTaskQuery(dsl: DSLContext, task: Task): Insert<TaskRecord> {
.set(Tables.TASK.IS_MILESTONE, task.isLegacyMilestone)
.set(Tables.TASK.IS_PROJECT_TASK, task.isProjectTask)
.set(Tables.TASK.START_DATE, task.start.toLocalDate())
.set(Tables.TASK.END_DATE, task.end.toLocalDate())
.set(Tables.TASK.DURATION, task.duration.length)
.set(Tables.TASK.COMPLETION, task.completionPercentage)
.set(Tables.TASK.EARLIEST_START_DATE, task.third?.toLocalDate())
.set(Tables.TASK.PRIORITY, task.priority.persistentValue)
.set(Tables.TASK.WEB_LINK, task.externalizedWebLink())
.set(Tables.TASK.COST_MANUAL_VALUE, costManualValue)
.set(Tables.TASK.COST, task.cost.value)
.set(Tables.TASK.IS_COST_CALCULATED, isCostCalculated)
.set(Tables.TASK.NOTES, task.externalizedNotes())
val customProps = mutableMapOf<Any, Any>()
Expand Down Expand Up @@ -86,13 +88,15 @@ fun buildInsertTaskDto(task: Task): OperationDto.InsertOperationDto {
Tables.TASK.IS_MILESTONE.name to task.isLegacyMilestone.toString(),
Tables.TASK.IS_PROJECT_TASK.name to task.isProjectTask.toString(),
Tables.TASK.START_DATE.name to task.start.toLocalDate().toString(),
Tables.TASK.END_DATE.name to task.end.toLocalDate().toString(),
Tables.TASK.DURATION.name to task.duration.length.toString(),
Tables.TASK.COMPLETION.name to task.completionPercentage.toString(),
Tables.TASK.EARLIEST_START_DATE.name to (task.third?.toLocalDate()?.toString()),
Tables.TASK.PRIORITY.name to task.priority.persistentValue,
Tables.TASK.WEB_LINK.name to task.externalizedWebLink(),
Tables.TASK.COST_MANUAL_VALUE.name to (costManualValue?.toString()),
Tables.TASK.IS_COST_CALCULATED.name to (isCostCalculated?.toString()),
Tables.TASK.COST.name to task.cost.value?.toString(),
Tables.TASK.NOTES.name to task.externalizedNotes(),
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ interface ProjectDatabase {
fun setProjectTask(oldValue: Boolean, newValue: Boolean)
fun setShape(oldValue: ShapePaint?, newValue: ShapePaint?)
fun setStart(oldValue: GanttCalendar, newValue: GanttCalendar)
fun setEnd(oldValue: GanttCalendar?, newValue: GanttCalendar)
fun setWebLink(oldValue: String?, newValue: String?)

fun interface Factory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,10 @@ class SqlTaskUpdateBuilder(private val task: Task,
override fun setStart(oldValue: GanttCalendar, newValue: GanttCalendar) =
appendUpdate(TASK.START_DATE, oldValue.toLocalDate(), newValue.toLocalDate())

override fun setEnd(oldValue: GanttCalendar?, newValue: GanttCalendar) {
appendUpdate(TASK.END_DATE, oldValue?.toLocalDate(), newValue.toLocalDate())
}

override fun setDuration(oldValue: TimeDuration, newValue: TimeDuration) =
appendUpdate(TASK.DURATION, oldValue.length, newValue.length)

Expand All @@ -504,6 +508,7 @@ class SqlTaskUpdateBuilder(private val task: Task,
override fun setCost(oldValue: Task.Cost, newValue: Task.Cost) {
appendUpdate(TASK.IS_COST_CALCULATED, oldValue.isCalculated, newValue.isCalculated)
appendUpdate(TASK.COST_MANUAL_VALUE, oldValue.manualValue, newValue.manualValue)
appendUpdate(TASK.COST, oldValue.value, newValue.value)
}

override fun setCustomProperties(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,17 @@ class CustomColumnsManager : CustomPropertyManager {
private val listeners = mutableListOf<CustomPropertyListener>()
private val mapIdCustomColum = mutableMapOf<String, CustomColumn>()
private var nextId = 0
private var isImporting = false

private fun addNewCustomColumn(customColumn: CustomColumn) {
private fun addNewCustomColumn(customColumn: CustomColumn, fireChange: Boolean) {
if (mapIdCustomColum[customColumn.id] != null) {
throw CustomColumnsException(CustomColumnsException.ALREADY_EXIST, "Column with ID=${customColumn.id} is already registered")
}
mapIdCustomColum[customColumn.id] = customColumn
val event = CustomPropertyEvent(CustomPropertyEvent.EVENT_ADD, customColumn)
fireCustomColumnsChange(event)
if (fireChange) {
val event = CustomPropertyEvent(CustomPropertyEvent.EVENT_ADD, customColumn)
fireCustomColumnsChange(event)
}
}

override fun addListener(listener: CustomPropertyListener) {
Expand All @@ -56,32 +59,40 @@ class CustomColumnsManager : CustomPropertyManager {
val stub = decodeTypeAndDefaultValue(typeAsString, defaultValueAsString)
val result = CustomColumn(this, name, stub.propertyClass, stub.defaultValue)
result.id = id
addNewCustomColumn(result)
addNewCustomColumn(result, true)
return result
}

override fun createDefinition(propertyClass: CustomPropertyClass, colName: String, defValue: String?): CustomPropertyDefinition {
val stub = create(propertyClass, defValue)
val result = CustomColumn(this, colName, stub.propertyClass, stub.defaultValue)
result.id = createId()
addNewCustomColumn(result)
addNewCustomColumn(result, true)
return result
}

override fun importData(source: CustomPropertyManager): Map<CustomPropertyDefinition, CustomPropertyDefinition> {
val result = mutableMapOf<CustomPropertyDefinition, CustomPropertyDefinition>()
for (thatColumn in source.definitions) {
var thisColumn = findByName(thatColumn.name)
if (thisColumn == null || thisColumn.propertyClass != thatColumn.propertyClass) {
thisColumn = CustomColumn(this, thatColumn.name, thatColumn.propertyClass, thatColumn.defaultValue)
thisColumn.id = createId()
thisColumn.attributes.putAll(thatColumn.attributes)
addNewCustomColumn(thisColumn)
override fun importData(source: CustomPropertyManager): Map<CustomPropertyDefinition, CustomPropertyDefinition> =
try {
isImporting = true
val result = mutableMapOf<CustomPropertyDefinition, CustomPropertyDefinition>()
for (thatColumn in source.definitions) {
var thisColumn = findByName(thatColumn.name)
if (thisColumn == null || thisColumn.propertyClass != thatColumn.propertyClass) {
thisColumn = CustomColumn(this, thatColumn.name, thatColumn.propertyClass, thatColumn.defaultValue)
thisColumn.id = findById(thatColumn.id)?.let { createId() } ?: thatColumn.id
thisColumn.attributes.putAll(thatColumn.attributes)
thisColumn.calculationMethod = thatColumn.calculationMethod
addNewCustomColumn(thisColumn, false)
}
result[thatColumn] = thisColumn
}
result[thatColumn] = thisColumn
result
} finally {
isImporting = false
val event = CustomPropertyEvent(CustomPropertyEvent.EVENT_REBUILD, null)
fireCustomColumnsChange(event)
}
return result
}


override fun getCustomPropertyDefinition(id: String): CustomPropertyDefinition? {
return mapIdCustomColum[id]
Expand All @@ -99,21 +110,26 @@ class CustomColumnsManager : CustomPropertyManager {
}

private fun fireCustomColumnsChange(event: CustomPropertyEvent) {
listeners.forEach {
try {
it.customPropertyChange(event)
} catch (ex: Exception) {
LOG.error("Failure when processing custom columns event", exception = ex)
if (!isImporting) {
listeners.forEach {
try {
it.customPropertyChange(event)
} catch (ex: Exception) {
LOG.error("Failure when processing custom columns event", exception = ex)
}
}
}
}

fun fireDefinitionChanged(event: Int, def: CustomColumn, oldDef: CustomColumn) {
val e = CustomPropertyEvent(event, def, oldDef)
fireCustomColumnsChange(e)
if (!isImporting) {
val e = CustomPropertyEvent(event, def, oldDef)
fireCustomColumnsChange(e)
}
}

private fun findByName(name: String) = mapIdCustomColum.values.find { it.name == name }
private fun findById(id: String) = mapIdCustomColum.values.find { it.id == id }
private fun createId(): String {
while (true) {
val id = "$ID_PREFIX${nextId++}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ internal open class MutatorImpl(
if (taskImpl.duration != myDurationChange.oldValue) {
taskUpdateBuilder.setDuration(myDurationChange.oldValue, taskImpl.duration)
}
if (taskImpl.end != myEndChange.oldValue) {
taskUpdateBuilder.setEnd(myEndChange.oldValue, taskImpl.end)
}
}
}

Expand Down
Loading

0 comments on commit 5b3cd6b

Please sign in to comment.