snapshot-labs / snapshot

Interface for Snapshot. Join us on Discord http://discord.snapshot.org
https://snapshot.org
MIT License
9.22k stars 1.23k forks source link

User Activities API Spec #2129

Closed midgerate closed 2 years ago

midgerate commented 2 years ago

The plan is to create a new GQL query

user_activities {
  type: "VOTE" | "CREATE_PROPOSAL" | "FOLLOW_SPACE"!
  account: string!
  timestamp: number!
  vote: Vote
  proposal: Proposal
  follow: Follow
}

Filters - account (eq), timestamp(eq, gte, let), type (eq, array-in)
Sort - timestamp
Limit (similar to what we already have)
  1. This will allow the client to consume this API based on the type of the activity and customise the UI as the type changes.
  2. Having separate types of vote, proposal, follow allows for future extensibility.
  3. Common timestamp allows to sort the activities chronologically.
  4. You cannot filter by a specific proposal / space / vote etc.
midgerate commented 2 years ago

@samuveth would this allow you to work with the design? @bonustrack @mktcode is the proposed structure good?

mktcode commented 2 years ago

This is what you want: https://graphql.org/learn/queries/#inline-fragments

Example from GitHub:

query { 
  repository (owner: "snapshot-labs" name: "snapshot") {
    issue (number: 2129) {
      timelineItems (first: 10) {
        nodes {
          __typename
          ... on IssueComment {
            author {
              login
            }
            body
          }
          ... on AssignedEvent {
            actor {
              login
            }
            assignee {
              __typename
              ... on User {
                login
              }
            }
          }
        }
      }
    }
  }
}

Try here: https://docs.github.com/en/graphql/overview/explorer

This would be the most "correct" way of doing it. But not sure how much that suits us. :P I mean... How much time we want to spend on the GraphQL part. Is this something other projects might want to use? Then it should be well designed I guess.

mktcode commented 2 years ago

I've used inline fragments in the past. I've found them hard to configure as well as consume.

Could you elaborate on that maybe? What problems did you face?

midgerate commented 2 years ago
  1. Haven't tested the filters, especially the created and type where filters and how it will work with the current code.
  2. Haven't explored edge cases and didn't try to break the API yet.
  3. The code is the first version and not the final code. I need some feedback before I clean it up.
  4. The votes do not include the proposal yet and maybe other fields are also missing.

Here's the logic. first, fetch 10-10 results from proposals and votes and check the created of the last record for each of those lists if we see that the created timestamp is different then I fetch the remaining proposals/votes up till the difference timestamp then I merge it into the original list and return the data

I am using Seek based pagination https://blog.jooq.org/faster-sql-paging-with-jooq-using-the-seek-method/

Here's the link to the code. https://github.com/snapshot-labs/snapshot-hub/commit/b947b29e25eb7ce189e8066145a5a0b019f96a1c

@mktcode @bonustrack Please give feedback on the approach / optimisations etc.

First Page Query

query Activities {
  activities {
    seek
    count
    activities {
      created
      type
      account
      payload {
        __typename
        ... on ProposalActivity{
          title
          space {
            name
          }
        }
        ... on VoteActivity{
          choice
          space {
            name
          }
        }
      }
    }
  }
}

Result

{
  "data": {
    "activities": {
      "seek": 1636014203,
      "count": 24,
      "activities": [
        {
          "created": 1647015199,
          "type": "proposal_created",
          "account": "0x04DB1bB49b7fFBcEC574f34D29c3153953890352",
          "payload": {
            "__typename": "ProposalActivity",
            "title": "ACV",
            "space": {
              "name": "midgerate.eth"
            }
          }
        },
        {
          "created": 1641448724,
          "type": "voted",
          "account": "0x24F15402C6Bb870554489b2fd2049A85d75B982f",
          "payload": {
            "__typename": "VoteActivity",
            "choice": 1,
            "space": {
              "name": "Thanku.eth Space"
            }
          }
        },
      ]
    }
  }
}

Second Query (take seek from result and send in the variable)

query Activities {
  activities(seek:1636014203) {
    seek
    count
    activities {
      created
      type
      account
      payload {
        __typename
        ... on ProposalActivity{
          title
          space {
            name
          }
        }
        ... on VoteActivity{
          choice
          space {
            name
          }
        }
      }
    }
  }
}
bonustrack commented 2 years ago

This logic should be on frontend, hub API should be as light as possible and do only things thats not possible to do somewhere else. Pagination should be consistent across all GraphQL queries using "first" and "skip".

bonustrack commented 2 years ago

The proposed implementation is not the right way to do it. If we want implement this on hub we would need to create a table with activities, this way we can query the number of activities we need without doing multiple requests.

mktcode commented 2 years ago

@bonustrack What do you mean with "as light as possible"? This is confusing me. Can you maybe define the exact purpose and scope of the hub? The current readme in the repo, doesn't even mention the GraphQL API.

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.