Customize the mock data defined against the types in graphql-tools - graphql-js

I am using graphql-tools to mock for my UI (create-react-app) functional tests. I have a question around the MockList
Currently, I am mocking by type and one of the properties is an array but I still need to be able to customise the elements in the list
e.g. I have a LineItem type which has a mock defined as
const LineItem = () =>
({
name: 'Item name'
} as LineItemType)
and the cart type has a list of LineItem
const Cart = () =>
({
id: 'cart-id',
lineItems: [...new Array(2)],
} as ActiveCartType)
Is there a way for the item name to be different for the 2 items in the cart?
I tried to map over the Array like so
const Cart = () =>
({
id: 'cart-id',
lineItems: [...new Array(2)].map(i => ({...i, id: '123', name: 'new item name'})),
} as ActiveCartType)
But the name doesn't change in the mock result. It is still set to Item name. Only id changes to 123. Am I missing something here?

From the documentation for graphql-tools/mocking
we are using casual, a fake data generator for JavaScript, so that we can get a different result every time the field is called. You might want to use a collection of fake objects, or a generator that always uses a consistent seed, if you are planning to use the data for testing.
From your code, you could assign name to a generator function that adds unique names to that field.
const LineItem = () =>
({
name: functionGoesHere()
} as LineItemType)
Two libraries that produce generated data for mocking are,
Casual library
Faker.js Library

Related

Handling of Firestore IDs in models and entities

I'm learning domain driven design for Flutter apps. I understand that the model is used between the infrastructure layer and the use-case, and the entity is used between in the use case and the UI.
Let's say that my app is dealing with books and I'm storing my books in Cloud Firestore. I have defined a very simple BookEntity with an id and a title.
#freezed
class BookEntity with _$BookEntity {
const factory BookEntity({
required String firestoreId, // This is the ID of the document in firestore
required String title,
}) = _BookEntity;
}
I believe that the ID of the document should be in the entity, because if I need to modify this book in the Firestore I will need to know the reference of the document, right?
As you know, in firestore the id is not part of the data themselves. When I read my database, I would be using the code below.
// code not tested
FirebaseFirestore
.instance
.collection('books')
.doc('uXSin0z3gqPHwhVLCP98')
.get()
.then((DocumentSnapshot<Map<String, dynamic>> snapshot) {
snapshot.id; // -> 'uXSin0z3gqPHwhVLCP98'
snapshot.data(); // -> {title: 'To Kill a Mockingbird', price: 11.69, year: 1960}
});
Somewhere, I will need to put the id together with the data. I think the right place to do it is in the model, which is created in the repository. I think my model would probably look very similar to the Entity:
#freezed
class BookModel with _$BookModel {
const factory BookModel({
required String firestoreId,
required String title,
}) = _BookModel;
}
And, when fetching data from Firestore I would create a model with:
BookModel(
firestoreId: snapshot.id,
title: snapshot.data()?['title'],
);
This can then be converted to a BookEntity which will be consumed by the UI.
The problem is that when I am reversing the flow, when I am creating a new book, the ID of the firestore document is not known in the presentation and domain layers. Therefore my BookEntity and BookModel must be updated so that the id is optional. The entity and the model now look like this
#freezed
class BookEntity with _$BookEntity {
const factory BookEntity({
String? firestoreId,
required String title,
}) = _BookEntity;
}
#freezed
class BookModel with _$BookModel {
const factory BookModel({
String? firestoreId,
required String title,
}) = _BookModel;
}
The problem is that now, every time I need to access the firestoreId field of my BookEntity, whose data originate from Firestore, I need to test whether the firestoreId field is null or not. But it cannot be null because the data come from Firestore, so there is always an ID. So I will either write a lot of null-checks, or use the ! (which I don't like).
In short, the "upstream" and "downstream" flows have different requirements for the firestoreId field. The Firebase -> UI flow needs a String, and the UI -> Firebase flow needs a String?.
So the question is what is the best and cleanest way to handle this?
firestoreId should be nullable, because it make sense for BookEntity to have firestoreId sometime and not have firestoreId sometime.
You probably don't need to use firestoreId in the UI, and It's only needed when writing to the Firestore. So you can have a writeToFirestore method and only use a single null check there.
You can also generate a new random id locally whenever a new BookEntity is created. Using your own document id when creating a document in Firestore
One more solution is to use late final String firestoreId, but it's skipping null check making debuging harder and doesn't work with Freezed.

How can I create a relation in Strapi if I don't know the id of the field?

I am creating a collection of judges and courthouses. Every judge will be assigned to one courthouse. I have set up my relation to be that courthouse has many judges
I am attempting to do this programmatically when the app loads. I have a function that is able to populate all the fields in judge except the relation to courthouse. My function uses the Strapi API like this
const judge = await strapi.query('judge').create({
name: data[i].name,
},
{
courthouse: data[i].courthouse_name // here is where I think the relation is created
}
)
I am passing in a string that has the name of courthouse, because I don't know the ID of the courthouse in the Courthouse collection.
My question is it possible to create a relation to another collection by anything other than an ID? How can I create a relation to a courthouse by its name?
I couldn't find a way around building a relationship between two models without the ID, so I created a custom solution using the Strapi lifecycle hooks
Essentially what I did I utilized the beforeCreate lifecycle hook to query and find the courthouse that matches the name like this:
// judges.js
async beforeCreate(result, data) {
const courthouse = await strapi.query('courthouse').find(
{courthouse_name:data.courthouse}
); // returns the courthouse that matches the name
result['courthouse'] = courthouse[0].id; // populates the relational field with the
// ID of the courthouse
}
The response object contained the courthouse's ID and I manipulated the data that is being sent to the create command like this:
const judge = await strapi.query('judge').create({
name: data[i].name,
courthouse: data[i].courthouse_name
})
The result is an object that looks like this:
{name: 'Garfield Lucas, courthouse: 7463987}

Resolving auto-generated typescript-mongodb types for GraphQL output

I'm using the typescript-mongodb plugin to graphql-codegen to generate Typescript types for pulling data from MongoDB and outputting it via GraphQL on Node.
My input GraphQL schema looks like this
type User #entity{
id: ID #id,
firstName: String #column #map(path: "first_name"),
...
The generated output Typescript types look correct
export type User = {
__typename?: 'User',
id?: Maybe<Scalars['ID']>,
firstName?: Maybe<Scalars['String']>,
...
And the corresponding DB object
export type UserDbObject = {
_id?: Maybe<String>,
first_name: Maybe<string>,
...
The problem is when actually sending back the mongo document as a UserDbObject I do not get the fields mapped in the output. I could write a custom resolver that re-maps the fields back to the User type, but that would mean I'm mapping the fields in two different places.
i.e. I do not get mapped fields from a resolver like this
userById: async(_root: any, args: QueryUserByIdArgs, _context: any) : Promise<UserDbObject> => {
const result = await connectDb().then((db) => {
return db.collection<UserDbObject>('users').findOne({'_id': args.id}).then((doc) => {
return doc;
});
})
...
return result as UserDbObject;
}
};
Is there a way to use the typescript-mongodb plugin to only have to map these fields in the schema, then use the auto-generated code to resolve them?
You can use mappers feature of codegen to map between your GraphQL types and your models types.
See:
https://graphql-code-generator.com/docs/plugins/typescript-resolvers#mappers---overwrite-parents-and-resolved-values
https://graphql-code-generator.com/docs/plugins/typescript-resolvers#mappers-object
Since all codegen plugins are independent and not linked together, you should do it manually, something like:
config:
mappers:
User: UserDbObject
This will make typescript-resolvers plugin to use UserDbObject at any time (as parent value, or as return value).
If you wish to automate this, you can either use the codegen programmatically (https://graphql-code-generator.com/docs/getting-started/programmatic-usage), or you can also create a .js file instead of .yaml file that will create the config section according to your needs.

Is there a Deconstruct Mongo Response to DTO short cut?

If I have a table in a mongoDB with five properties and I only want to return four of them and none of the mongo added info such as v1 I can map the reposne to a dto like so,
const product = await this.productModel.findById(productId).exec()
return { id: product.id, title: product.title }
Is there a deconstruct shortcut for the return, to extract every field from an interface (Product) from the product response, to save typing each property out ? If for example im retunring 127 properties from a table of entires with 140.
interface Product {
id: string
title: string
...
}
Unfortunately no, typescript interfaces do not really exist when your program compiles
Interface is a structure that defines the contract in your application. It defines the syntax for classes to follow. Classes that are derived from an interface must follow the structure provided by their interface.
The TypeScript compiler does not convert interface to JavaScript. It
uses interface for type checking. This is also known as "duck typing"
or "structural subtyping".
So, you can't really read interface fields and then write some logic (you can maybe achieve this through reflection but it's a bad practice)
An alternative is to explicitly define what fields are to include/or exclude from your object
Suppose that I have an object with this interface:
interface Foo {
field1: string;
field2: string;
field3: string;
.....
field140: string;
}
What you can do here is to define what properties you want to exclude (you take the exclude approach here since you are returning 127 fields of 140)
// This isn't an implementation with mongoose (if you are using it),
// it's just to give you the idea
const FIELDS_TO_EXCLUDE = ["field128", "field129", "field130", ..., "field140"];
productModel.toDTO(){
const documentData = this;
FIELDS_TO_EXCLUDE.forEach(x => delete documentData[x]);
return documentData;
}
In this way, when you will execute the toDTO function your manipulate itself excluding (or including) the fields you want

Support for resource nesting

I am wondering, is it possible to configure DataProvider/Resource/List to support REST urls like api/users/1/roles?
For RESTful API it is very common use case to get childs of certain parent entity, but I cant figure it how to setup React Admin and achieve this. I am using custom DataProvider build on OData spec backend.
I understand that I can get roles of certain user by filtered request on api/roles?filter={userId: 1} or something like that, but my issue is that my users and roles are in many-to-many relation so relation references are stored in pivot table. In other words, I dont have reference on user in roles table so I cant filter them.
Am I overseeing something or is there some approach which I simply dont see?
EDIT:
REST API is built in OData spec and it supports many-to-many relations with classic pivot (or intermediate) table. This table is not exposed in API, but is utilized in urls like the one above. So I cant directly access it as a resource.
Schema for User - Role relations looks pretty standard too.
|----------| |-----------| |--------|
| USER | | User_Role | | Role |
|----------| |-----------| |--------|
| Id |-\ | Id | /-| Id |
| Login | \-| UserId | / | Name |
| Password | | RoleId |-/ | Code |
|----------| |-----------| |--------|
TL;DR: By default, React Admin doesn't support nested resource, you have to write a custom data provider.
This question was answered on a past issue: maremelab/react-admin#261
Detailed Answer
The default data provider in React Admin is ra-data-simple-rest.
As explained on its documentation, this library doesn't support nested resources since it only use the resource name and the resource ID to build a resource URL:
In order to support nested resources, you have to write your own data provider.
Nested resources support is a recurrent feature request but, at the time, the core team don't want to handle this load of work.
I strongly suggest to gather your forces and write an external data provider and publish it like ra-data-odata provider. It would be a great addition and we will honored to help you with that external package.
Your question was already answer here, but I would like to tell you about my workaround in order for React-Admin work with many-to-many relations.
As said in the mentioned answer you have to extend the DataProvider in order for it to fetch resources of a many-to-many relation. However you need to use the new REST verb, lets suppose GET_MANY_MANY_REFERENCE somewhere on your application. Since different REST services/API can have different routes formats to fetch related resources I didn't bother trying to build a new DataProvider, I know is not a great solution, but for short deadlines is considerable simple.
My solution was taking inspiration on <ReferenceManyField> and build a new component <ReferenceManyManyField> for many-to-many relations. This component fetches related records on componentDidMount using fetch API. On response uses the response data to build to objects one data being an object with keys being record ids, and values the respective record object, and an ids array with the ids of records. This is passes to children along with other state variables like page, sort, perPage, total, to handle pagination and ordering of data. Be aware that changing the order of the data in a Datagrid means a new request will be made to the API. This component is divided in a controller and a view, like <ReferencemanyField>, where controller fetches data, manages it and passes it to children and view that receives controller data and passes it to children render its content. That made me possible to render many-to-many relations data on a Datagrid, even if with some limitation, is a component to aggregated to my project and only work with my current API if something changes I would have to change the field to, but as for now it works and can be reused along my app.
Implementation details go as follow:
//ReferenceManyManyField
export const ReferenceManyManyField = ({children, ...prop}) => {
if(React.Children.count(children) !== 1) {
throw new Error( '<ReferenceManyField> only accepts a single child (like <Datagrid>)' )
}
return <ReferenceManyManyFieldController {...props}>
{controllerProps => (<ReferenceManyManyFieldView
{...props}
{...{children, ...controllerProps}} /> )}
</ReferenceManyManyFieldController>
//ReferenceManyManyFieldController
class ReferenceManyManyFieldController extends Component {
constructor(props){
super(props)
//State to manage sorting and pagination, <ReferecemanyField> uses some props from react-redux
//I discarded react-redux for simplicity/control however in the final solution react-redux might be incorporated
this.state = {
sort: props.sort,
page: 1,
perPage: props.perPage,
total: 0
}
}
componentWillMount() {
this.fetchRelated()
}
//This could be a call to your custom dataProvider with a new REST verb
fetchRelated({ record, resource, reference, showNotification, fetchStart, fetchEnd } = this.props){
//fetchStart and fetchEnd are methods that signal an operation is being made and make active/deactivate loading indicator, dataProvider or sagas should do this
fetchStart()
dataProvider(GET_LIST,`${resource}/${record.id}/${reference}`,{
sort: this.state.sort,
pagination: {
page: this.state.page,
perPage: this.state.perPage
}
})
.then(response => {
const ids = []
const data = response.data.reduce((acc, record) => {
ids.push(record.id)
return {...acc, [record.id]: record}
}, {})
this.setState({data, ids, total:response.total})
})
.catch(e => {
console.error(e)
showNotification('ra.notification.http_error')
})
.finally(fetchEnd)
}
//Set methods are here to manage pagination and ordering,
//again <ReferenceManyField> uses react-redux to manage this
setSort = field => {
const order =
this.state.sort.field === field &&
this.state.sort.order === 'ASC'
? 'DESC'
: 'ASC';
this.setState({ sort: { field, order } }, this.fetchRelated);
};
setPage = page => this.setState({ page }, this.fetchRelated);
setPerPage = perPage => this.setState({ perPage }, this.fetchRelated);
render(){
const { resource, reference, children, basePath } = this.props
const { page, perPage, total } = this.state;
//Changed basePath to be reference name so in children can nest other resources, not sure why the use of replace, maybe to maintain plurals, don't remember
const referenceBasePath = basePath.replace(resource, reference);
return children({
currentSort: this.state.sort,
data: this.state.data,
ids: this.state.ids,
isLoading: typeof this.state.ids === 'undefined',
page,
perPage,
referenceBasePath,
setPage: this.setPage,
setPerPage: this.setPerPage,
setSort: this.setSort,
total
})
}
}
ReferenceManyManyFieldController.defaultProps = {
perPage: 25,
sort: {field: 'id', order: 'DESC'}
}
//ReferenceManyManyFieldView
export const ReferenceManyManyFieldView = ({
children,
classes = {},
className,
currentSort,
data,
ids,
isLoading,
page,
pagination,
perPage,
reference,
referenceBasePath,
setPerPage,
setPage,
setSort,
total
}) => (
isLoading ?
<LinearProgress className={classes.progress} />
:
<Fragment>
{React.cloneElement(children, {
className,
resource: reference,
ids,
data,
basePath: referenceBasePath,
currentSort,
setSort,
total
})}
{pagination && React.cloneElement(pagination, {
page,
perPage,
setPage,
setPerPage,
total
})}
</Fragment>
);
//Assuming the question example, the presentation of many-to-many relationship would be something like
const UserShow = ({...props}) => (
<Show {...props}>
<TabbedShowLayout>
<Tab label='User Roles'>
<ReferenceManyManyField source='users' reference='roles' addLabel={false} pagination={<Pagination/>}>
<Datagrid>
<TextField source='name'/>
<TextField source='code'/>
</Datagrid>
</ReferenceManyManyField>
</Tab>
</TabbedShowLayout>
</Show>
)
//Used <TabbedShowLayout> because is what I use in my project, not sure if works under <Show> or <SimpleShowLayout>, but I think it work since I use it in other contexts
I think the implementation can be improved and be more compatible with React-Admin. In other reference fields data fetch is stored on react-redux state, in this implementation it's not. The relation is not saved anywhere besides the component making application not work on offline since can't fetch data, not even ordering is possible.
Had a very similar question. My solution was more of a hack but a little simpler to implement if all you want is to enable a ReferenceManyField. Only the dataProvider needs to be modified:
I'm repeating my solution here modified for the current question:
Using the stock ReferenceManyField:
<Show {...props}>
<TabbedShowLayout>
<Tab label="Roles">
<ReferenceManyField reference="roles" target="_nested_users_id" pagination={<Pagination/>} >
<Datagrid>
<TextField source="role" />
</Datagrid>
</ReferenceManyField>
</Tab>
</TabbedShowLayout>
</Show>
I then modified my dataProvider, which is a fork of ra-jsonapi-client.
I changed index.js under the case GET_MANY_REFERENCE from this:
// Add the reference id to the filter params.
query[`filter[${params.target}]`] = params.id;
url = `${apiUrl}/${resource}?${stringify(query)}`;
to this:
// Add the reference id to the filter params.
let refResource;
const match = /_nested_(.*)_id/g.exec(params.target);
if (match != null) {
refResource = `${match[1]}/${params.id}/${resource}`;
} else {
query[`filter[${params.target}]`] = params.id;
refResource = resource;
}
url = `${apiUrl}/${refResource}?${stringify(query)}`;
So basically I just remap the parameters to the url for the special case where the target matches a hard coded regex.
ReferenceManyField would normally have caused the dataProvider to call api/roles?filter[_nested_users_id]=1 and this modification makes the dataProvider call api/users/1/roles instead. It is transparent to react-admin.
Not elegant but it works and doesn't seem to break anything on the front end.