Hi guys, i am stuck with how to create links betwe...
# orm-help
m
Hi guys, i am stuck with how to create links between tables in grapqhl: How do i link dashboard and portfolio? A dashboard can have many widgets, one of which is portfolio.. For example, a newsfeed could be another widget. Portfolio and newsfeed would be both widgets, but their types would differ. (according to settings that the user provides). With how i defined it below, i can’t query all the way from an user to his widgets and what they represent.
Copy code
type Dashboard {
  id: ID! @unique
  name: String!
  widgets: [Widget!]! @relation(name: "DashboardToWidgets", onDelete: SET_NULL)
  user: User! @relation(name: "UserToDashboard", onDelete: SET_NULL)
  createdAt: DateTime!
  updatedAt: DateTime!
}

type Widget {
  id: ID! @unique
  name: String!
  createdAt: DateTime!
  updatedAt: DateTime!
}

type Portfolio {
  id: ID! @unique
  name: String!
  widget: Widget!
  assets: [Asset!]! @relation(name: "PortfolioToAsset", onDelete: CASCADE)
  createdAt: DateTime!
  updatedAt: DateTime!
}
e
I think in this case it would be correct to use
extend
, to let Portfolio extend Widget. But I'm still a beginner at GraphQL myself so it's just a thought.
m
hmm looks like extend is just a way to clean up your schema
e
Maybe an approach like this, using
union
, so your Dashboard can have an array of multiple different widget types. https://stackoverflow.com/a/52102073
m
omg! look like thats what i was searching for!
thanks man! i will shed tears, if it is and if it isnt..
πŸ˜…
e
I was thinking I might run in to a similar issue like you are currently facing. So let me know how it works out, and if you solve it.
m
Copy code
Dashboard
    βœ– The field `widgets` has the type `[Widget!]!` but there's no type or enum declaration with that name.
😭 1
union Widget = Newsfeed | Portfolio
i am looking on this page, will try out this: https://github.com/prisma/prisma/issues/165#issuecomment-336125858
tldr: it’s currently not supported πŸ˜•
e
Alright, thanks for sharing your findings. The workaround doesn't seem too bad, making the fields optional.
I've also been using a
data: Json
type a bit, which can be any data when you need to set some flags etc. But think that type has to be converted to a
String
in some cases when Json is not supported.
m
yeah, that work as well i guess, but rather nasty suprise this is πŸ˜„
hmm, i think i got it to work
at least this is the docs from the graphql server side of things
the big idea is, organise your prisma side like whatever is compatible with prisma
create interfaces on the graphql api side of things
Copy code
interface Widget {
  id: ID!
  name: String!
}

type Portfolio implements Widget {
  id: ID!
  name: String!
  assets: [Asset!]!
  createdAt: String!
  updatedAt: String!
}

type Newsfeed implements Widget {
  id: ID!
  name: String!
  feed: String!
}
still working on accessing the right data, i think it requires some work but i will share my further experiences here
e
Very interesting. Are you then able to reference
type Dashboard { … widgets: [Widget!]! … }
or do you have to do it in a different way?
m
Copy code
type Dashboard {
  id: ID!
  name: String!
  panels: [Panel!]!
  user: User!
  createdAt: String!
  updatedAt: String!
}

type Panel {
  id: ID!
  name: String!
  widgets: [Widget!]!
}

interface Widget {
  id: ID!
  name: String!
}

type Portfolio implements Widget {
  id: ID!
  name: String!
  assets: [Asset!]!
  createdAt: String!
  updatedAt: String!
}

type Newsfeed implements Widget {
  id: ID!
  name: String!
  feed: String!
}
forgot i had panels as well πŸ˜‰
e
Alright, cool! πŸ’ͺ So this setup allows you to give both
Portfolio
and
Newsfeed
as a return value of
type Panel { … widgets: [Widget!]! }
?
m
hmm, but implementing the resolvers is another thing πŸ˜•
its not straighforward as i though, sigh
it should be doable
dang, queries work
the important bits (for the queries to work) In your prisma schema
Copy code
type Panel {
  id: ID! @unique
  name: String!
  dashboard: Dashboard! @relation(name: "DashboardToPanel", onDelete: SET_NULL)
}

type Portfolio {
  id: ID! @unique
  name: String!
  assets: [Asset!]! @relation(name: "PortfolioToAsset", onDelete: CASCADE)
  # not sure about relation below
  panel: Panel! @relation(name: "PortfolioToPanel", onDelete: SET_NULL)
  createdAt: DateTime!
  updatedAt: DateTime!
}

type Newsfeed {
  id: ID! @unique
  name: String!
  feed: String!
  # not sure about relation below
  panel: Panel! @relation(name: "NewsfeedToPanel", onDelete: SET_NULL)
  createdAt: DateTime!
  updatedAt: DateTime!
}
in your graphqlserver schema:
Copy code
type Query {
  widgets: [Widget!]!
}

type Panel {
  id: ID!
  name: String!
  dashboard: Dashboard!
  widgets: [Widget!]!
}

interface Widget {
  id: ID!
  name: String!
}

type Portfolio implements Widget {
  id: ID!
  name: String!
  assets: [Asset!]!
  createdAt: String!
  updatedAt: String!
}

type Newsfeed implements Widget {
  id: ID!
  name: String!
  feed: String!
}
Your query resolver:
Copy code
widgets: async (parent, args, { prisma }, info) => {
      return [
        ...(await prisma.query.portfolios(null, info)),
        ...(await prisma.query.newsfeeds(null, info)),
      ]
    }
Your Widget resolver:
Copy code
export const Widget = {
  __resolveType(book, context, info) {
    if (book.assets) {
      return 'Portfolio'
    }

    if (book.feed) {
      return 'Newsfeed'
    }

    return null
  },
}
Your query
Copy code
query {
  widgets {
    id
    name
    ... on Portfolio {
      id
      name
      assets {
        id
      }
    }
  ... on Newsfeed {
      id
      name
      feed
    }
  }
}
Response
Copy code
{
  "data": {
    "widgets": [
      {
        "id": "cjrb6ckus00610795cpx3y0gi",
        "name": "m",
        "assets": []
      },
      {
        "id": "cjrb6gb7j006i0795wswrw42h",
        "name": "m",
        "feed": "hola"
      }
    ]
  }
}
πŸŽ‰πŸŽ‰πŸŽ‰ You should also override the Panel resolver as such
Copy code
export const Panel = {
  widgets: async (parent, args, { prisma }, info) => {
    return [
      ...(await prisma.query.portfolios(null, info)),
      ...(await prisma.query.newsfeeds(null, info)),
    ]
  },
}
And add it to your resolvers of course
never thougt it would work but it does lol πŸ˜„ parrotwave1 parrotwave2 parrotwave3 as for the mutations, in those you are in full control so it should be easy.
-> gave up, just too difficult, chose to add all widgets as linked tables in panel
e
Wow, that was quite impressive. But you met some more unexpected roadblocks with this approach then?
m
yes, not everything works, try it out
e
Alright, basically too complex and still not ideal. Interesting seeing the process you've been through here. Would it maybe be better to just return an array of independent IDs (strings)? Then you do additional queries to resolve each IDs into a Newsfeed and Portfolio. Is that the solution you ended up with?
m
No, i decided to just add entry for each type within panel, having to make multiple trips to the api negates the whole purpose of using graphql (IMO)
Copy code
type Panel {
  portfolios: [Portfolios!]!
  newsfeeds: [Newsfeeds!]!
}
we are in early stages of development so it doesnt really matter for now
i will change this in the future once unions / interfaces are supporterd
e
"multiple trips to the api negates the whole purpose of using graphql", was thinking the same! But sometimes you get desperate. πŸ˜‚ Your current solution seems alright as well, but looking forward to having unions. πŸ’ͺ
m
indeed, i also tried to query the db directly btw (with some sweet sql lol)
but still some errors
you can follow this guid here:

https://www.youtube.com/watch?v=YUjlBuI8xsUβ–Ύ

e
@mark Said I was going to run into this down the line. πŸ˜‰ Do you still use the approach of the multiple widget types individually like:
Copy code
type Panel {
  portfolios: [Portfolios!]!
  newsfeeds: [Newsfeeds!]!
}
OR did you hack a
union
solution together in the end? I see Prisma still doesn't support
union
.
@mark Which solution did you end up with in the end for this?
m
yeah basically that and planning to add PanelSettings for the common settings, which is a one way relation only
Copy code
type Panel {
  portfolios: [Portfolios!]!
  newsfeeds: [Newsfeeds!]!
}

type PanelSetting {
  id: !ID
  # common panel related settings here
}

type Portfolio {
  panel: [Panel!]
  panelSetting: PanelSetting! #add this in prima schema "@relation(onDelete: CASCADE)"
}

type Newsfeed {
  panel: [Panel!]
  panelSetting: PanelSetting! #add this in prima schema "@relation(onDelete: CASCADE)"
}
e
Alright, makes a lot of sense. Thanks for the update! Seems like a good way to do it. πŸ‘