Question

I'm creating an SPFx web part that reads tasks from one project file stored in Project Server, gets some values from those tasks (including the values of some local custom fields), and then creates new tasks in a different project on Project Server, in which the values of the local custom fields are stored in enterprise custom fields.

Because I couldn't get everything I needed to do working using the REST API directly, I resorted to using the Project Server JSOM library (PS.js), which I was able to get working in SPFx.

Now I'm able to create new tasks in the project using the JSOM syntax, which includes creating a new PS.TaskCreationInformation object, setting some properties on that object, and then adding that to the task collection in a draft project. However, the options for what properties you can set on a TaskCreationInformation object are limited. So I need to subsequently update the task's enterprise custom fields with the correct values.

I can't seem to find any information on how to do this. All of the things I've found online about "updating enterprise custom fields" are all about updating what I would consider the field definition on the project itself, not about updating the values for a particular task.

Here's the code I have so far:

private createDraftTasks = (tasks: any[]): Promise<any[]> => {
    return new Promise<any[]> ((resolve, reject) => {
        if (tasks.length > 0) {
            const task = tasks.shift();
            const newTaskInfo = new PS.TaskCreationInformation();
            newTaskInfo.set_name(task.Name);
            newTaskInfo.set_start(task.Start);
            newTaskInfo.set_finish(task.Finish);
            newTaskInfo.set_duration(task.Duration);
            const draftTask = this.draftProj.get_tasks().add(newTaskInfo);
            this.psCtx.load(draftTask);
            this.psCtx.executeQueryAsync(() => {
                // i figure this is where i would need to add the values, since
                // i can't really do it on the TaskCreationInformation object, can I?
                // or is there a way to do it that way?

                // i've tried setting it this way, but this doesn't seem to work:
                draftTask.MyEnterpriseCustomField = task.localCustomFieldValue;

                // and I've tried setting it this way, and this defintiely throws an error:
                draftTask.set_MyEnterpriseCustomField(task.localCustomFieldValue);

                // also with camelCasing the field name, still throws an error:
                draftTask.set_myEnterpriseCustomField(task.localCustomFieldValue);

                const updateJob = this.draftProj.update();
                this.psCtx.waitForQueueAsync(updateJob, 5, (status) => {
                    if (status === 4) {
                        this.createDraftTasks(tasks).then((result: any[]) => {
                            resolve([draftTask, ...result]);
                        }).catch(err => {
                            console.log(`fail after task "${task.Name}"`, err);
                        });
                    } else {
                        console.log(`update fail after "${task.Name}" with code ${status}`);
                    }
                });
            }, (sender, args) => {
                console.log(`task "${task.Name}" fail`);
                console.log(sender);
                console.log(args);
            });
        } else {
            resolve([]);
        }
    });
}

What is the correct Project Server JSOM syntax for setting the value of an enterprise custom field on a particular task?

Was it helpful?

Solution 2

Ok, I finally found the answer in this post. The way to set an enterprise custom field value on a task is

draftTask.set_item('Custom Field Internal Name', 'your value');

If you know the GUID of the draft task, you can get it by doing

draftProject.get_tasks().getByGuid('guid');

however in my case, I am creating new tasks, so I can't know the GUID until I ctx.load() the task. That meant figuring out a complicated order of operations in order to get everything to work, and in fact the code I posted in my question above does not actually work (I think maybe because I load the draft task before I update the project?).

The order of operations I finally figured out to get this all working is this:

  • Create all the new (draft) tasks by calling draftProject.get_tasks().add(taskCreationInfo)
  • Keep a reference to the unresolved/unloaded draft task(s) returned from that function.
  • Update the draft project to actually create/save the new tasks.
  • Load all the unresolved/unloaded draft tasks.
  • Now that they're loaded, you can access their properties, and update the enterprise custom field values.
  • Update the draft project again to save the updates to the custom fields on all the draft tasks.

Here's the code I ended up with that is working. Keep in mind that by the time the createNewTasks function is called to kick off the whole process, I have already created a Project Server JSOM context object, and checked out the project so it is in a draft project state.

public createNewTasks = (newTasks: any[], httpClient: HttpClient) => {
    return new Promise((resolve, reject) => {
        const tasksCopy: any[] = [...newTasks];
        this.createDraftTasks(tasksCopy).then((draftTasks: any[]) => {

            // i needed the guids of the newly created tasks
            const newTaskIds: string[] = draftTasks.map((taskInfo: any) => {
                return taskInfo.draft.get_id().toString();
            });

            this.updateDraftTasks(draftTasks, httpClient).then(() => {
                this.updateAndCheckinProject().then(() => {
                    resolve(newTaskIds);
                }).catch((err) => {
                    console.log(err);
                    reject(err);
                });
            }).catch((err) => {
                console.log(err);
                reject(err);
            });
        });
    });
}
private createDraftTasks = (tasks: any[]): Promise<any[]> => {
    return new Promise<any[]> ((resolve, reject) => {
        const draftTasks = [];
        tasks.forEach(task => {
            // set what values we can initially with task creation information
            const newTaskInfo = new PS.TaskCreationInformation();
            newTaskInfo.set_name(task.Name);
            newTaskInfo.set_start(task.Start);
            newTaskInfo.set_finish(task.Finish);
            newTaskInfo.set_duration(task.Duration);
            // keep a ref to the draft task
            const draft = this.draftProj.get_tasks().add(newTaskInfo);
            // need to keep the draft task associated with the correct object
            // that has the other data that needs to go in the enterprise custom fields
            // because we need to load the draft tasks before we can update them
            draftTasks.push({
                task,
                draft
            });
        });
        const updateJob: any = this.draftProj.update();
        this.psCtx.waitForQueueAsync(updateJob, 20, (updateStatus) => {
            if (updateStatus === 4) {
                // now that the draft tasks have actually been created by
                // updating the project, need to load them so i can
                // get their guids and update their enterprise custom fields
                this.loadDraftTasks(draftTasks).then((loadedTasks: any[])=> {
                    resolve(loadedTasks);
                });
            } else {
                reject({
                    error: {
                        message: `Adding new tasks failed with error code ${updateStatus}`
                    }
                });
            }
        });
    })
}
private loadDraftTasks = (draftTasks: any[]): Promise<any[]> => {
    return new Promise<any[]> ((resolve, reject) => {
        if (draftTasks.length > 0) {
            const taskInfo = draftTasks.shift();
            this.psCtx.load(taskInfo.draft);
            this.psCtx.executeQueryAsync(() => {
                this.loadDraftTasks(draftTasks).then((result: any[]) => {
                    resolve([{
                        task: taskInfo.task,
                        draft: taskInfo.draft
                    }, ...result]);
                }).catch(err => {
                    console.log(err);
                    reject(err);
                });
            }, (sender, args) => {
                const message = args.get_message();
                console.log(message);
                reject(message);
            })
        } else {
            resolve([]);
        }
    });
}
private updateDraftTasks = (draftTasks: any[], httpClient: HttpClient): Promise<any[]> => {
    return new Promise<any[]> ((resolve, reject) => {
        const customFieldsUri = `${strings.ProjectSiteBaseUrl}/_api/ProjectServer/CustomFields?$select=Name,InternalName`;
        httpClient.get(customFieldsUri, HttpClient.configurations.v1, {
            headers: {
                accept: 'application/json'
            }
        }).then((response: HttpClientResponse) => {
            response.json().then(fieldsJSON => {
                if (response.ok) {
                    const enterpriseCustomField_1 = fieldsJSON.value.filter(fld => fld.Name === strings.EnterpriseField1_DisplayName)[0];
                    const enterpriseCustomField_2 = fieldsJSON.value.filter(fld => fld.Name === strings.EnterpriseField2_DisplayName)[0];
                    draftTasks.forEach(taskInfo => {
                        // set the enterprise custom field values by using set_item()
                        taskInfo.draft.set_item(enterpriseCustomField_1.InternalName, taskInfo.task.Field_1_Value);
                        taskInfo.draft.set_item(enterpriseCustomField_2.InternalName, taskInfo.task.FIeld_2_Value);
                    });
                    // save the new field values by updating the project
                    const updateJob = this.draftProj.update();
                    this.psCtx.waitForQueueAsync(updateJob, 20, (status) => {
                        if (status === 4) {
                            console.log('update succeeded');
                            resolve();
                        } else {
                            reject({
                                error: {
                                    message: `Updating fields on tasks failed with error code ${status}`
                                }
                            });
                        }
                    })
                } else {
                    reject(fieldsJSON);
                }
            });
        });
    });
}

OTHER TIPS

Have you tried using

draftTask["MyEnterpriseCustomField"] = task.localCustomFieldValue;

Assuming MyEnterpriseCustomField is your field name.

Licensed under: CC-BY-SA with attribution
Not affiliated with sharepoint.stackexchange
scroll top