Has anyone integrated Discussions into Power BI for reporting/monitoring?

Hi everyone,

I’m exploring ways to manage and monitor Discussions from a Speckle project directly in Power BI.

From what I understand, the official Speckle Power BI connector is great for loading model/object data, but it doesn’t seem to expose Discussions as a table or entity. Since Discussions are part of the web app’s collaboration layer, I’m wondering if anyone here has:

  • Pulled Discussions data (title, status, author, created date, etc.) into Power BI
  • Used the GraphQL or REST API for this
  • Built any workflows or dashboards to track/monitor discussion threads for project management purposes

My goal is to create a dashboard that lets the team filter and visualize active vs. closed discussions, see activity by user/date, and monitor overall project communication trends.

Here’s a starting point for a GraphQL query to fetch discussions:

graphql

query {
  project(id: "7a858ef447") {
    discussions {
      items {
        id
        title
        status
        createdAt
        createdBy {
          name
        }
        comments {
          totalCount
        }
      }
    }
  }
}

Has anyone tried something similar? Any tips, sample queries, or Power Query steps would be much appreciated.

Thanks in advance!
— David

Yes,. you can query discussions the same way you’d query model data, just with a different GraphQL string. The core call in Power Query is:

Speckle.Api.Fetch(baseUrl, myGraphQLQuery, myVariables)

For example, to get the first page of discussions for a project:

threadsQuery = "
  query($projectId: String!) {
    project(id: $projectId) {
      discussions(limit: 10) {
        items { id title rawText createdAt }
      }
    }
  }
",

vars = [ projectId = "YOUR_PROJECT_ID" ],

resp = Speckle.Api.Fetch("https://app.speckle.systems", threadsQuery, vars)

From there you just expand resp[project][discussions][items] into a table and shape it however you like.

2 Likes

I’ll add that our teams are using Jonathon’s technique above for issue tracking in PowerBI at the moment and I have heard no complaints from them

3 Likes

Thanks a lot for the diagram Jon, I’ll try it out. :smiley:

Thank you so much for sharing your experience, Cris.
Regards :smiley:

Thanks a lot, Jon! I managed to build a Speckle Comments Extractor for Power BI. I’m sharing the script in case it’s useful to someone else. Requirements: Power BI Desktop (Data Extensions enabled), Speckle Desktop Service, and read access to the target project. Just replace projectId with your actual ID.

SPECKLE COMMENTS EXTRACTOR FOR POWER BI

Requirements: Power BI Desktop (Data Extensions), Speckle Desktop Service and read access to the target project.
Configure: projectId with your actual ID.

let
    // --- Basic configuration ---
    baseUrl = "https://app.speckle.systems",
    projectId = "{PROJECT_ID}",

    // --- GraphQL query ---
    threadsQuery = "
        query($projectId: String!, $filter: ProjectCommentsFilter) {
            project(id: $projectId) {
                commentThreads(filter: $filter) {
                    totalCount
                    items {
                        id
                        rawText
                        createdAt
                        updatedAt
                        archived
                        authorId
                        author { name id }
                        replies {
                            totalCount
                            items {
                                id
                                rawText
                                createdAt
                                authorId
                                author { name id }
                            }
                        }
                        viewerState
                    }
                }
            }
        }
    ",

    // --- Query variables ---
    vars = [
        projectId = projectId,
        filter = [ includeArchived = true ]
    ],

    // --- Step 1: Fetch data from API ---
    response = Speckle.Api.Fetch(baseUrl, threadsQuery, vars),
    threads = response[project][commentThreads][items],
    threadsTable = Table.FromList(threads, Splitter.SplitByNothing()),

    // --- Step 2: Expand thread structure ---
    expandedThreads = Table.ExpandRecordColumn(
        threadsTable,
        "Column1",
        {"id", "rawText", "createdAt", "updatedAt", "archived", "authorId", "author", "replies", "viewerState"},
        {"ThreadId", "RawText", "CreatedAt", "UpdatedAt", "Archived", "AuthorId", "Author", "Replies", "ViewerState"}
    ),
    expandedAuthor = Table.ExpandRecordColumn(
        expandedThreads,
        "Author",
        {"name", "id"},
        {"AuthorName", "AuthorIdDetail"}
    ),

    // --- Step 3: Time-based metrics ---
    withTimeCalculations = Table.AddColumn(
        expandedAuthor,
        "TimeMetrics",
        each
            let
                createdDateTime = DateTime.FromText([CreatedAt]),
                now = DateTime.LocalNow(),
                dDays = Number.RoundDown(Duration.TotalDays(now - createdDateTime)),
                dHours = Number.RoundDown(Duration.TotalHours(now - createdDateTime)),
                dMinutes = Number.RoundDown(Duration.TotalMinutes(now - createdDateTime)),
                timeAgoText =
                    if dDays >= 1 then Text.From(dDays) & " days ago"
                    else if dHours >= 1 then Text.From(dHours) & " hours ago"
                    else if dMinutes >= 1 then Text.From(dMinutes) & " minutes ago"
                    else "Just now",
                activityLevel =
                    if dDays = 0 then "Today"
                    else if dDays <= 7 then "Recent"
                    else if dDays <= 30 then "Active"
                    else "Old"
            in
                [
                    DaysSinceCreated = dDays,
                    HoursSinceCreated = dHours,
                    MinutesSinceCreated = dMinutes,
                    TimeAgoText = timeAgoText,
                    ActivityLevel = activityLevel
                ]
    ),
    expandedTimeMetrics = Table.ExpandRecordColumn(
        withTimeCalculations,
        "TimeMetrics",
        {"DaysSinceCreated", "HoursSinceCreated", "MinutesSinceCreated", "TimeAgoText", "ActivityLevel"},
        {"DaysSinceCreated", "HoursSinceCreated", "MinutesSinceCreated", "TimeAgoText", "ActivityLevel"}
    ),

    // --- Step 4: Discussion metrics ---
    withMetrics = Table.AddColumn(
        expandedTimeMetrics,
        "CommentMetrics",
        each
            let
                repliesRecord = [Replies],
                totalReplies = if repliesRecord <> null then Record.Field(repliesRecord, "totalCount") else 0,
                repliesItems = if repliesRecord <> null then Record.Field(repliesRecord, "items") else {},
                threadCreatedAt = DateTime.FromText([CreatedAt]),
                threadUpdatedAt = if [UpdatedAt] <> null then DateTime.FromText([UpdatedAt]) else threadCreatedAt,
                replyDates =
                    if List.Count(repliesItems) > 0
                    then List.Transform(repliesItems, each DateTime.FromText(Record.Field(_, "createdAt")))
                    else {},
                firstCommentDate =
                    if List.Count(replyDates) > 0
                    then List.Min({threadCreatedAt} & replyDates)
                    else threadCreatedAt,
                lastCommentDate =
                    if List.Count(replyDates) > 0
                    then List.Max({threadCreatedAt} & replyDates)
                    else threadUpdatedAt,
                status = if [Archived] = true then "Resolved" else "Open"
            in
                [
                    ResponseCount = totalReplies,
                    FirstCommentDate = firstCommentDate,
                    LastCommentDate = lastCommentDate,
                    Status = status
                ]
    ),
    expandedMetrics = Table.ExpandRecordColumn(
        withMetrics,
        "CommentMetrics",
        {"ResponseCount", "FirstCommentDate", "LastCommentDate", "Status"},
        {"ResponseCount", "FirstCommentDate", "LastCommentDate", "Status"}
    ),

    // --- Step 5: Display-friendly title ---
    withTitle = Table.AddColumn(
        expandedMetrics,
        "Title",
        each
            if [RawText] <> null and Text.Length([RawText]) > 0 then
                if Text.Length([RawText]) > 50
                then Text.Start([RawText], 47) & "..."
                else [RawText]
            else
                "Comment " & Text.End([ThreadId], 8)
    ),

    // --- Step 6: Data types and cleanup ---
    finalTable =
        Table.TransformColumnTypes(
            Table.RemoveColumns(withTitle, {"Replies", "AuthorIdDetail", "ViewerState"}),
            {
                {"CreatedAt", type datetime},
                {"UpdatedAt", type datetime},
                {"FirstCommentDate", type datetime},
                {"LastCommentDate", type datetime},
                {"ResponseCount", Int64.Type},
                {"Archived", type logical},
                {"DaysSinceCreated", Int64.Type},
                {"HoursSinceCreated", Int64.Type},
                {"MinutesSinceCreated", Int64.Type}
            }
        )
in
    finalTable

I’ll be glad if this helps others in the community. Feedback or improvements are welcome!

1 Like

Hey @David_Moreira ,

After seeing your message, i created a helper function for our data connector. Once that’s merged in, users will be able to load their discussions with a single line:
Speckle.GetDiscussions(url as Uri.Type, optional getReplies as logical) as table

Also, can i ask why you picked these specific fields:

                {"CreatedAt", type datetime},
                {"UpdatedAt", type datetime},
                {"FirstCommentDate", type datetime},
                {"LastCommentDate", type datetime},
                {"ResponseCount", Int64.Type},
                {"Archived", type logical},
                {"DaysSinceCreated", Int64.Type},
                {"HoursSinceCreated", Int64.Type},
                {"MinutesSinceCreated", Int64.Type}

Can you describe how you intend to use them? I picked some other fields but adding them should be easy.

1 Like

Thanks a lot for creating the new helper function! :raising_hands: Speckle.GetDiscussions will be really useful and definitely simplify the process.

Regarding the fields I included: my idea was to prepare a dashboard for supervision and coordination of work teams. That’s why I added time-based metrics (days/hours/minutes since creation, activity level), discussion status (open/resolved), and the number of messages in each thread.

This combination makes it easier to track the freshness of discussions, identify unresolved issues, and measure activity levels. In particular, the message counts help to analyze potential bottlenecks in coordination, since high numbers of replies or long threads often indicate areas where the team needs extra alignment.

Thanks again, Mucahit!

1 Like