Cursor Pagination with Apollo/GraphQL keeps giving me error - pagination

I'm trying to implement cursor pagination and followed the examples in the doc but I keep getting an error saying 'Cannot query field "cursor" on type "Query"'.
I'm aware that the "cursor" field doesn't actually exist on the Accounts schema...but from what I'm reading from the docs.. you have to include it somewhere in the gql`` query. Furthermore, not sure if I'm missing anything but I'm a bit confused of how to structure my query to allow cursor pagination.
Original Query: (running this gives me no error)
const AccountsQuery = gql`
query {
accounts {
accountName
accountId
}
}
`;
New Query: (this gives "cannot find cursor field on accounts" error)
const AccountsQuery = gql`
query Accounts($cursor: String){
accounts(cursor: $cursor) {
cursor
accountName
accountId
}
}
`;
GraphQL wrapper:
export default graphql(AccountsQuery, {
props: ({ data: { loading, cursor, accounts, fetchmore } }) => ({
loading,
accounts,
loadMoreEntries() {
return fetchmore({
query: AccountsQuery,
variables: {
cursor: cursor,
},
updateQuery: (previousResult, { fetchMoreResult }) => {
const previousEntry = previousResult.entry;
const newAccounts = fetchMoreResult.accounts;
return {
cursor: fetchMoreResult.cursor,
entry: {
accounts: [...newAccounts, ...previousEntry]
},
};
},
})
}
})
})(QuickViewContainer);
Any help would be appreciated to getting cursor pagination working!

Sounds like the cursor field isn't getting implemented on the server. Your Account type needs to have that field like so:
Account {
cursor
accountName
accountId
}
For a convention on how to do cursor pagination, you should follow the standard Relay spec. You can read more about how it's implemented here in a Relay-compliant GraphQL API.
This would make your query look like this:
query {
viewer {
allAccounts {
edges {
cursor
node {
accountName
accountId
}
}
}
}
}
Each edge account has a cursor that corresponds to a node and will be auto-populated with globally-unique opaque cursor IDs from the server.
Hope this helps!

Related

Get soft deleted records using Nestjs and TypeOrmQueryService

I am trying to get soft deleted records (deletedAt column) when using query from TypeOrmQueryService but looks like there is no way to do that. I know that I can use withDeleted if I use find or findOne but I cannot switch to these methods or use a query builder since it would require a lot of changed in the front-end.
#QueryService(Patient)
export class PatientQueryService extends TypeOrmQueryService<Patient> {
constructor(#InjectRepository(Patient) repo: Repository<Patient>) {
super(repo);
}
async getOnePatient(currentUser: User, patientId: number) {
const result = await super.query({
paging: { limit: 1 },
filter: { id: { eq: 1 } },
});
}
}

How to use elasticsearch query with searchUsers function in Kuzzle?

i set a property called type in some users with the kuzzle console, now i want to search for user who have the type set to user so i use a query for searching user.
Here is my code:
const resultUsers = await kuzzle.security.searchUsers({
query: {
term: {
type: "user"
}
}
})
console.log(resultUsers)
I also tried with this query too:
query: {
term: {
"content.type": "user"
}
}
and this one:
query: {
term: {
"_source.type": "user"
}
}
But the function always return 0 users. Can someone explain me why please ?
You can only search for properties that have been properly indexed.
You can check the mappings of the internal users collection with the security:getUserMapping API action or with the Admin Console.
Then you need to modify the mappings to include your new property:
{
"properties": {
"type": { "keyword" }
}
}
For this, you can use the security:updateUserMapping API action or again, the Admin Console.

How to implement a selector in easy: search for Meteor, using React instead of Blaze

I'm trying to follow the documentation and examples to add a server-side selector to a search function in my Meteor app, implemented using the Easy Search plugin. The end goal is to ensure that only documents the user has permission to see are returned by searching.
I can see a selector working in the Leaderboard example, but I can't get it to work in my code.
Versions:
Meteor 1.7.0.1
easy:search#2.2.1
easysearch:components#2.2.2
easysearch:core#2.2.2
I modified the Meteor 'todos' example app to demonstrate the problem, and my demo code is in a repo.
NOTE! to demonstrate the problem, you need to create an account in the demo app, then create a list and make it private. This add the 'userId' field to the list.
Then you can search for the name of the list, by typing in the search box near the top of the main section; search results are written to the browser console.
The first problem is that if I copy the code from the example in the documentation, I see a server error 'searchObject is not defined:
copied from docs, causes an error: imports/api/lists/lists.js
export const MyIndex = new Index({
'collection': Lists,
'fields': ['name'],
engine: new MongoDBEngine({
selector(searchDefinition, options, aggregation) {
// retrieve the default selector
const selector = this.defaultConfiguration()
.selector(searchObject, options, aggregation)
// options.search.userId contains the userId of the logged in user
selector.userId = options.search.userId
return selector
},
}),
});
It seems there is an error in the docs.
Working instead from the leaderboard example, the code below runs but intermittently returns no results. For example if I have a list called "My list", and I type the search term 's', sometimes the list is returned from the search and sometimes it is not. If I use the MiniMongo engine it all works perfectly.
index selector {"$or":[{"name":{"$regex":".*my.*","$options":"i"}}],"userId":"Wtrr5FRHhkKuAcrLZ"}
client and server: imports/api/lists/lists.js
export const MyIndex = new Index({
'collection': Lists,
'fields': ['name'],
'engine': new MongoDBEngine({
selector: function (searchObject, options, aggregation) {
let selector = this.defaultConfiguration().selector(searchObject, options, aggregation);
selector.userId = options.search.userId;
console.log('index selector', JSON.stringify(selector));
return selector;
}
}),
permission: () => {
return true;
},
});
client: imports/ui/components/lists-show.js
Template.Lists_show.events({
'keyup #search'(event) {
console.log('search for ', event.target.value);
const cursor = MyIndex.search(event.target.value);
console.log('count',cursor.count());
console.log('results', cursor.fetch());
},
});
client: imports/ui/components/lists-show.html
<input id="search" type="text" placeholder="search..." />
Edit: I think the problem is that while the Minimongo engine runs on the client, the MongoDBEngine runs on the server and there are timing issues with the results. The docs show using Tracker.autorun, but that's not a natural fit with my React / Redux app. I'll post an answer if I manage to figure something out - I can't be the only person trying to do something like this.
I got it working in my React / Redux / Meteor app. Things to note:
The cursor MyIndex.search(searchTerm) is a reactive data source - you can't just use it as a return value. When searching on the client with MiniMongo this isn't an issue, but it's important when you use MongoDBEngine to search on the server, because it's asynchronous. In React you can wrap the cursor in withTracker to pass data to the component reactively. In Blaze you would use autorun.tracker. This is shown in the docs but not explained, and it took me a while to understand what was happening.
The docs have an error in the selector example, easily corrected but it's confusing if you have other problems in your code.
With MongoDBEngine, 'permission' must be specified - it does not default to 'true'. Without it, you will see no results.
Writing out the default selector object to the console let me see how it's constructed, and then create a new selector that returns MyDocs that are either public or created by the user.
My code is below. In case it helps anybody else, I've shown how to search on tags also, which are objects with a name property stored in a collection Tags. Each MyDoc has a 'tags' property which is an array of tag ids. The selector first searches the Tags collection to find tags whose name matches the search term, then selects docs in MyDocs with the ids of those tags in their doc.tags array.
There may be a better way to find the search term, or to structure the Tags search, but this is what I could get working.
On server and client:
import { Index, MongoDBEngine } from 'meteor/easy:search';
export const MyDocs = new Mongo.Collection('mydocs');
export const Tags = new Mongo.Collection('tags');
export const MyIndex = new Index({
'collection': MyDocs,
'fields': ['name'],
'engine': new MongoDBEngine({
'selector': function (searchObject, options, aggregation) {
const selector = this.defaultConfiguration().selector(searchObject, options, aggregation);
console.log('default selector', selector); // this searches on name only
// find docs by tag as well as by name
const searchTerm = searchObject.name;
const matchingTags = Tags.find({ 'name': { '$regex': searchTerm } }).fetch();
const matchingTagIds = matchingTags.map((tag) => tag._id);
selector.$or.push({ 'tags': { '$in': matchingTagIds } });
const newSelector = {
'$and': [
{
'$or': [
{ 'isPublic': { '$eq': true } },
{ 'createdBy': options.search.userId },
],
},
{
'$or': selector.$or,
},
],
};
return newSelector;
},
'fields': (searchObject, options) => ({
'_id': 1,
'createdBy': 1,
'name': 1,
}),
'sort': () => ({ 'name': 1 }),
}),
'permission': () => true,
});
React component in client only code:
import React from 'react';
import { connect } from 'react-redux';
import { withTracker } from 'meteor/react-meteor-data';
import PropTypes from 'prop-types';
import store from '../modules/store';
import {
getSearchTerm,
searchStart,
} from '../modules/search'; // contains Redux actions and partial store for search
import { MyIndex } from '../../modules/collection';
function Search(props) {
// functional React component that contains the search box
...
const onChange = (value) => {
clearTimeout(global.searchTimeout);
if (value.length >= 2) {
// user has entered a search term
// of at least 2 characters
// wait until they stop typing
global.searchTimeout = setTimeout(() => {
dispatch(searchStart(value)); // Redux action which sets the searchTerm in Redux state
}, 500);
}
};
...
// the component returns html which calls onChange when the user types in the search input
// and a list which displays the search results, accessed in props.searchResults
}
const Tracker = withTracker(({ dispatch }) => {
// searchTerm is saved in Redux state.
const state = store.getState();
const searchTerm = getSearchTerm(state); // Redux function to get searchTerm out of Redux state
let results = [];
if (searchTerm) {
const cursor = MyIndex.search(searchTerm); // search is a reactive data source
results = cursor.fetch();
console.log('*** cursor count', cursor.count());
return {
'searchResults': results,
};
})(Search);
export default connect()(Tracker);

Understanding GraphQl query

I was trying to learn and comprehend GraphQl.
In order to do so, I went to apollo-graphQL blog and started with getting started launch
From their blogs, in our schema.js file, consider we have something like
onst { gql } = require('apollo-server');
const typeDefs = gql`
type Query {
launches: [Launch]!
launch(id: ID!): Launch
me: User
}
type Launch {
id: ID!
site: String
mission: Mission
rocket: Rocket
isBooked: Boolean!
}
module.exports = typeDefs;
Now in tool from where we can query (like graphiqL), there in their example they have done something like this in query
{
launch(id: 1) {
site
}
}
I am unsure- here about the place our site in the above graphiqL object is coming and how can we write it (since in our query, launch is expecting a return type if Launch and only want id launch(id: ID!): Launch)
Why is this query invalid
{
launch(id: 1)
}
You need to specify fields for complex types. For your example ("and only want id").
{
launch(id: 1) {
id
}
}
What goes in (id: 1) is an input for the query (like an argument for a function). But you still have to specify what you want back.
UPD. Just to be clear the same rule applies to nested complex types. For example, if you want to get launch rocket as well you can't simply do
{
launch(id: 1) {
id
rocket # does not work
}
}
You need to specify which rocket fields you want
{
launch(id: 1) {
id
rocket {
id
}
}
}

How to use Github GraphQL search to return user profiles with similar name( including login and display name)?

I have this simple search query
query test($name: String!) {
search(query: $name, type: USER, last: 100) {
edges {
textMatches {
fragment
property
highlights {
text
}
}
}
userCount
}
}
and say, for example, I would like to have the login information for all users from the search result. How would I do that? The results contain login or display names that matches the search text. Is there a way to find the login for those who only appear in the search because of their display name?
You were almost there! In "edges", you're dealing with an array of SearchResultItemEdge which contains a "node" property at the same level as "textMatches".
Since the node is a SearchResultItem, and can be one of User, Issue, PullRequest, etc, you have to specifically spread your node as a "User" in order to be able to access the login.
Give this query a try in the Explorer:
query test($name: String!) {
search(query: $name, type: USER, last: 100) {
edges {
node {
__typename
...on User {
login
}
}
textMatches {
fragment
property
highlights {
text
}
}
}
userCount
}
}

Resources