Retrieve docs from MongoDB using Array - mongodb

I have a route:
router.get('/:bar', (req, res) => {
Style.distinct('id', {'foo': req.params.bar })
.then(ids =>
// [00001, 00002, 00003, 00004, ... 99999]]
// get Style
// get Config
// etc..
});
});
In distinct then function, I would like to replace id 00001 with it's object & configuration (to do it, I have to query the Schema):
Style.find({'id', id})
Config.find({'id', id})
I'm stuck in callback hell.
I am trying to achieve the following output:
{
00001 : {
style: {}
config: {}
},
00002 : {
style: {}
config: {}
},
00003 : {
style: {}
config: {}
}
}
However, I am unsure how to return the response of two concurrent API calls in an object, and return an array of objects when complete.

Related

Mochawesome with Cypress - how to get aggregated charts at higher level?

I've just started using mochawesome with Cypress (9.7). Our test structure is basically a number of spec files, each following something like the following format:
describe('(A): description of this spec', () => {
describe ('(B): description of test abc', () => {
before(() => {
// do specific set up bits for this test
})
it('(C): runs test abc', () => {
// do actual test stuff
})
})
})
Where within each spec file there would be a single 'A' describe block, but there can be many 'B' level blocks (each with a single 'C') - done this way because the before block for each 'C' is always different - I couldn't use a beforeEach.
When I run my various spec files, each structured similarly to the above, the mochaewsome output is mostly correct - I get a collapsible block for each spec file at level 'A', each with multiple collapsible blocks at level B, each with test info as expected at level C.
But... The circular charts are only displayed at level B. What I was hoping, was that it might be possible to have aggregated charts at level A, and a further aggregated chart for all the level A blocks.
Not sure I've explained this brilliantly(!), but hopefully someone understands, and can offer a suggestion?!
In cypress-mochawesome-reporter there's an alternative setup using on('after:run') which can perform the aggregation.
In Cypress v9.7.0
// cypress/plugins/index.js
const { beforeRunHook, afterRunHook } = require('cypress-mochawesome-reporter/lib');
const { aggregateResults } = require('./aggregate-mochawesome-report-chart');
module.exports = (on, config) => {
on('before:run', async (details) => {
await beforeRunHook(details);
});
on('after:run', async () => {
aggregateResults(config)
await afterRunHook();
});
};
In Cypress v10+
// cypress.config.js
const { defineConfig } = require('cypress');
const { beforeRunHook, afterRunHook } = require('cypress-mochawesome-reporter/lib');
const { aggregateResults } = require('./aggregate-mochawesome-report-chart');
module.exports = defineConfig({
reporter: 'cypress-mochawesome-reporter',
video: false,
retries: 1,
reporterOptions: {
reportDir: 'test-report',
charts: true,
reportPageTitle: 'custom-title',
embeddedScreenshots: true,
inlineAssets: false,
saveAllAttempts: false,
saveJson: true
},
e2e: {
setupNodeEvents(on, config) {
on('before:run', async (details) => {
await beforeRunHook(details);
});
on('after:run', async () => {
aggregateResults(config)
await afterRunHook();
});
},
},
});
The module to do the aggregation is
// aggregate-mochawesome-reporter-chart.js
const path = require('path');
const fs = require('fs-extra')
function aggregateResults(config) {
const jsonPath = path.join(config.reporterOptions.reportDir , '/.jsons', '\mochawesome.json');
const report = fs.readJsonSync(jsonPath)
const topSuite = report.results[0].suites[0]
aggregate(topSuite)
fs.writeJsonSync(jsonPath, report)
}
function aggregate(suite, level = 0) {
const childSuites = suite.suites.map(child => aggregate(child, ++level))
suite.passes = suite.passes.concat(childSuites.map(child => child.passes)).flat()
suite.failures = suite.failures.concat(childSuites.map(child => child.failures)).flat()
suite.pending = suite.pending.concat(childSuites.map(child => child.pending)).flat()
suite.skipped = suite.skipped.concat(childSuites.map(child => child.skipped)).flat()
if (!suite.tests.length && suite.suites[0].tests.length) {
// trigger chart when to describe has no tests
suite.tests = [
{
"title": "Aggregate of tests",
"duration": 20,
"pass": true,
"context": null,
"err": {},
"uuid": "0",
"parentUUID": suite.uuid,
},
]
}
return suite
}
module.exports = {
aggregateResults
}
The function aggregate() recursively loops down through child suites and adds the test results to the parent.
json files
Note the json file is different at the point where afterRunHook runs and at the end of the test run.
If you have the option saveJson: true set, you will get a final json file in the report directory called index.json.
At the afterRunHook stage the file is mochawesome.json.
Before aggregation
After aggregation

Redux Toolkit Query: Reduce state from "mutation" response

Let's say I have an RESTish API to manage "posts".
GET /posts returns all posts
PATCH /posts:id updates a post and responds with new record data
I can implement this using RTK query via something like this:
const TAG_TYPE = 'POST';
// Define a service using a base URL and expected endpoints
export const postsApi = createApi({
reducerPath: 'postsApi',
tagTypes: [TAG_TYPE],
baseQuery,
endpoints: (builder) => ({
getPosts: builder.query<Form[], string>({
query: () => `/posts`,
providesTags: (result) =>
[
{ type: TAG_TYPE, id: 'LIST' },
],
}),
updatePost: builder.mutation<any, { formId: string; formData: any }>({
// note: an optional `queryFn` may be used in place of `query`
query: (data) => ({
url: `/post/${data.formId}`,
method: 'PATCH',
body: data.formData,
}),
// this causes a full re-query.
// Would be more efficient to update state based on resp.body
invalidatesTags: [{ type: TAG_TYPE, id: 'LIST' }],
}),
}),
});
When updatePost runs, it invalidates the LIST tag which causes getPosts to run again.
However, since the PATCH operation responds with the new data itself, I would like to avoid making an additional server request and instead just update my reducer state for that specific record with the content of response.body.
Seems like a common use case, but I'm struggling to find any documentation on doing something like this.
You can apply the mechanism described in optimistic updates, just a little bit later:
import { createApi, fetchBaseQuery } from '#reduxjs/toolkit/query'
import { Post } from './types'
const api = createApi({
// ...
endpoints: (build) => ({
// ...
updatePost: build.mutation<void, Pick<Post, 'id'> & Partial<Post>>({
query: ({ id, ...patch }) => ({
// ...
}),
async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
const { data } = await queryFulfilled
dispatch(
api.util.updateQueryData('getPost', id, (draft) => {
Object.assign(draft, data)
})
)
},
}),
}),
})

MongoDB query with 300k documents takes more than 30 seconds

Ok, as said in title, I have "performance issue" where I need to get all documents from a collection but it takes too long. Players collection contains around 300k documents with small size and query in service goes like this:
async getAllPlayers() {
const players = await this.playersCollection.find({}, {projection: { playerId: 1, name: 1, surname: 1, shirtNumber: 1, position: 1 }}).toArray();
return players;
}
Overall size is 6.4MB. I'm using Fastify adapter, fastify-compress and mongodb native driver. If I remove projection, it takes almost a minute.
Any idea how to improve this?
The best time I get is 8 seconds, where fast-json-stringify give me more than 10 seconds boost over 300k records:
'use strict'
// run fresh mongo
// docker run --name temp --rm -p 27017:27017 mongo
const fastify = require('fastify')({ logger: true })
const fjs = require('fast-json-stringify')
const toString = fjs({
type: 'object',
properties: {
playerId: { type: 'integer' },
name: { type: 'string' },
surname: { type: 'string' },
shirtNumber: { type: 'integer' },
}
})
fastify.register(require('fastify-mongodb'), {
forceClose: true,
url: 'mongodb://localhost/mydb'
})
fastify.get('/', (request, reply) => {
const dataStream = fastify.mongo.db.collection('foo')
.find({}, {
limit: 300000,
projection: { playerId: 1, name: 1, surname: 1, shirtNumber: 1, position: 1 }
})
.stream({
transform(doc) {
return toString(doc) + '\n'
}
})
reply.type('application/jsonl')
reply.send(dataStream)
})
fastify.get('/insert', async (request, reply) => {
const collection = fastify.mongo.db.collection('foo')
const batch = collection.initializeOrderedBulkOp();
for (let i = 0; i < 300000; i++) {
const player = {
playerId: i,
name: `Name ${i}`,
surname: `surname ${i}`,
shirtNumber: i
}
batch.insert(player);
}
const { result } = await batch.execute()
return result
})
fastify.listen(8080)
In any case, you should consider to:
paginate your output
or pushing the data into a bucket (like S3) and return to the client a URL to download the file directly, this will speed up a lot the process and will save your node.js process from this data streaming
Note that the compression in node.js is a heavy process, so it slows it down a lot the response. An nginx proxy adds it by default without the need to implement it in your business logic server.

graphql-tools, how can I use mockServer to mock a "Mutation"?

I try to use mockServer of graphql-tools to mock an Mutation.
here is my unit test:
it('should update user name correctly', () => {
mockserver
.query(
`{
Mutation {
updateUserName(id: 1, name: "du") {
id
name
}
}
}`
)
.then(res => {
console.log(res);
expect(1).to.be.equal(1);
});
});
But, got an error:
mock server test suites
✓ should get users correctly
✓ should get user by id correctly
✓ should update user name correctly
{ errors:
[ { GraphQLError: Cannot query field "Mutation" on type "Query".
at Object.Field (/Users/ldu020/workspace/apollo-server-express-starter/node_modules/graphql/validation/rules/FieldsOnCorrectType.js:65:31)
at Object.enter (/Users/ldu020/workspace/apollo-server-express-starter/node_modules/graphql/language/visitor.js:324:29)
at Object.enter (/Users/ldu020/workspace/apollo-server-express-starter/node_modules/graphql/language/visitor.js:366:25)
at visit (/Users/ldu020/workspace/apollo-server-express-starter/node_modules/graphql/language/visitor.js:254:26)
at visitUsingRules (/Users/ldu020/workspace/apollo-server-express-starter/node_modules/graphql/validation/validate.js:74:22)
at validate (/Users/ldu020/workspace/apollo-server-express-starter/node_modules/graphql/validation/validate.js:59:10)
at graphqlImpl (/Users/ldu020/workspace/apollo-server-express-starter/node_modules/graphql/graphql.js:106:50)
at /Users/ldu020/workspace/apollo-server-express-starter/node_modules/graphql/graphql.js:66:223
at new Promise (<anonymous>)
at Object.graphql (/Users/ldu020/workspace/apollo-server-express-starter/node_modules/graphql/graphql.js:63:10)
at Object.query (/Users/ldu020/workspace/apollo-server-express-starter/node_modules/graphql-tools/dist/mock.js:19:63)
at Context.it (/Users/ldu020/workspace/apollo-server-express-starter/src/mockServer/index.spec.js:95:8)
at callFn (/Users/ldu020/workspace/apollo-server-express-starter/node_modules/mocha/lib/runnable.js:383:21)
at Test.Runnable.run (/Users/ldu020/workspace/apollo-server-express-starter/node_modules/mocha/lib/runnable.js:375:7)
at Runner.runTest (/Users/ldu020/workspace/apollo-server-express-starter/node_modules/mocha/lib/runner.js:446:10)
at /Users/ldu020/workspace/apollo-server-express-starter/node_modules/mocha/lib/runner.js:564:12
at next (/Users/ldu020/workspace/apollo-server-express-starter/node_modules/mocha/lib/runner.js:360:14)
at /Users/ldu020/workspace/apollo-server-express-starter/node_modules/mocha/lib/runner.js:370:7
at next (/Users/ldu020/workspace/apollo-server-express-starter/node_modules/mocha/lib/runner.js:294:14)
at Immediate.<anonymous> (/Users/ldu020/workspace/apollo-server-express-starter/node_modules/mocha/lib/runner.js:338:5)
at runCallback (timers.js:763:18)
at tryOnImmediate (timers.js:734:5)
at processImmediate (timers.js:716:5)
message: 'Cannot query field "Mutation" on type "Query".',
locations: [Array],
path: undefined } ] }
and, I read graphql-tools interface.d.ts file.
export interface IMockServer {
query: (query: string, vars?: {
[key: string]: any;
}) => Promise<ExecutionResult>;
}
Obviously, there is no mutation function in mockServer.
Does mockServer support Mutation?
https://github.com/apollographql/graphql-tools/issues/279
It's the structure of your query. The query should be structured much like you would in GraphiQL such as:
mutation {
updateUserName(id: 1, name: "du") {
id
name
}
}
Hence, your code should look something like this, with the mutation keyword as the first thing in your query before your opening brace { :
it('should update user name correctly', () => {
mockserver
.query(`mutation {
updateUserName(id: 1, name: "du") {
id
name
}
}`)
.then(res => {
console.log(res);
expect(1).to.be.equal(1);
});
});

How to change http status codes in Strongloop Loopback

I am trying to modify the http status code of create.
POST /api/users
{
"lastname": "wqe",
"firstname": "qwe",
}
Returns 200 instead of 201
I can do something like that for errors:
var err = new Error();
err.statusCode = 406;
return callback(err, info);
But I can't find how to change status code for create.
I found the create method:
MySQL.prototype.create = function (model, data, callback) {
var fields = this.toFields(model, data);
var sql = 'INSERT INTO ' + this.tableEscaped(model);
if (fields) {
sql += ' SET ' + fields;
} else {
sql += ' VALUES ()';
}
this.query(sql, function (err, info) {
callback(err, info && info.insertId);
});
};
In your call to remoteMethod you can add a function to the response directly. This is accomplished with the rest.after option:
function responseStatus(status) {
return function(context, callback) {
var result = context.result;
if(testResult(result)) { // testResult is some method for checking that you have the correct return data
context.res.statusCode = status;
}
return callback();
}
}
MyModel.remoteMethod('create', {
description: 'Create a new object and persist it into the data source',
accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}},
returns: {arg: 'data', type: mname, root: true},
http: {verb: 'post', path: '/'},
rest: {after: responseStatus(201) }
});
Note: It appears that strongloop will force a 204 "No Content" if the context.result value is falsey. To get around this I simply pass back an empty object {} with my desired status code.
You can specify a default success response code for a remote method in the http parameter.
MyModel.remoteMethod(
'create',
{
http: {path: '/', verb: 'post', status: 201},
...
}
);
For loopback verion 2 and 3+: you can also use afterRemote hook to modify the response:
module.exports = function(MyModel) {
MyModel.afterRemote('create', function(
context,
remoteMethodOutput,
next
) {
context.res.statusCode = 201;
next();
});
};
This way, you don't have to modify or touch original method or its signature. You can also customize the output along with the status code from this hook.