Pair data ('list id' and 'task') from different nodes

Hi,

I’m creating a workflow for deleting Microsoft To Do’s if they are completed, for all Lists I have.
I don’t want to add a node for each list, so i’m trying to do it all in n8n.

For deleting a Task, I need the List ID.

I tried to add a field list_id with the Set node, and using the parameter List ID from the node getting all tasks. But I’m getting each list id only once, on the first 8 (number of lists) items. The other items have a empty ‘list_id’
I think n8n iterates per dataset/node…?

So, how to pair the Task item with its Lists ID?

Schermafbeelding 2021-11-25 om 21.52.29

{
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "resource": "list",
        "operation": "getAll",
        "returnAll": true
      },
      "name": "To Do's: all lists",
      "type": "n8n-nodes-base.microsoftToDo",
      "typeVersion": 1,
      "position": [
        460,
        300
      ],
      "credentials": {
        "microsoftToDoOAuth2Api": {
          "id": "11",
          "name": "Microsoft To Do account"
        }
      }
    },
    {
      "parameters": {
        "values": {
          "string": [
            {
              "name": "task_id",
              "value": "={{$node[\"To Do: all tasks\"].json[\"id\"]}}"
            },
            {
              "name": "status",
              "value": "={{$node[\"To Do: all tasks\"].json[\"status\"]}}"
            },
            {
              "name": "list_id",
              "value": "={{$node[\"To Do: all tasks\"].parameter[\"taskListId\"]}}"
            }
          ],
          "boolean": []
        },
        "options": {}
      },
      "name": "Set",
      "type": "n8n-nodes-base.set",
      "typeVersion": 1,
      "position": [
        860,
        300
      ]
    },
    {
      "parameters": {
        "operation": "getAll",
        "taskListId": "={{$node[\"To Do's: all lists\"].json[\"id\"]}}",
        "returnAll": true
      },
      "name": "To Do: all tasks",
      "type": "n8n-nodes-base.microsoftToDo",
      "typeVersion": 1,
      "position": [
        640,
        300
      ],
      "credentials": {
        "microsoftToDoOAuth2Api": {
          "id": "11",
          "name": "Microsoft To Do account"
        }
      }
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "To Do's: all lists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "To Do's: all lists": {
      "main": [
        [
          {
            "node": "To Do: all tasks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "To Do: all tasks": {
      "main": [
        [
          {
            "node": "Set",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Welcome to the community @yoaz

You are right. It does. I think the issue happens because the parameter you are referencing in the set node it’s referencing an expression in the first node. Never seems this scenario before.

I will sync with my coworker about this and come back to you. In the meantime, you can use the workflow below to achieve that.

{
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        -140,
        140
      ]
    },
    {
      "parameters": {
        "resource": "list",
        "operation": "getAll",
        "returnAll": true
      },
      "name": "To Do's: all lists",
      "type": "n8n-nodes-base.microsoftToDo",
      "typeVersion": 1,
      "position": [
        60,
        140
      ],
      "credentials": {
        "microsoftToDoOAuth2Api": {
          "id": "325",
          "name": "saasasas"
        }
      }
    },
    {
      "parameters": {
        "keepOnlySet": true,
        "values": {
          "string": [
            {
              "name": "task_id",
              "value": "={{$node[\"To Do: all tasks\"].json[\"id\"]}}"
            },
            {
              "name": "status",
              "value": "={{$node[\"To Do: all tasks\"].json[\"status\"]}}"
            },
            {
              "name": "list_id",
              "value": "={{ $item(0).$node[\"SplitInBatches\"].json[\"id\"] }}"
            }
          ],
          "boolean": []
        },
        "options": {}
      },
      "name": "Set",
      "type": "n8n-nodes-base.set",
      "typeVersion": 1,
      "position": [
        680,
        140
      ]
    },
    {
      "parameters": {
        "operation": "getAll",
        "taskListId": "={{$node[\"To Do's: all lists\"].json[\"id\"]}}",
        "returnAll": true
      },
      "name": "To Do: all tasks",
      "type": "n8n-nodes-base.microsoftToDo",
      "typeVersion": 1,
      "position": [
        460,
        140
      ],
      "credentials": {
        "microsoftToDoOAuth2Api": {
          "id": "325",
          "name": "saasasas"
        }
      }
    },
    {
      "parameters": {
        "batchSize": 1,
        "options": {}
      },
      "name": "SplitInBatches",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 1,
      "position": [
        260,
        140
      ]
    },
    {
      "parameters": {
        "functionCode": "const allData = []\n\nlet counter = 0;\ndo {\n  try {\n    const items = $items(\"Set\", 0, counter).map(item => item);\n    allData.push.apply(allData, items);\n  } catch (error) {\n    return allData;  \n  }\n\n  counter++;\n} while(true);\n"
      },
      "name": "Function",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1180,
        160
      ]
    },
    {
      "parameters": {
        "mode": "wait"
      },
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 1,
      "position": [
        960,
        160
      ]
    },
    {
      "parameters": {},
      "name": "NoOp",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        780,
        400
      ]
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "To Do's: all lists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "To Do's: all lists": {
      "main": [
        [
          {
            "node": "SplitInBatches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set": {
      "main": [
        [
          {
            "node": "SplitInBatches",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "To Do: all tasks": {
      "main": [
        [
          {
            "node": "Set",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SplitInBatches": {
      "main": [
        [
          {
            "node": "To Do: all tasks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Function",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "NoOp": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    }
  }
}
1 Like

Thanks! That is some advanced stuff, trying to understand that workflow.

It didn’t give the result I need. So I was tweaking it with trial & error (learning how n8n operates and can do) and I have made it work.
I want to share my work:

The use of the split in batches node was clever. Thanks @RicardoE105.

The node which gets all tasks per list was still using the expression from the first node, not the SplitInBachtes node, so i corrected it.
That was workig (the right data was paired). But when a list is empty, the run stopped. So I set ‘Always Output Data’ option on.

The function node for. merging all run on the set node was executed on the first run. So the merge node in wait mode was not waiting for all runs to finish.
I now use an If node to check for the SplitInBatches context. ‘noItemsLeft’.

I added a function node for filtering out those items from empty lists (which don’t have a task_id field).

Finishing off with an if node for checking if task is completed and an node for deleting those task, and it’s all working now!

Here the workflow code, for those who want to use it also (since Microsoft To Do’s doesn’t have a function for deleting completed tasks…)

{
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        980,
        580
      ]
    },
    {
      "parameters": {
        "resource": "list",
        "operation": "getAll",
        "returnAll": true
      },
      "name": "To Do's: all lists",
      "type": "n8n-nodes-base.microsoftToDo",
      "typeVersion": 1,
      "position": [
        1120,
        660
      ],
      "credentials": {
        "microsoftToDoOAuth2Api": {
          "id": "11",
          "name": "Microsoft To Do account"
        }
      }
    },
    {
      "parameters": {
        "operation": "getAll",
        "taskListId": "={{$node[\"SplitInBatches\"].json[\"id\"]}}",
        "returnAll": true
      },
      "name": "To Do: all tasks",
      "type": "n8n-nodes-base.microsoftToDo",
      "typeVersion": 1,
      "position": [
        1420,
        660
      ],
      "alwaysOutputData": true,
      "credentials": {
        "microsoftToDoOAuth2Api": {
          "id": "11",
          "name": "Microsoft To Do account"
        }
      }
    },
    {
      "parameters": {
        "batchSize": 1,
        "options": {
          "reset": false
        }
      },
      "name": "SplitInBatches",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 1,
      "position": [
        1280,
        660
      ]
    },
    {
      "parameters": {
        "functionCode": "const allData = []\n\nlet counter = 0;\ndo {\n  try {\n    const items = $items(\"Set1\", 0, counter).map(item => item);\n    allData.push.apply(allData, items);\n  } catch (error) {\n    return allData;  \n  }\n\n  counter++;\n} while(true);\n"
      },
      "name": "Function",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1820,
        500
      ],
      "notesInFlow": true,
      "notes": "Merge all runs form Set node"
    },
    {
      "parameters": {
        "conditions": {
          "number": [],
          "boolean": [
            {
              "value1": "={{$node[\"SplitInBatches\"].context[\"noItemsLeft\"]}}",
              "value2": true
            }
          ]
        }
      },
      "name": "IF",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1660,
        660
      ]
    },
    {
      "parameters": {
        "keepOnlySet": true,
        "values": {
          "string": [
            {
              "name": "task_id",
              "value": "={{$node[\"To Do: all tasks\"].json[\"id\"]}}"
            },
            {
              "name": "status",
              "value": "={{$node[\"To Do: all tasks\"].json[\"status\"]}}"
            },
            {
              "name": "list_id",
              "value": "={{ $item(0).$node[\"SplitInBatches\"].json[\"id\"] }}"
            },
            {
              "name": "listname",
              "value": "={{ $item(0).$node[\"SplitInBatches\"].json[\"displayName\"] }}"
            },
            {
              "name": "taskTitle",
              "value": "={{$node[\"To Do: all tasks\"].json[\"title\"]}}"
            }
          ],
          "boolean": []
        },
        "options": {}
      },
      "name": "Set1",
      "type": "n8n-nodes-base.set",
      "typeVersion": 1,
      "position": [
        1540,
        660
      ]
    },
    {
      "parameters": {
        "functionCode": "let newItems = items.filter(item => item.json.hasOwnProperty(\"task_id\"));\n\nreturn newItems;"
      },
      "name": "Function2",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1980,
        500
      ],
      "notesInFlow": true,
      "notes": "filter out empty lists"
    },
    {
      "parameters": {
        "operation": "delete",
        "taskListId": "={{$node[\"IF task is completed\"].json[\"list_id\"]}}",
        "taskId": "={{$node[\"IF task is completed\"].json[\"task_id\"]}}"
      },
      "name": "To Do: Delete",
      "type": "n8n-nodes-base.microsoftToDo",
      "typeVersion": 1,
      "position": [
        2300,
        420
      ],
      "credentials": {
        "microsoftToDoOAuth2Api": {
          "id": "11",
          "name": "Microsoft To Do account"
        }
      }
    },
    {
      "parameters": {},
      "name": "NoOp",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        2300,
        580
      ]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$node[\"Function2\"].json[\"status\"]}}",
              "value2": "completed"
            }
          ]
        }
      },
      "name": "IF task is completed",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        2140,
        500
      ]
    },
    {
      "parameters": {
        "triggerTimes": {
          "item": [
            {
              "mode": "everyWeek",
              "hour": 9
            }
          ]
        }
      },
      "name": "Cron",
      "type": "n8n-nodes-base.cron",
      "typeVersion": 1,
      "position": [
        980,
        740
      ],
      "notesInFlow": true,
      "notes": "Every monday at 09:00"
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "To Do's: all lists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "To Do's: all lists": {
      "main": [
        [
          {
            "node": "SplitInBatches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "To Do: all tasks": {
      "main": [
        [
          {
            "node": "Set1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SplitInBatches": {
      "main": [
        [
          {
            "node": "To Do: all tasks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Function": {
      "main": [
        [
          {
            "node": "Function2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF": {
      "main": [
        [
          {
            "node": "Function",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "SplitInBatches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set1": {
      "main": [
        [
          {
            "node": "IF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Function2": {
      "main": [
        [
          {
            "node": "IF task is completed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF task is completed": {
      "main": [
        [
          {
            "node": "To Do: Delete",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "NoOp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cron": {
      "main": [
        [
          {
            "node": "To Do's: all lists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
1 Like

Great that you figured it out.

This use case is infrequent. First time I’ve seen it in two years. Usually, the API would have returned the list id, meaning you would not have needed to reference the parameter in the second node, which is referencing an expression in the first node. I would speculate that in 98% of cases, you do not need these many nodes for such a simple workflow. I spoke with Jan about it, and this is how it was initially designed, and at some point, we will improve to cover cases such as this one.

1 Like

Thanks for the reply and your explanation. I understand it is an infrequent use case. To bad this API does not follow that principle.
Luckily n8n provides enough tools to accomplish it. Thanks for the work you all are putting in.