0

New or Updated List Item

by
Published today

Emits the items created (or, with `events`, also modified) in a list since the last poll, with their column values. The first run records the newest item and emits nothing. Polls because Microsoft Graph push needs a change-notification subscription renewed every few weeks against a public endpoint, which belongs in a Windmill native trigger; run this as a Scheduled Poll until then. Requires Sites.Read.All (covered by the default Sites.ReadWrite.All).

Scriptยท trigger ms_sharepoint Verified

The script

Submitted by hugo989 Typescript (fetch-only)
Verified 5 hours ago
1
//native
2

3
import * as wmill from "windmill-client"
4

5
const graph = "https://graph.microsoft.com/v1.0"
6

7
export type DynSelect_site_id = string
8

9
export async function site_id(auth: RT.MsSharepoint) {
10
  const response = await fetch(
11
    `${graph}/sites?search=*&$select=id,displayName,webUrl&$top=200`,
12
    {
13
      headers: {
14
        Authorization: `Bearer ${auth.token}`,
15
        Accept: "application/json",
16
      },
17
    }
18
  )
19
  if (!response.ok) {
20
    throw new Error(`${response.status} ${await response.text()}`)
21
  }
22
  const { value } = (await response.json()) as {
23
    value: { id: string; displayName: string; webUrl: string }[]
24
  }
25
  return value.map((s) => ({
26
    value: s.id,
27
    label: `${s.displayName} (${s.webUrl})`,
28
  }))
29
}
30

31
export type DynSelect_list_id = string
32

33
export async function list_id(auth: RT.MsSharepoint, site_id: string) {
34
  const response = await fetch(
35
    `${graph}/sites/${site_id}/lists?$select=id,displayName`,
36
    {
37
      headers: {
38
        Authorization: `Bearer ${auth.token}`,
39
        Accept: "application/json",
40
      },
41
    }
42
  )
43
  if (!response.ok) {
44
    throw new Error(`${response.status} ${await response.text()}`)
45
  }
46
  const { value } = (await response.json()) as {
47
    value: { id: string; displayName: string }[]
48
  }
49
  return value.map((l) => ({ value: l.id, label: l.displayName }))
50
}
51

52
/**
53
 * New or Updated List Item
54
 * Emits the items created (or, with `events`, also modified) in a list since the last poll, with their column values. The first run records the newest item and emits nothing. Polls because Microsoft Graph push needs a change-notification subscription renewed every few weeks against a public endpoint, which belongs in a Windmill native trigger; run this as a Scheduled Poll until then. Requires Sites.Read.All (covered by the default Sites.ReadWrite.All).
55
 */
56
export async function main(
57
  auth: RT.MsSharepoint,
58
  site_id: DynSelect_site_id,
59
  list_id: DynSelect_list_id,
60
  events: "created" | "created_or_updated" = "created"
61
) {
62
  const field =
63
    events === "created" ? "createdDateTime" : "lastModifiedDateTime"
64
  const since: number | undefined = await wmill.getState()
65

66
  // ponytail: reads the whole list every poll, as the timestamps cannot be
67
  // filtered server-side without an indexed column; switch to items/delta if lists get large
68
  const items: { [key: string]: any }[] = []
69
  let next: string | undefined =
70
    `${graph}/sites/${site_id}/lists/${list_id}/items?$expand=fields&$top=200`
71
  while (next) {
72
    const response = await fetch(next, {
73
      headers: {
74
        Authorization: `Bearer ${auth.token}`,
75
        Accept: "application/json",
76
      },
77
    })
78
    if (!response.ok) {
79
      throw new Error(`${response.status} ${await response.text()}`)
80
    }
81
    const page = (await response.json()) as {
82
      value: { [key: string]: any }[]
83
      "@odata.nextLink"?: string
84
    }
85
    items.push(...page.value)
86
    next = page["@odata.nextLink"]
87
  }
88

89
  // The watermark is the newest timestamp seen, so it follows SharePoint's clock
90
  const newest = items.reduce(
91
    (max, i) => Math.max(max, Date.parse(i[field])),
92
    since ?? 0
93
  )
94
  await wmill.setState(newest)
95

96
  // First run: set the watermark and don't emit a backlog
97
  if (since == null) return []
98

99
  return items.filter((i) => Date.parse(i[field]) > since)
100
}
101