Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Virtual field breaks AdminUI #3210

Closed
ra-external opened this issue Jun 29, 2020 · 7 comments
Closed

Virtual field breaks AdminUI #3210

ra-external opened this issue Jun 29, 2020 · 7 comments

Comments

@ra-external
Copy link

ra-external commented Jun 29, 2020

I set up a virtual field relatedArticles in my Article field

relatedArticles: {
	
				schemaDoc: 'Articles related to this article',
				type: Virtual,
				graphQLReturnType: '[Article]',
				resolver: virtualRelatedFieldsResolver
			}

the resolver for which is as follows


import { sampleSize } from 'lodash';
import log from '../../util/log';
import { PUBLISHED } from '../../lists/Article';

const NUMBER_OF_RELATED_ARTICLES = 3;

const ARTICLE_QUERY = `query Article($where:ArticleWhereUniqueInput!) {
  Article(where:$where){
		url
    topic {
       topic
    }
  }
}`;

const ARTICLES_QUERY = `query allArticles($where:ArticleWhereInput) {
  allArticles(where:$where){
    title
    url
    cardDescription
  }
}`;

type articleQueryReturn = {
	Article: {
		data: any;
		errors: any;
		[key: string]: any;
	};
};

type articlesQueryReturn = {
	allArticles: {
		data: any;
		errors: any;
		[key: string]: any;
	};
};

export default async (item, args, context) => {
	try {
		log.info('entering related articles', item, args);
		const id = item?.id;
		if (!id) throw new Error('getRelatedArticlesVirtualField: No ID passed');
		if (typeof id !== 'string') throw new Error('getRelatedArticlesVirtualField:  ID passed is not a string');

		// get current article based on ID passed
		const skipAccessControl = true;
		// const result = await keystone.executeQuery(ARTICLE_QUERY, { variables: { where: { id } } });
		const { data, errors }: { data: articleQueryReturn; errors: any } = await context.executeGraphQL({
			query: ARTICLE_QUERY,
			variables: { where: { id } }
		});
		if (errors) throw new Error(`getRelatedArticlesVirtualField: Error getting article from ID: ${errors.toString()}`);
		const article = data?.Article;
		// if this article has no topics, punt
		if (!article?.topic || !article?.topic?.length) throw new Error('getRelatedArticlesVirtualField: ID does not return any article');

		// get the topics from this article
		const selectedTopics = article.topic.map((topicObj) => topicObj.topic);

		// find all related articles
		/*	const result2 = await keystone.executeQuery(ARTICLES_QUERY, {
				variables: { where: { status: PUBLISHED, topic_some: { topic_in: selectedTopics } } }
			});*/
		const { data: data2, errors: errors2 }: { data: articlesQueryReturn; errors: any } = await context.executeGraphQL({
			context: context.createContext({ skipAccessControl }),
			query: ARTICLES_QUERY,
			variables: { where: { status: PUBLISHED, topic_some: { topic_in: selectedTopics } } }
		});
		// if there is a problem getting these then punt
		if (errors2) throw new Error(`getRelatedArticlesVirtualField: Error getting related articles: ${errors.toString()}`);

		if (!data2?.allArticles)
			throw new Error(
				`getRelatedArticlesVirtualField: error with fetching related articles from ID: no articles returned with these topics:
				${selectedTopics}`
			);
		const relatedArticles = data2?.allArticles;
		// remove the current article
		const filtered = relatedArticles.filter((relatedArticle) => relatedArticle.url !== article.url);
		// now get  NUMBER_OF_RELATED_ARTICLES randomly from this array
		return sampleSize(filtered, NUMBER_OF_RELATED_ARTICLES);
	} catch (error) {
		log.error(`error in getting related articles ${error.toString()}`);
		return [];
	}
};

and this worked fine. However when I went to the AdminUI and clicked on an article I got an error

Couldn't find an Article matching that ID

when I checked the browser console log I saw

image

and when I checked the Keystone logs I found

query getItem { .. }
 ⤷ inspect @ http://localhost:4545/admin/graphiql/go?id=1539bcba2c6b7950ea38ca56b7276dd3b674344a
{"level":50,"time":1593432803526,"pid":13566,"hostname":"AGOMA1002.local","name":"graphql","message":"Field \"relatedArticles\" of type \"[Article]\" must have a selection of subfields. Did you mean \"relatedArticles { ... }\"?","locations":[{"line":38,"column":5}],"uid":"ckc0gr0060001guwk6h1x5m2l","name":"ValidationError","stack":"Object.Field (node_modules/graphql/validation/rules/ScalarLeafs.js:45:31)\nObject.enter (node_modules/graphql/language/visitor.js:324:29)\nObject.enter (node_modules/graphql/language/visitor.js:375:25)\nvisit (node_modules/graphql/language/visitor.js:242:26)\nObject.validate (node_modules/graphql/validation/validate.js:73:24)\nvalidate (node_modules/apollo-server-core/dist/requestPipeline.js:221:34)\nObject.<anonymous> (node_modules/apollo-server-core/dist/requestPipeline.js:118:42)\nGenerator.next (<anonymous>)\nfulfilled (node_modules/apollo-server-core/dist/requestPipeline.js:5:58)\nrunMicrotasks (<anonymous>)\n","v":1}
{"level":30,"time":1593432803527,"pid":13566,"hostname":"AGOMA1002.local","req":{"id":31,"method":"POST","url":"/admin/api","headers":{"host":"localhost:4545","connection":"keep-alive","content-length":"812","pragma":"no-cache","cache-control":"no-cache","accept":"*/*","user-agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1","content-type":"application/json","origin":"http://localhost:4545","sec-fetch-site":"same-origin","sec-fetch-mode":"cors","sec-fetch-dest":"empty","referer":"http://localhost:4545/admin/articles/5eeb2fb96103015885720c90","accept-encoding":"gzip, deflate, br","accept-language":"en-GB,en-US;q=0.9,en;q=0.8,es;q=0.7,de;q=0.6","cookie":"keystone.sid=s%3Aa5X5eLDp8NYH05e5GOrVm-2GcT58ISr0.9RiZ2s3BflE4yNRCa1rGZlbMKhHyDKRo5dUniW3%2BnRk"},"remoteAddress":"::1","remotePort":53023},"res":{"statusCode":400,"headers":{"x-powered-by":"Express","x-keystone-app-version":"1.0.0","access-control-allow-origin":"http://localhost:4545","vary":"Origin","access-control-allow-credentials":"true","content-type":"application/json; charset=utf-8","content-length":"1622","etag":"W/\"656-8Ep+1ytrsZl4HYFFDtrb/HiYjiw\""}},"responseTime":5,"msg":"request completed","v":1}

which led to the query

query getItem($id: ID!) {
  Article(where: {id: $id}) {
    _label_
    id
    title
    url
    text
    author {
      id
      _label_
      __typename
    }
    articleType
    cardDescription
    cardImage {
      id
      _label_
      __typename
    }
    headerImage {
      id
      _label_
      __typename
    }
    topic {
      id
      _label_
      __typename
    }
    status
    publishDate
    readTime
    proposal {
      id
      _label_
      __typename
    }
    relatedArticles
    updatedBy {
      id
      _label_
      __typename
    }
    createdBy {
      id
      _label_
      __typename
    }
    updatedAt
    createdAt
    __typename
  }
}

If I removed the GraphQLReturnType from my virtual field description, the AdminUI worked but the GraphQL API broke, since it was now assuming that the relatedFields were a string, not an array of Articles.

@gautamsi
Copy link
Member

you need to add graphQLReturnFragment, see this example https://www.keystonejs.com/keystonejs/fields/src/types/virtual/#complex-return-types

    graphQLReturnFragment: `{
        ...ArticleFields
      }`,

@ra-external
Copy link
Author

@gautamsi Thanks -- I'll try that. What is the role of graphQLReturnFragment then? It says "For more complex types you can define a graphQLReturnFragment as well as extendGraphQLTypes" -- but it seems optional. What does it do here, and why, in my case, is it necessary? If it's always necessary for complex types the documentation should state that clearly (in the section above it says "If the return type is not String then you need to define graphQLReturnType", which is clear -- you have to define graphQLReturnType).

@ra-external
Copy link
Author

I would suggest -- if it's accurate -- that the documentation say something like

"For more complex types you must also define a graphQLReturnFragment as well as extendGraphQLTypes."

@stale
Copy link

stale bot commented Oct 28, 2020

It looks like there hasn't been any activity here in over 6 months. Sorry about that! We've flagged this issue for special attention. It wil be manually reviewed by maintainers, not automatically closed. If you have any additional information please leave us a comment. It really helps! Thank you for you contribution. :)

@stale stale bot added the needs-review label Oct 28, 2020
@asaad2691
Copy link

nice! it really help me a lot to learn and give me a lot of information. #Virtual Visual Field

@stale stale bot removed the needs-review label Nov 14, 2020
@stale
Copy link

stale bot commented Mar 19, 2021

It looks like there hasn't been any activity here in over 6 months. Sorry about that! We've flagged this issue for special attention. It wil be manually reviewed by maintainers, not automatically closed. If you have any additional information please leave us a comment. It really helps! Thank you for you contribution. :)

@stale stale bot added the needs-review label Mar 19, 2021
@bladey
Copy link
Contributor

bladey commented Apr 8, 2021

Keystone 5 has officially moved into active maintenance mode as we push towards the next major new version Keystone Next, you can find out more information about this transition here.

In an effort to sustain the project going forward, we're cleaning up and closing old issues such as this one. If you feel this issue is still relevant for Keystone Next, please let us know.

@bladey bladey closed this as completed Apr 8, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants