Msg.Planner.Tasks (msg v0.3.8)

Manage Microsoft Planner Tasks.

Planner Tasks belong to Plans and can be assigned to users. Tasks support custom metadata embedded in the description field via HTML comments for application-specific data.

Required Permissions

Tasks in Group Plans

  • Application: ❌ Not supported
  • Delegated: Tasks.ReadWrite or Group.ReadWrite.All - required for group plan tasks

User Tasks

  • Application: Tasks.ReadWrite.All - read/write all tasks
  • Delegated: Tasks.ReadWrite - read/write user's tasks

Authentication

  • Tasks in group plans: Requires delegated permissions (refresh token)
  • User tasks: Works with application-only authentication

Etag-Based Concurrency

Like Plans, Tasks require etags for all update and delete operations. Etags are returned in the @odata.etag field and must be included in the If-Match header.

Metadata Embedding

Since Planner tasks don't support open extensions, this module provides helper functions to embed custom metadata in task descriptions using HTML comments:

<!-- metadata:project_id=proj_123,resource_id=res_456 -->

Examples

# List tasks in a plan
{:ok, tasks} = Msg.Planner.Tasks.list_by_plan(client, "plan-id")

# List tasks assigned to a user
{:ok, tasks} = Msg.Planner.Tasks.list_by_user(client, user_id: "user@contoso.com")

# Create task with embedded metadata
description = Msg.Planner.Tasks.embed_metadata(
  "Complete project deliverable",
  %{project_id: "proj_123", resource_id: "res_456"}
)

{:ok, task} = Msg.Planner.Tasks.create(client, %{
  plan_id: "plan-id",
  title: "Complete deliverable by Jan 15",
  description: description
})

# Parse metadata from task
metadata = Msg.Planner.Tasks.parse_metadata(task["description"])
# => %{project_id: "proj_123", resource_id: "res_456"}

References

Summary

Functions

Creates a new Planner Task.

Deletes a Planner Task.

Embeds metadata in a task description.

Gets a single Planner Task.

Lists tasks in a Planner Plan.

Lists tasks assigned to a user.

Parses metadata from a task description.

Updates a Planner Task.

Functions

create(client, task)

@spec create(Req.Request.t(), map()) :: {:ok, map()} | {:error, term()}

Creates a new Planner Task.

Parameters

  • client - Authenticated Req.Request client
  • task - Map with task properties:
    • :plan_id (required) - ID of plan to create task in
    • :title (required) - Task title
    • :due_date_time (optional) - Due date
    • :start_date_time (optional) - Start date
    • :percent_complete (optional) - 0-100
    • :assignments (optional) - Map of user assignments
    • :description (optional) - Task description

Returns

  • {:ok, task} - Created task with generated id and @odata.etag
  • {:error, term} - Error

Examples

{:ok, task} = Msg.Planner.Tasks.create(client, %{
  plan_id: "plan-id",
  title: "Complete deliverable by Jan 15",
  due_date_time: "2025-01-15T17:00:00Z",
  description: "Complete project deliverable and submit for review"
})

delete(client, task_id, etag)

@spec delete(Req.Request.t(), String.t(), String.t()) :: :ok | {:error, term()}

Deletes a Planner Task.

Important: Requires the current etag for concurrency control.

Parameters

  • client - Authenticated Req.Request client
  • task_id - ID of task to delete
  • etag - Current etag for concurrency control

Returns

  • :ok - Task deleted successfully (204 status)
  • {:error, {:etag_mismatch, current_etag}} - Etag conflict (412)
  • {:error, :not_found} - Task doesn't exist
  • {:error, term} - Other errors

Examples

{:ok, task} = Msg.Planner.Tasks.get(client, task_id)
:ok = Msg.Planner.Tasks.delete(client, task_id, task["@odata.etag"])

embed_metadata(description, metadata)

@spec embed_metadata(String.t() | nil, map()) :: String.t()

Embeds metadata in a task description.

Adds or updates an HTML comment at the beginning of the description with custom metadata. If metadata already exists, it will be replaced.

Parameters

  • description - Existing description (may be nil or empty)
  • metadata - Map of metadata key-value pairs

Returns

  • Updated description string with metadata embedded

Examples

desc = Msg.Planner.Tasks.embed_metadata(
  "Complete the deliverable",
  %{project_id: "proj_123", resource_id: "res_456"}
)
# => "<!-- metadata:project_id=proj_123,resource_id=res_456 -->\nComplete the deliverable"

# Update existing metadata
existing = "<!-- metadata:old=value -->\nOld description"
updated = Msg.Planner.Tasks.embed_metadata(existing, %{new: "data"})
# => "<!-- metadata:new=data -->\nOld description"

get(client, task_id)

@spec get(Req.Request.t(), String.t()) :: {:ok, map()} | {:error, term()}

Gets a single Planner Task.

Parameters

  • client - Authenticated Req.Request client
  • task_id - ID of the task to retrieve

Returns

  • {:ok, task} - Task map with details including @odata.etag
  • {:error, :not_found} - Task doesn't exist
  • {:error, term} - Other errors

Examples

{:ok, task} = Msg.Planner.Tasks.get(client, "task-id")
etag = task["@odata.etag"]

list_by_plan(client, plan_id, opts \\ [])

@spec list_by_plan(Req.Request.t(), String.t(), keyword()) ::
  {:ok, [map()]} | {:ok, map()} | {:error, term()}

Lists tasks in a Planner Plan.

Parameters

  • client - Authenticated Req.Request client
  • plan_id - ID of the plan
  • opts - Keyword list of options:
    • :auto_paginate - Boolean, default true (fetch all pages)

Returns

  • {:ok, [task]} - List of tasks (all tasks in the plan)
  • {:error, term} - Error

Examples

{:ok, tasks} = Msg.Planner.Tasks.list_by_plan(client, "plan-id")

list_by_user(client, opts)

@spec list_by_user(
  Req.Request.t(),
  keyword()
) :: {:ok, [map()]} | {:ok, map()} | {:error, term()}

Lists tasks assigned to a user.

Parameters

  • client - Authenticated Req.Request client
  • opts - Keyword list of options:
    • :user_id (required) - User ID or UPN
    • :auto_paginate - Boolean, default true (fetch all pages)

Returns

  • {:ok, [task]} - All tasks assigned to specified user (across all plans)
  • {:error, term} - Error

Examples

{:ok, tasks} = Msg.Planner.Tasks.list_by_user(client, user_id: "user@contoso.com")

parse_metadata(description)

@spec parse_metadata(String.t() | nil) :: map() | nil

Parses metadata from a task description.

Extracts custom metadata embedded in an HTML comment at the beginning of the description.

Parameters

  • description - Task description string (may be nil)

Returns

  • Map of metadata key-value pairs, or nil if no metadata found

Format

The metadata must be in the first line as an HTML comment:

<!-- metadata:key1=value1,key2=value2,key3=value3 -->

Examples

description = """
<!-- metadata:project_id=proj_123,resource_id=res_456,organization_id=org_789 -->
Complete the deliverable
Due by end of day
"""

metadata = Msg.Planner.Tasks.parse_metadata(description)
# => %{project_id: "proj_123", resource_id: "res_456", organization_id: "org_789"}

# No metadata
Msg.Planner.Tasks.parse_metadata("Just a description")
# => nil

update(client, task_id, updates, opts)

@spec update(Req.Request.t(), String.t(), map(), keyword()) ::
  {:ok, map()} | {:error, term()}

Updates a Planner Task.

Important: Requires the current etag for concurrency control.

Parameters

  • client - Authenticated Req.Request client
  • task_id - ID of task to update
  • updates - Map of fields to update
  • opts - Keyword list of options:
    • :etag (required) - Current etag from the task

Returns

  • {:ok, task} - Updated task with new @odata.etag
  • {:error, {:etag_mismatch, current_etag}} - Etag conflict (412)
  • {:error, :not_found} - Task doesn't exist
  • {:error, term} - Other errors

Examples

{:ok, task} = Msg.Planner.Tasks.get(client, task_id)

{:ok, updated} = Msg.Planner.Tasks.update(client, task_id,
  %{percent_complete: 50},
  etag: task["@odata.etag"]
)