joniles / mpxj

Primary repository for MPXJ library

Home Page:http://www.mpxj.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Get UserDefinedFields (UDF) assigned to Task

alex-matatov opened this issue · comments

Hi Jon,

I am looking for a way to get all UDFs assigned to a particular Task. Basically, I found out a solution but it is inefficient and inconvenient.

There is project.getUserDefinedFields().getTaskFields() method which returns all UDF definitions. If we would like to get UDFs assigned to a Task we need to iterate over all UDFs and call task.getFieldByAlias(udfName) to check if returned value is not null.

My question, is there another way how to do that? If not would it be possible to add a new Task's getUserDefinedFields() method similar to Task.getActivityCodes() ?

Regards,
Alex

The first thing we need to do is determine what UDFs or custom fields we have. These are slightly different concepts depending on where your schedule data is coming from (P6 only has UDFs, desktop Microsoft Project only uses custom fields, Microsoft Project Professional with Microsoft Project Server can have both custom fields and UDFs, in the form of Enterprise Custom Fields). I'll assume that we're after a generic solution which will work with schedule data from any source.

The code below opens a schedule file, and creates a set containing details of all task UDFs and task custom fields in the schedule.

// Read our schedule data
ProjectFile file = new UniversalProjectReader().read("my.schedule.file");

// Determine what task UDF and custom fields we have.
// First find all custom fields which have been configured.
Set<FieldType> fields = file.getTasks().getCustomFields().stream().map(CustomField::getFieldType).filter(Objects::nonNull).collect(Collectors.toSet());

// Add all the UDFs...
fields.addAll(file.getUserDefinedFields().stream().filter(f -> f.getFieldTypeClass() == FieldTypeClass.TASK).collect(Collectors.toSet()));

// Finally, add all custom fields with values.
// This last step will pick up any custom fields in use which don't have user configuration
// (for example, in Microsoft Project the user is working with Text1, and hasn't
// changed its name or added any other configuration).
fields.addAll(file.getTasks().getPopulatedFields().stream().filter(FieldLists.CUSTOM_FIELDS::contains).collect(Collectors.toSet()));

Now we have the set of fields we're interested in, we can retrieve the their values. To do this we're using the get method, and passing in a FieldType instance from the set we've just created, which is more efficient than looking up the field by alias. In the example code below I'm producing a map of field values for each task, keyed by field type.

for (Task task : file.getTasks())
{
   Map<FieldType, Object> values = fields.stream().collect(Collectors.toMap(f -> f, task::get));
   // your code here to use these values
}

My question, is there another way how to do that?

The code I've outlined above is the most efficient way to access field values, given a set of field types you want to work with.

If not would it be possible to add a new Task's getUserDefinedFields() method similar to Task.getActivityCodes() ?

This wouldn't be any more efficient that the code noted above. Internally the field values are stored in a Map instance. Retrieving values from that Map given of field types would require us to stream and filter the Map, or duplicate it and use retainAll to produce the subset we're interested in. The "gotcha" with this approach is that it doesn't handle calculated fields: some of the fields you could potentially ask to be retrieved might be calculated "on the fly" by MPXJ if it doesn't already have a value present. Working with the get method as shown in the sample code above handles these calculations.

I suspect you may already have seen these notes: https://www.mpxj.org/howto-use-fields/, if not you may find some useful background detail there.

Thank you SO much! The approach above works for me.