lib/Classes/Public/TMTask.ps1
class TMTask { #region Non-Static Properties [System.Int64]$Id [System.Int64]$TaskNumber [System.String]$Title [System.String]$Comment [ValidateSet('Hold', 'Planned', 'Ready', 'Pending', 'Started', 'Completed', 'Terminated')] [System.String]$Status [Nullable[System.DateTime]]$StatusUpdated [System.String]$StatusUpdatedElapsed [Nullable[System.DateTime]]$LastUpdated [System.String]$LastUpdatedElapsed [TMTaskAction]$Action [TMTaskAsset]$Asset [TMReference]$AssignedTo [TMReference]$CreatedBy [System.String]$Category [Nullable[System.DateTime]]$DateCreated [ValidateRange(0, 1)] [System.Int64]$HardAssigned [System.Int64]$EstDurationMinutes [String]$EstStart [String]$EstFinish [System.Int64]$Slack [System.Boolean]$IsCriticalPath [Nullable[System.DateTime]]$ActStart [Nullable[System.DateTime]]$ActFinish [System.String]$Team [System.Boolean]$IsPublished [ValidateRange(0, 100)] [System.Int64]$PercentageComplete [TMReference]$Project [System.Boolean]$IsActionInvocableLocally [System.Boolean]$IsActionInvocableRemotely [System.Boolean]$IsAutomatic [System.Int64]$Duration [System.Boolean]$SendNotification [ValidateRange(0, 5)] [System.Int64]$Priority [TMReference]$Event [Nullable[System.DateTime]]$DueDate [System.String]$InstructionsLink [TMTaskDependency[]]$Predecessors [TMTaskDependency[]]$Successors [System.Int32]$Schema [System.String]$Attribute [System.Boolean]$AutoGenerated [System.String]$DisplayOption [System.Object]$Recipe [System.Int64]$TaskSpec #endregion Non-Static Properties #region Static Properties # The only valid statuses for a Task static [System.String[]]$ValidStatuses = @( 'Hold', 'Planned', 'Ready', 'Pending', 'Started', 'Completed', 'Terminated' ) # List of field/property names that are used in the TMQL request to TM static [System.String[]]$TMQLFetchProperties = @( 'actFinish' 'actStart' 'apiAction.actionType' 'apiAction.description' 'apiAction.id' 'apiAction.isRemote' 'apiAction.methodParams' 'apiAction.name' 'apiActionCompletedAt' 'apiActionInvokedAt' 'apiActionSettings' 'assetEntity.Asset Class' 'assetEntity.assetType' 'assetEntity.Bundle' 'assetEntity.Id' 'assetEntity.Name' 'assetEntity.Tags' 'assignedTo.id' 'assignedTo.name' 'attribute' 'autoGenerated' 'category' 'comment' 'commentType' 'createdBy.id' 'createdBy.name' 'dateCreated' 'dateResolved' 'displayOption' 'dueDate' 'duration' 'estFinish' 'estStart' 'hardAssigned' 'id' 'instructionsLink' 'isCriticalPath' 'isPublished' 'lastUpdated' 'moveEvent.id' 'moveEvent.name' 'percentageComplete' 'predecessors' 'priority' 'project.id' 'project.name' 'recipe' 'role' 'sendNotification' 'slack' 'status' 'statusUpdated' 'successors' 'taskNumber' 'taskSpec' ) # String that is used in the TMQL query to fetch all of the necessary details about the Task static [String]$TMQLFetchString = "fetch '" + ([TMTask]::TMQLFetchProperties -join "', '") + "'" #endregion Static Properties #region Constructors TMTask() { $this.addPublicMembers() } TMTask([Object]$object) { $this.Schema = $object.PSObject.Properties.Name -contains 'assetEntity.Tags' ? 2 : 1 $this.Id = $object.id $this.TaskNumber = $object.taskNumber $this.Comment = $object.comment ?? $object.title $this.Title = $object.title ?? $object.comment $this.Status = $object.status $this.StatusUpdated = $object.statusUpdated $this.StatusUpdatedElapsed = $object.statusUpdatedElapsed ?? ($object.statusUpdated ? (New-TimeSpan -Start $object.statusUpdated -End (Get-Date -AsUTC)) : "") $this.LastUpdated = $object.lastUpdated $this.LastUpdatedElapsed = $object.lastUpdatedElapsed ?? ($object.statusUpdated ? (New-TimeSpan -Start $object.lastUpdated -End (Get-Date -AsUTC)) : "") $this.Action = if ($this.Schema -eq 1) { [TMTaskAction]::new(($object.action ?? $object.apiAction)) } else { [TMTaskAction]::new( $object.'apiAction.id', $object.'apiAction.name', $object.'apiAction.isRemote', $object.'apiAction.actionType', $object.'apiAction.description', $object.'apiActionInvokedAt', $object.'apiActionCompletedAt', $object.'apiAction.methodParams' ) } $this.Asset = if ($this.Schema -eq 1) { [TMTaskAsset]::new($object.asset) } else { [TMTaskAsset]::new( $object.'assetEntity.Id', $object.'assetEntity.Name', $object.'assetEntity.Asset Class', $object.'assetEntity.assetType', @{id = $object.'assetEntity.Bundle.id'; name = $object.'assetEntity.Bundle.name' }, $object.'assetEntity.tags' ) } $this.AssignedTo = $this.Schema -eq 1 ? [TMReference]::new($object.assignedTo) : [TMReference]::new($object.'assignedTo.name', $object.'assignedTo.id') $this.CreatedBy = $this.Schema -eq 1 ? [TMReference]::new($object.createdBy) : [TMReference]::new($object.'createdBy.name', $object.'createdBy.id') $this.Category = $object.category $this.DateCreated = $object.dateCreated $this.HardAssigned = $object.hardAssigned $this.EstDurationMinutes = $object.estDurationMinutes $this.EstStart = $object.estStart $this.EstFinish = $object.estFinish $this.Slack = $object.slack $this.IsCriticalPath = $object.isCriticalPath $this.ActStart = $object.actStart $this.ActFinish = $object.actFinish $this.Team = $object.team ?? $object.role $this.IsPublished = $object.isPublished $this.PercentageComplete = $object.percentageComplete $this.Project = $this.Schema -eq 1 ? [TMReference]::new($object.project) : [TMReference]::new($object.'project.name', $object.'project.id') $this.IsActionInvocableLocally = $object.isActionInvocableLocally ?? $this.isActionInvokable($object, 'Local') $this.IsActionInvocableRemotely = $object.isActionInvocableRemotely ?? $this.isActionInvokable($object, 'Remote') $this.IsAutomatic = $object.isAutomatic ?? ($object.role -eq 'AUTO') -or ($object.'assignedTo.id' -eq 0) $this.Duration = $object.duration $this.SendNotification = $object.sendNotification $this.Priority = $object.priority $this.Event = $this.Schema -eq 1 ? [TMReference]::new($object.event) : [TMReference]::new($object.'moveEvent.name', $object.'moveEvent.id') $this.DueDate = $object.dueDate $this.InstructionsLink = $object.instructionsLink $this.Predecessors = $object.predecessors | ForEach-Object { [TMTaskDependency]::new($_) } $this.Successors = $object.successors | ForEach-Object { [TMTaskDependency]::new($_) } $this.Attribute = $object.attribute $this.AutoGenerated = $object.autoGenerated $this.DisplayOption = $object.displayOption $this.Recipe = $object.recipe $this.TaskSpec = $object.taskSpec $this.addPublicMembers() } #endregion Constructors #region Non-Static Methods <# Summary: Formats the Task into an object that can be used in the Update-TMTask web services request to TM Params: UpdateTaskDependencies - Boolean indicating if the Task's dependencies should be updated Outputs: None #> [PSCustomObject]GetWSUpdateObject([Boolean]$UpdateTaskDependencies) { $returnObject = [PSCustomObject]@{ id = $this.Id comment = $this.Comment ? $this.Comment : $this.Title project = $this.Project.Id status = $this.Status assignedTo = $this.AssignedTo.id apiAction = $this.Action.Id apiActionId = "$($this.Action.Id)" category = $this.Category assetEntity = $this.Asset.Id hardAssigned = $this.HardAssigned moveEvent = $this.Event.id priority = $this.Priority role = $this.Team percentageComplete = $this.PercentageComplete sendNotification = $this.SendNotification ? 1 : 0 instructionsLink = $this.InstructionsLink duration = $this.Duration durationScale = 'M' durationLocked = 0 } if ($UpdateTaskDependencies) { $returnObject | Add-Member -NotePropertyName 'taskDependency' -NotePropertyValue @() $returnObject | Add-Member -NotePropertyName 'taskSuccessor' -NotePropertyValue @() foreach ($Predecessor in $this.Predecessors) { if (-not $Predecessor.Id -or $Predecessor.Id -eq 0) { $Predecessor.Id = -1 } $returnObject.taskDependency += "$($Predecessor.Id)_$($Predecessor.TaskId)" } foreach ($Successor in $this.Successors) { if (-not $Successor.Id -or $Successor.Id -eq 0) { $Successor.Id = -1 } $returnObject.taskSuccessor += "$($Successor.Id)_$($Successor.TaskId)" } } return $returnObject } <# Summary: Formats the Task into an object that can be used in the Update-TMTask REST request to TM Params: Note - A note/comment to be added to the Task Status - A new Status to be set on the Task UpdateTaskDependencies - Boolean indicating if the Task's dependencies should be updated Outputs: None #> [PSCustomObject]GetApiUpdateObject([String]$Note, [String]$Status, [Boolean]$UpdateTaskDependencies) { $returnObject = [PSCustomObject]@{ action = @{ id = $this.Action.Id } asset = @{ id = $this.Asset.Id } event = @{ id = $this.Event.id } assignedTo = $this.AssignedTo.id category = $this.Category comment = $this.comment ? $this.comment : $this.title title = $this.comment ? $this.comment : $this.title hardAssigned = $this.HardAssigned instructionsLink = $this.InstructionsLink percentageComplete = $this.PercentageComplete priority = $this.Priority role = $this.Team status = [String]::IsNullOrWhiteSpace($Status) ? $this.Status : $Status currentStatus = $this.Status sendNotification = $this.SendNotification ? 1 : 0 project = $this.Project.Id note = $Note } if ($UpdateTaskDependencies) { $returnObject | Add-Member -NotePropertyName 'predecessors' -NotePropertyValue @() $returnObject | Add-Member -NotePropertyName 'successors' -NotePropertyValue @() foreach ($Predecessor in $this.Predecessors) { if (-not $Predecessor.Id -or $Predecessor.Id -eq 0) { $Predecessor.Id = -1 } $returnObject.predecessors += @{id = $Predecessor.Id; taskId = $Predecessor.TaskId } } foreach ($Successor in $this.Successors) { if (-not $Successor.Id -or $Successor.Id -eq 0) { $Successor.Id = -1 } $returnObject.successors += @{id = $Successor.Id; taskId = $Successor.TaskId } } } return $returnObject } #endregion Non-Static Methods #region Private Methods <# Summary: Copy of the logic from the TM source code to determine if the Action is invokable Params: object - The object returned from TM representing the Task location - The invocation location. Remote or Local Outputs: None #> hidden [System.Boolean]isActionInvokable([System.Object]$object, [System.String]$location) { $invokable = switch ($location) { 'Remote' { ( ($object.'apiAction.id' -gt 0) -and (-not $object.apiActionInvokedAt) -and ($object.'apiAction.isRemote') -and ($object.status -in 'Ready', 'Started') ) } 'Local' { ( ($object.'apiAction.id' -gt 0) -and (-not $object.apiActionInvokedAt) -and (-not $object.'apiAction.isRemote') -and ($object.status -in 'Ready', 'Started') ) } default { $false } } return $invokable } <# Summary: Adds properties that have a custom getter and or setter script Params: None Outputs: None #> hidden [void]addPublicMembers() { # Add a read-only property that calculates the Score of the Task $this.PSObject.Properties.Add( [PSScriptProperty]::new( 'Score', { # get $score = switch ($this.Status) { 'Hold' { 9000000 } 'Completed' { $this.StatusUpdated -ge (Get-Date).AddMinutes(-1) ? 8000000 : 3000000 } 'Started' { 7000000 } 'Ready' { 6000000 } 'Pending' { 5000000 } 'Planned' { 4000000 } 'Terminated' { 2000000 } } return ( $score - ($this.Status -in 'Hold', 'Completed', 'Started', 'Terminated' ? ( $null -ne $this.StatusUpdated ? ( [Math]::Round(([DateTimeOffSet]::UtcNow.ToUnixTimeSeconds() - ([DateTimeOffset]$this.StatusUpdated).ToUnixTimeSeconds()) / 60) ) : 0 ) : 0) + (($this.Status -in 'Ready', 'Pending', 'Planned') -and ($null -ne $this.EstStart) ? ( [Math]::Floor( [Math]::Sqrt( 10000 * ( (([DateTimeOffset]$this.EstStart).ToUnixTimeSeconds() + $this.Slack) -lt [DateTimeOffSet]::UtcNow.ToUnixTimeSeconds() ? ( [Math]::Round(([DateTimeOffSet]::UtcNow.ToUnixTimeSeconds() - ([DateTimeOffset]$this.EstStart).ToUnixTimeSeconds() - $this.Slack) / 60) ) : ( [Math]::Round((([DateTimeOffset]$this.EstStart).ToUnixTimeSeconds() + $this.Slack - [DateTimeOffSet]::UtcNow.ToUnixTimeSeconds()) / 60) ) ) ) * ((([DateTimeOffset]$this.EstStart).ToUnixTimeSeconds() + $this.Slack) -lt [DateTimeOffSet]::UtcNow.ToUnixTimeSeconds() ? 1 : -1) ) ) : 0) - (($this.Status -ne 'Hold') -and (($this.Team -eq 'AUTO') -or ($this.AssignedTo.Id -eq 5667)) ? 500000 : 0) ) } ) ) } #endregion Private Methods } class TMTaskAction { #region Non-Static Properties [System.Int64]$Id [System.String]$Name [System.Boolean]$IsRemote [TMReference]$ActionType [System.String]$Description [Nullable[System.DateTime]]$InvokedAt [System.Object]$CompletedAt [TMTaskActionMethodParam[]]$MethodParams #endregion Non-Static Properties #region Constructors TMTaskAction() {} TMTaskAction( [System.Int64]$id, [System.String]$name, [System.Boolean]$isRemote, [System.Object]$actionType, [System.String]$description, [Nullable[System.DateTime]]$invokedAt, [System.Object]$completedAt, [System.Object]$params ) { $this.Id = $id $this.Name = $name $this.IsRemote = $isRemote $this.ActionType = [TMReference]::new($actionType) $this.Description = $description $this.InvokedAt = $invokedAt $this.CompletedAt = $completedAt if ($params -is [System.String]) { $params = ($params | ConvertFrom-Json -Depth 5) } $this.MethodParams = $params | ForEach-Object { [TMTaskActionMethodParam]::new($_) } } TMTaskAction([Object]$object) { $this.Id = $object.id $this.Name = $object.name $this.IsRemote = $object.isRemote $this.ActionType = [TMReference]::new($object.actionType) $this.Description = $object.description $this.InvokedAt = $object.invokedAt $this.CompletedAt = $object.completedAt if ($object.methodParams -is [System.String]) { $object.methodParams = ($object.methodParams | ConvertFrom-Json -Depth 5) } $this.MethodParams = $object.methodParams | ForEach-Object { [TMTaskActionMethodParam]::new($_) } } #endregion Constructors } class TMTaskActionMethodParam { #region Non-Static Properties [System.String]$Type [System.String]$Value [System.String]$Context [System.Boolean]$Encoded [System.Boolean]$Invalid [System.Boolean]$Readonly [System.Boolean]$Required [System.String]$FieldName [System.String]$ParamName [System.String]$Description #endregion Non-Static Properties #region Constructors TMTaskActionMethodParam() {} TMTaskActionMethodParam([Object]$object) { $this.Type = $object.type $this.Value = $object.value $this.Context = $object.context $this.Encoded = $object.encoded $this.Invalid = $object.invalid $this.Readonly = $object.readonly $this.Required = $object.required $this.FieldName = $object.fieldName $this.ParamName = $object.paramName $this.Description = $object.description } #endregion Constructors } class TMTaskAsset { #region Non-Static Properties [System.Int64]$Id [System.String]$Name [System.String]$Class [System.String]$Type [TMReference]$Bundle [System.String[]]$Tags #endregion Non-Static Properties #region Constructors TMTaskAsset() {} TMTaskAsset( [System.Int64]$id, [System.String]$name, [System.String]$class, [System.String]$type, [System.Object]$bundle, [String[]]$tags ) { $this.Id = $id $this.Name = $name $this.Class = $id -eq 0 ? '' : $class $this.Type = $type $this.Bundle = [TMReference]::new($bundle) $this.Tags = $tags } TMTaskAsset([Object]$object) { $this.Id = $object.id $this.Name = $object.name $this.Class = $object.id -eq 0 ? '' : $object.class $this.Type = $object.type $this.Bundle = [TMReference]::new($object.bundle) $this.Tags = $object.tags } #endregion Constructors } class TMTaskDependency { #region Non-Static Properties [System.Int64]$Id [System.Int64]$TaskId [System.Int64]$Number [System.String]$Title #endregion Non-Static Properties #region Static Properties # List of field/property names that are used in the TMQL request to TM static [System.String[]]$TMQLFetchProperties = @( 'id' 'assetComment.id' 'assetComment.taskNumber' 'assetComment.comment' 'predecessor.id' 'predecessor.taskNumber' 'predecessor.comment' ) # String that is used in the TMQL query to fetch all of the necessary details about the Task static [String]$TMQLFetchString = "fetch '" + ([TMTaskDependency]::TMQLFetchProperties -join "', '") + "'" #endregion Static Properties #region Constructors TMTaskDependency() {} TMTaskDependency([System.Int64]$taskId) { $this.TaskId = $taskId } TMTaskDependency([System.Int64]$id, [System.Int64]$taskId, [System.Int64]$number, [System.String]$title) { $this.Id = $id $this.TaskId = $taskId $this.Number = $number $this.Title = $title } TMTaskDependency([Object]$object) { $this.Id = $object.id $this.TaskId = $object.taskId $this.Number = $object.number $this.Title = $object.title } TMTaskDependency([Object]$object, [Boolean]$successor) { $this.Id = $object.id $this.TaskId = $successor ? $object.'assetComment.id' : $object.'predecessor.id' $this.Number = $successor ? $object.'assetComment.taskNumber' : $object.'predecessor.taskNumber' $this.Title = $successor ? $object.'assetComment.comment' : $object.'predecessor.comment' } #endregion Constructors } |