From 1a804248267c191e6b42fea58683753d7dfc83ba Mon Sep 17 00:00:00 2001 From: avinash Date: Wed, 25 Jan 2017 13:27:03 +0530 Subject: [PATCH] search and pagination functionality done --- gql-schema/schema.graphql | 75 ++++++-- gql-schema/schema.js | 74 ++++++-- gql-schema/schema.json | 349 +++++++++++++++++++++++++++++++++----- js/components/App.js | 58 ++++++- js/routes/AppHomeRoute.js | 2 +- 5 files changed, 483 insertions(+), 75 deletions(-) diff --git a/gql-schema/schema.graphql b/gql-schema/schema.graphql index d37d36f..8ed0f78 100644 --- a/gql-schema/schema.graphql +++ b/gql-schema/schema.graphql @@ -2,6 +2,27 @@ schema { query: TwitterAPI } +# An object with an ID +interface Node { + # The id of the object. + id: ID! +} + +# Information about pagination in a connection. +type PageInfo { + # When paginating forwards, are there more items? + hasNextPage: Boolean! + + # When paginating backwards, are there more items? + hasPreviousPage: Boolean! + + # When paginating backwards, the cursor to continue. + startCursor: String + + # When paginating forwards, the cursor to continue. + endCursor: String +} + # Retweet of a tweet type Retweet { id: ID @@ -25,23 +46,38 @@ type Tweet { retweets(limit: Int = 5): [Retweet] } +# A connection to a list of items. +type TweetConnection { + # Information to aid in pagination. + pageInfo: PageInfo! + + # A list of edges. + edges: [TweetEdge] +} + +# An edge in a connection. +type TweetEdge { + # The item at the end of the edge + node: Tweet + + # A cursor for use in pagination + cursor: String! +} + # The Twitter API type TwitterAPI { + # Fetches an object given its ID + node( + # The ID of an object + id: ID! + ): Node tweet( # Unique ID of tweet id: String! ): Tweet # Returns a collection of relevant Tweets matching a specified query. - search( - # A UTF-8, URL-encoded search query of 500 characters maximum, including - # operators. Queries may additionally be limited by complexity. - q: String! - - # The number of tweets to return per page, up to a maximum of 100. This was - # formerly the “rpp” parameter in the old Search API. - count: Int - ): Viewer + search: Viewer } # Twitter user @@ -66,6 +102,23 @@ type TwitterUser { tweets(limit: Int = 10): [Tweet] } -type Viewer { - tweet: [Tweet] +type Viewer implements Node { + # The ID of an object + id: ID! + + # Returns a list of tweets matching the query, to be specified as an argument + tweetConnection( + after: String + first: Int + before: String + last: Int + + # A UTF-8, URL-encoded search query of 500 characters maximum, including + # operators. Queries may additionally be limited by complexity. + q: String + + # The number of tweets to return per page, up to a maximum of 100. This was + # formerly the “rpp” parameter in the old Search API. + count: Int + ): TweetConnection } diff --git a/gql-schema/schema.js b/gql-schema/schema.js index 8e62b62..e8d36f1 100644 --- a/gql-schema/schema.js +++ b/gql-schema/schema.js @@ -1,3 +1,4 @@ + import { GraphQLSchema, GraphQLObjectType, @@ -7,9 +8,38 @@ import { GraphQLInt, GraphQLList } from 'graphql'; + +import { + globalIdField, + fromGlobalId, + nodeDefinitions, + connectionDefinitions, + connectionArgs, + connectionFromPromisedArray, + mutationWithClientMutationId +} from "graphql-relay"; + import * as twitterCli from '../twitter-cli'; import * as Weather from '../weather'; +class Store {} +let store = new Store(); + +let nodeDefs = nodeDefinitions( + (globalId) => { + let {type} = fromGlobalId(globalId); + if (type === 'Viewer') { + return store + } + return null; + }, + (obj) => { + if (obj instanceof Store) { + return viewerType; + } + return null; + } + ); let UserType = new GraphQLObjectType({ name: 'TwitterUser', description: 'Twitter user', @@ -95,21 +125,43 @@ let RetweetType = new GraphQLObjectType({ }) }); +let tweetConnection = connectionDefinitions({ + name: 'Tweet', + nodeType: TweetType +}); + let viewerType = new GraphQLObjectType({ name: 'Viewer', fields: { - tweet: { - type: new GraphQLList(TweetType), - resolve: (viewer) => viewer, - }, + id: globalIdField("Viewer"), + tweetConnection: { + type: tweetConnection.connectionType, + description:"Returns a list of tweets matching the query, to be specified as an argument", + args: { + ...connectionArgs, + q: { + type: GraphQLString, + description: "A UTF-8, URL-encoded search query of 500 characters maximum, including operators. Queries may additionally be limited by complexity." + }, + count: { + type: GraphQLInt, + description: "The number of tweets to return per page, up to a maximum of 100. This was formerly the “rpp” parameter in the old Search API." + } + }, + resolve: (_, args) => connectionFromPromisedArray(twitterCli.searchTweets(args),args) + }, }, + interfaces: [nodeDefs.nodeInterface] }); + + let twitterType = new GraphQLObjectType({ name: 'TwitterAPI', description: 'The Twitter API', fields: { - tweet: { + node: nodeDefs.nodeField, + tweet: { type: TweetType, args: { id: { @@ -122,17 +174,7 @@ let twitterType = new GraphQLObjectType({ search: { type: viewerType, description: "Returns a collection of relevant Tweets matching a specified query.", - args: { - q: { - type: new GraphQLNonNull(GraphQLString), - description: "A UTF-8, URL-encoded search query of 500 characters maximum, including operators. Queries may additionally be limited by complexity." - }, - count: { - type: GraphQLInt, - description: "The number of tweets to return per page, up to a maximum of 100. This was formerly the “rpp” parameter in the old Search API." - } - }, - resolve: (_, searchArgs) => twitterCli.searchTweets(searchArgs) + resolve: () => store } } }); diff --git a/gql-schema/schema.json b/gql-schema/schema.json index c8ce2ee..34889a4 100644 --- a/gql-schema/schema.json +++ b/gql-schema/schema.json @@ -13,18 +13,18 @@ "description": "The Twitter API", "fields": [ { - "name": "tweet", - "description": null, + "name": "node", + "description": "Fetches an object given its ID", "args": [ { "name": "id", - "description": "Unique ID of tweet", + "description": "The ID of an object", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", - "name": "String", + "name": "ID", "ofType": null } }, @@ -32,20 +32,20 @@ } ], "type": { - "kind": "OBJECT", - "name": "Tweet", + "kind": "INTERFACE", + "name": "Node", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "search", - "description": "Returns a collection of relevant Tweets matching a specified query.", + "name": "tweet", + "description": null, "args": [ { - "name": "q", - "description": "A UTF-8, URL-encoded search query of 500 characters maximum, including operators. Queries may additionally be limited by complexity.", + "name": "id", + "description": "Unique ID of tweet", "type": { "kind": "NON_NULL", "name": null, @@ -56,18 +56,20 @@ } }, "defaultValue": null - }, - { - "name": "count", - "description": "The number of tweets to return per page, up to a maximum of 100. This was formerly the “rpp” parameter in the old Search API.", - "type": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - }, - "defaultValue": null } ], + "type": { + "kind": "OBJECT", + "name": "Tweet", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "search", + "description": "Returns a collection of relevant Tweets matching a specified query.", + "args": [], "type": { "kind": "OBJECT", "name": "Viewer", @@ -82,6 +84,49 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "SCALAR", + "name": "ID", + "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INTERFACE", + "name": "Node", + "description": "An object with an ID", + "fields": [ + { + "name": "id", + "description": "The id of the object.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": [ + { + "kind": "OBJECT", + "name": "Viewer", + "ofType": null + } + ] + }, { "kind": "SCALAR", "name": "String", @@ -190,16 +235,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "SCALAR", - "name": "ID", - "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, { "kind": "SCALAR", "name": "Int", @@ -481,15 +516,253 @@ "description": null, "fields": [ { - "name": "tweet", - "description": null, + "name": "id", + "description": "The ID of an object", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "tweetConnection", + "description": "Returns a list of tweets matching the query, to be specified as an argument", + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "q", + "description": "A UTF-8, URL-encoded search query of 500 characters maximum, including operators. Queries may additionally be limited by complexity.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "count", + "description": "The number of tweets to return per page, up to a maximum of 100. This was formerly the “rpp” parameter in the old Search API.", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "TweetConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + { + "kind": "INTERFACE", + "name": "Node", + "ofType": null + } + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "TweetConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", "args": [], "type": { "kind": "LIST", "name": null, "ofType": { "kind": "OBJECT", - "name": "Tweet", + "name": "TweetEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PageInfo", + "description": "Information about pagination in a connection.", + "fields": [ + { + "name": "hasNextPage", + "description": "When paginating forwards, are there more items?", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "hasPreviousPage", + "description": "When paginating backwards, are there more items?", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "startCursor", + "description": "When paginating backwards, the cursor to continue.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "endCursor", + "description": "When paginating forwards, the cursor to continue.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Boolean", + "description": "The `Boolean` scalar type represents `true` or `false`.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "TweetEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Tweet", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", "ofType": null } }, @@ -845,16 +1118,6 @@ ], "possibleTypes": null }, - { - "kind": "SCALAR", - "name": "Boolean", - "description": "The `Boolean` scalar type represents `true` or `false`.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, { "kind": "OBJECT", "name": "__Field", diff --git a/js/components/App.js b/js/components/App.js index 228e721..61c174f 100644 --- a/js/components/App.js +++ b/js/components/App.js @@ -1,10 +1,29 @@ import React from 'react'; import Relay from 'react-relay'; +import {debounce} from "lodash"; import { Card, CardHeader, CardText } from 'material-ui/Card'; import './app.css'; class App extends React.Component { + constructor(props) { + super(props); + this.setVariables = debounce(this.props.relay.setVariables, 1000); + } + + _search = (e) => { + this.setVariables({ query: e.target.value , after:null , before:null,currentPage:1}); + } + + _previousPage() { + this.setVariables({before:this.props.tweet.tweetConnection.pageInfo.startCursor,after:null, + last:5,first:null,currentPage:this.props.relay.variables.currentPage-1}) + } + + _nextPage() { + this.setVariables({before:null,after:this.props.tweet.tweetConnection.pageInfo.endCursor, + last:null,first:5,currentPage:this.props.relay.variables.currentPage+1}) + } trimDate(fullDate) { const dateLength = fullDate.length; @@ -13,15 +32,24 @@ class App extends React.Component { } render() { + var maxPage = this.props.relay.variables.count / this.props.relay.variables.first; return (

Twitteratis

- +
- {this.props.tweet.tweet.map(tweet => + + + {this.props.tweet.tweetConnection.edges.map(({node:tweet}) => // id: {tweet.id}

{tweet.text}

-- @{tweet.user.screen_name}
Relay.QL` fragment on Viewer { - tweet{ - id, + id, + tweetConnection(first:$first,last:$last,q:$query,count:$count,after:$after,before:$before){ + pageInfo{ + hasNextPage, + hasPreviousPage, + startCursor, + endCursor + }, + edges { + cursor, + node { + id, text, created_at user{ screen_name, profile_image_url } + } + } + } } `, diff --git a/js/routes/AppHomeRoute.js b/js/routes/AppHomeRoute.js index c4543c2..c7a2f93 100644 --- a/js/routes/AppHomeRoute.js +++ b/js/routes/AppHomeRoute.js @@ -4,7 +4,7 @@ export default class extends Relay.Route { static queries = { tweet: () => Relay.QL` query TwitterAPI{ - search(q: "graphQl") + search } `, };