struensee
struensee

Reputation: 606

Can I use a directive on a fragment and call its variable conditionally?

I'm working on an app that allows users to upload large datasets. The uploader has a "draft" version that they can edit in the UI, then they publish snapshots of the draft, which all users can see. The draft files should only be queried if the uploader is logged in.

While non-edit privilege users don't see the draft, the app still queries its entire fileset, which makes fetching the dataset page really slow.

The query has a fragment for the "drafts" data, including the conditionally unwanted files. If I omit the files from the fragment, the component works for all users...but then the uploader can't get their draft files. So it's this damned if I do, damned if I don't quandary and my lack of experience with graphql and apollo really shines here.

Here's the query and accompanying hook:

export const getDatasetPage = gql`
  query dataset($datasetId: ID!) {
    dataset(id: $datasetId) {
      id
      created
      public
      following
      starred
      ...DatasetDraft
      ...DatasetPermissions
      ...DatasetSnapshots
      ...DatasetIssues
      ...DatasetMetadata
      ...DatasetComments
      uploader {
        id
        name
        email
      }
      analytics {
        downloads
        views
      }
      onBrainlife
    }
  }
  ${DatasetQueryFragments.DRAFT_FRAGMENT}
  ${DatasetQueryFragments.PERMISSION_FRAGMENT}
  ${DatasetQueryFragments.DATASET_SNAPSHOTS}
  ${DatasetQueryFragments.DATASET_ISSUES}
  ${DatasetQueryFragments.DATASET_METADATA}
  ${DATASET_COMMENTS}
`

export const DatasetQueryHook = ({ datasetId }) => {
  const {
    data: { dataset },
    loading,
    error,
  } = useQuery(getDatasetPage, {
    variables: { datasetId },
  })
  if (loading) {
    return <Spinner text="Loading Dataset" active />
  } else {
    if (error) Sentry.captureException(error)
    return (
      <ErrorBoundary error={error} subject={'error in dataset page'}>
        <DatasetQueryContext.Provider
          value={{
            datasetId,
          }}>
          <DatasetPage dataset={dataset} />
        </DatasetQueryContext.Provider>
      </ErrorBoundary>
    )
  }
}

And this is the fragment:

export const DRAFT_FRAGMENT = gql`
  fragment DatasetDraft on Dataset {
    id
    draft {
      id
      modified
      readme
      partial
      description {
        Name
        Authors
        DatasetDOI
        License
        Acknowledgements
        HowToAcknowledge
        Funding
        ReferencesAndLinks
      }
      files {    //when this is removed, it works...except for users with edit privileges
        id
        filename
        size
      }
      summary {
        modalities
        sessions
        subjects
        subjectMetadata {
          participantId
          age
          sex
          group
        }
        tasks
        size
        totalFiles
        dataProcessed
      }
    }
  }
`

Long story short: The query should omit draft files if the user doesn't have edit privileges. I know the logic for hasEdit, but I don't know how I can implement it using a directive. Can I even use @skip on a fragment, and even if so, can I pass its variable conditionally? I can't find anything in the docs about this and at a loss for a solution.

Upvotes: 3

Views: 5740

Answers (1)

Daniel Rearden
Daniel Rearden

Reputation: 84777

Yes, directives can be used inside fragments and, yes, you can use a variable for this.

Here's a simple example using the SWAPI API:

query MyQuery ($showDate: Boolean!){
  allFilms {
    films {
      ...FilmFields
    }
  }
}

fragment FilmFields on Film {
  id
  title
  releaseDate @include(if: $showDate)
}

One thing to note here is that the variable is still defined as part of the operation. That means if you put your fragments in separate files and then import them inside your queries, you need to make sure A) the operation actually defines the variable and B) the name of the variable is correct.

Bonus

Also worth noting is that there is experimental support for defining variables as part of fragments. This has to be explicitly enabled server-side. If you're using graphql-tag, it also has to be explicitly enabled there as well. If you're using makeExecutableSchema or Apollo Server, you can pass in a parseOptions parameter to enable this feature server side:

makeExecutableSchema({
  typeDefs,
  resolvers,
  parseOptions: {
    experimentalFragmentVariables: true,
  },
})

Then you can create fragments like:

fragment FilmFields on Film ($showDate: Boolean!) {
  id
  title
  releaseDate @include(if: $showDate)
}

Upvotes: 12

Related Questions