Nesting multiple [Op.not] queries in a single where clause - javascript

I've got a filter that allows users to indicate themes and genres they would like removed from the search results. This is an OR, not AND situation, so if either a theme or a genre is matched, it should be removed from the pool.
If I run the query below with a single [Op.not] clause, the themes and genres filter correctly. When I attempt to combine them as show in the example, only the genres [Op.not] executes. I assume I've chained them incorrectly in the where clause?
Code Example
//Variables to store filters from JSON body
let sortBy = [req.body.galleryFilters.sortBy, 'ASC']
let hiddenThemes = [];
let hiddenGenres = [];
//Parse the body for activeFilters create and array of Active Filters
req.body.galleryFilters.themes.forEach(theme => {
if(theme.hidden === true) {
hiddenThemes.push(theme.name)
}
})
req.body.galleryFilters.genres.forEach(genre => {
if(genre.hidden === true) {
hiddenGenres.push(genre.name)
}
})
//use variables above to create new playlist obje
const playlists = await db.playlists.findAll({
where: {
[Op.not]: {
themes: {
[Op.overlap]: hiddenThemes
},
},
[Op.not]: {
genres: {
[Op.overlap]: hiddenGenres
},
},
},
order: [[req.body.galleryFilters.sortBy, 'ASC']]
});
return res.send(playlists);
}
});

I'm too inexperienced to give a thorough explanation but the revision below seems to work for now:
const playlists = await db.playlists.findAll({
where: {
[Op.not]: {
[Op.or]: {
themes: { [Op.overlap]: hiddenThemes },
genres: { [Op.overlap]: hiddenGenres },
}
}
},
order: [[req.body.galleryFilters.sortBy, 'ASC']]
});
return res.send(playlists);
}

Related

Trying to write a recursive asynchronous search in JavaScript

I am trying to write some code that searches through a bunch of objects in a MongoDB database. I want to pull the objects from the database by ID, then those objects have ID references. The program should be searching for a specific ID through this process, first getting object from id, then ids from the object.
async function objectFinder(ID1, ID2, depth, previousList = []) {
let route = []
if (ID1 == ID2) {
return [ID2]
} else {
previousList.push(ID1)
let obj1 = await findObjectByID(ID1)
let connectedID = obj1.connections.concat(obj1.inclusions) //creates array of both references to object and references from object
let mapPromises = connectedID.map(async (id) => {
return findID(id) //async function
})
let fulfilled = await Promise.allSettled(mapPromises)
let list = fulfilled.map((object) => {
return object.value.main, object.value.included
})
list = list.filter(id => !previousList.includes(id))
for (id of list) {
await objectFinder(id, ID2, depth - 1, previousList).then(result => {
route = [ID1].concat(result)
if (route[route.length - 1] == ID2) {
return route
}})
}
}
if (route[route.length - 1] == ID2) {
return route
}
}
I am not sure how to make it so that my code works like a tree search, with each object and ID being a node.
I didn't look too much into your code as I strongly believe in letting your database do the work for you if possible.
In this case Mongo has the $graphLookup aggregation stage, which allows recursive lookups. here is a quick example on how to use it:
db.collection.aggregate([
{
$match: {
_id: 1,
}
},
{
"$graphLookup": {
"from": "collection",
"startWith": "$inclusions",
"connectFromField": "inclusions",
"connectToField": "_id",
"as": "matches",
}
},
{
//the rest of the pipeline is just to restore the original structure you don't need this
$addFields: {
matches: {
"$concatArrays": [
[
{
_id: "$_id",
inclusions: "$inclusions"
}
],
"$matches"
]
}
}
},
{
$unwind: "$matches"
},
{
"$replaceRoot": {
"newRoot": "$matches"
}
}
])
Mongo Playground
If for whatever reason you want to keep this in code then I would take a look at your for loop:
for (id of list) {
await objectFinder(id, ID2, depth - 1, previousList).then(result => {
route = [ID1].concat(result);
if (route[route.length - 1] == ID2) {
return route;
}
});
}
Just from a quick glance I can tell you're executing this:
route = [ID1].concat(result);
Many times at the same level. Additional I could not understand your bottom return statements, I feel like there might be an issue there.

Javascript - filtering a list: how can I find an intersection between an array with objects including array and an array?

How can I filter a list (array with objects) with a filter list (array) and find intersections? I add to the filter array every time a user checks the checkbox clicking on particular filter. When user unchecks the checkbox I remove from filter array. Somehow whateever i try doing, i always return the entire reviews array including ALL not filtered items. Why? Thanks!!
const reviews = [
{
title: "item 1",
filter_results: {
features: ["message", "call"],
pricing: ["Business", "Free", "Whatever"],
rating: [1]
}
},
{
title: "item 2",
filter_results: {
features: ["call", "copy", "paste"],
pricing: ["Business"],
rating: [1]
}
},
{
title: "item 3",
filter_results: {
features: ["copy", "connect", "wifi"],
pricing: ["Free",
rating: [2]
}
}
]
const filteredReviews = {
pricing_options: ["Business"],
popular_features: ["copy, call"],
rating: [1, 2]
}
const update = (reviews, categoryName) => {
if (categoryName) {
return reviews.filter(review => {
return review.filter_results[categoryName].filter(value => {
if (filteredReviews[categoryName].includes(value)) {
return review
}
})
})
} else {
return reviews
}
}
update(reviews, "pricing")
Return a boolean on filter callback, and do a better filtering mechanism:
const update = (reviews, filters) => {
if (filters) {
return reviews.filter(review =>
Object.entries(filters)
// Change to `some` if the filters are OR'ed instead of AND'ed
.every(
([filter_key, filter_values]) =>
// Change `some` to `every` if all elements in the
// userFilter[*] array MUST be matched instead of some of them
filter_values.some( (filter_value) =>
review.filter_results[filter_key]
.includes(filter_value)
)
)
)
} else {
return reviews
}
}
// Fix variables names:
// - `userFilters` contains the filters selected by the user
// - `filteredReviews` contains the array of reviews, resulting from
// filtering the reviews using the `userFilters`
// Fix key names: Use same keys than in reviews, instead of:
// - `pricing_options` => `pricing`
// - `popular_features` => `features`
const userFilters = {
pricing: ["Business"],
// Transformed/fixed to 2 values. Was it a typo?
features: ["copy", "call"],
};
const filteredReviews = update(reviews, userFilters);
Filter callback function should return a "boolean", you are returning arrays which evaluate always to "true".

Apollo GraphQL updateQuery to typePolicy

I am beating my head against a wall. I have updated to Apollo 3, and cannot figure out how to migrate an updateQuery to a typePolicy. I am doing basic continuation based pagination, and this is how I used to merged the results of fetchMore:
await fetchMore({
query: MessagesByThreadIDQuery,
variables: {
threadId: threadId,
limit: Configuration.MessagePageSize,
continuation: token
},
updateQuery: (prev, curr) => {
// Extract our updated message page.
const last = prev.messagesByThreadId.messages ?? []
const next = curr.fetchMoreResult?.messagesByThreadId.messages ?? []
return {
messagesByThreadId: {
__typename: 'MessagesContinuation',
messages: [...last, ...next],
continuation: curr.fetchMoreResult?.messagesByThreadId.continuation
}
}
}
I have made an attempt to write the merge typePolicy myself, but it just continually loads and throws errors about duplicate identifiers in the Apollo cache. Here is what my typePolicy looks like for my query.
typePolicies: {
Query: {
fields: {
messagesByThreadId: {
keyArgs: false,
merge: (existing, incoming, args): IMessagesContinuation => {
const typedExisting: IMessagesContinuation | undefined = existing
const typedIncoming: IMessagesContinuation | undefined = incoming
const existingMessages = (typedExisting?.messages ?? [])
const incomingMessages = (typedIncoming?.messages ?? [])
const result = existing ? {
__typename: 'MessageContinuation',
messages: [...existingMessages, ...incomingMessages],
continuation: typedIncoming?.continuation
} : incoming
return result
}
}
}
}
}
So I was able to solve my use-case. It seems way harder than it really needs to be. I essentially have to attempt to locate existing items matching the incoming and overwrite them, as well as add any new items that don't yet exist in the cache.
I also have to only apply this logic if a continuation token was provided, because if it's null or undefined, I should just use the incoming value because that indicates that we are doing an initial load.
My document is shaped like this:
{
"items": [{ id: string, ...others }],
"continuation": "some_token_value"
}
I created a generic type policy that I can use for all my documents that have a similar shape. It allows me to specify the name of the items property, what the key args are that I want to cache on, and the name of the graphql type.
export function ContinuationPolicy(keyArgs: Array<string>, itemPropertyKey: string, typeName: string) {
return {
keyArgs,
merge(existing: any, incoming: any, args: any) {
if (!!existing && !!args.args?.continuation) {
const existingItems = (existing ? existing[itemPropertyKey] : [])
const incomingItems = (incoming ? incoming[itemPropertyKey] : [])
let items: Array<any> = [...existingItems]
for (let i = 0; i < incomingItems.length; i++) {
const current = incomingItems[i] as any
const found = items.findIndex(m => m.__ref === current.__ref)
if (found > -1) {
items[found] === current
} else {
items = [...items, current]
}
}
// This new data is a continuation of the last data.
return {
__typename: typeName,
[itemPropertyKey]: items,
continuation: incoming.continuation
}
} else {
// When we have no existing data in the cache, we'll just use the incoming data.
return incoming
}
}
}
}

Update the provider array & increment count only if providerId is not there in Document

I am stuck in a situation like this. I have the following document.
{
"_id" : ObjectId("5ceba7c419b48423e452972a"),
"userId" : ObjectId("5ceba7c419b48423e4529727"),
"providers" : [
"1689736266",
"1598763690",
"1528069614",
"1831364272",
"1548463045",
"1245301159",
"1386616399",
"1790775971",
"1629462130",
"1992169783"
],
"countByType" : {
"doctors" : 6,
"labs" : 0,
"hospitals" : 0,
"imagingCenters" : 0,
"other" : 4
}
}
Initially, I fetch the providerArray from elastic search, added to providers array and incremented the count by using the following query.
let updateProviderId = await userProviderCollection.update(
regUserId, {
$set: {
providers: combinedProviderArray,
countByType: providerCountType
}
});
where combinedProviderArray is a combined array of providers. ProviderId is created when a new report is generated. The above condition will work if reports already there.
But when a new report comes, I need to check in providers array and increment the count if not in an array, else do nothing. How can I achieve this.
Please see the code below when a report with providerId is already in an array.
// Searching for Providers in Elastic Search
let searchForProviders = await esHelper.getProvidersById(combinedProviderArray, true);
searchForProviders.forEach((getCategory) => {
let prCategory = getCategory.category;
prCategory.forEach((cat) => {
// Combining categories
categoryArray = categoryArray.concat(cat);
});
});
let counts = {
"doctor": 0,
"laboratory": 0,
"hospital": 0,
"imagingcenter": 0,
"other": 0
};
let x;
categoryArray.forEach(function(x) {
counts[x] = (counts[x] || 0)+1;
});
let providerCountType = {
"doctors": counts.doctor,
"labs": counts.laboratory,
"hospitals": counts.hospital,
"imagingCenters": counts.imagingcenter,
"other": counts.other
}
// Updating npiId & category count
userproviderData.getDataSource().connector.connect(async (err, db) => {
let userProviderCollection = db.collection("UserProviders");
let regUserId = { userId: userId};
let updateProviderId = await userProviderCollection.update(
regUserId, {
$set: {
providers: combinedProviderArray,
countByType: providerCountType
}
});
});
Please provide an appropriate solution for the same. Any help would be really appreciated. I am stuck on how to add a provider to array & increment the count according to that.
Instead of rebuilding the array every time, you can use $addToSet. This has the effect of adding the value to the array, but only if it is not already present...
let updateProviderId = await userProviderCollection.update(
regUserId, {
$addToSet: {
providers: providerID
}
});
As for the counts, it can be done similarly, using $inc.
let updateProviderId = await userProviderCollection.update(
regUserId, {
$inc: {
"countByType.doctors": 1
}
});
You could build your increment object easily:
let $inc= {
"countByType.doctors": counts.doctor,
"countByType.labs": counts.laboratory,
"countByType.hospitals": counts.hospital,
"countByType.imagingCenters": counts.imagingcenter,
"countByType.other": counts.other
}
let updateProviderId = await userProviderCollection.update(regUserId, {$inc});
If you are just adding one, and want to do everything at the same time, you could just the first 2 examples:
let $inc = {};
$inc["countByType."+categoryName] = 1;
let updateProviderId = await userProviderCollection.update(
regUserId, {
$addToSet: {
providers: providerID
},
$inc
});
But it would increment the count even tough the provider was already present... So you'd need to check if the $addToSet worked, and then do the increment:
let updateProviderId = await userProviderCollection.update(
regUserId, {
$addToSet: {
providers: providerID
}
});
if(updateProviderId["nMatched"] === 1 //A document was matched
&& updateProviderId["nModified"] === 1 //A document was modified
){
let updateProviderCount = await userProviderCollection.update(regUserId, {$inc});
}

Match two values in a collection

I have two values I need to match as you can see in the picture below:
I tried something like this:
const index = state.locks.users.findIndex(
stateUser => stateUser._id === action.payload.customerPayload.accessid
But I’m getting the error:
findIndex of undefined.
And I guess that’s because of locks being an array.
But I’m really uncertain how to fix this issue. Should I have multiple findIndexes? One for the lock and one for to match the users?
Thanks for reading my post. And I appreciate all the help I can get.
The code snippet should be
let itemIndex = -1;
state.locks.map((lock) => {
lock.users.findIndex(...)
});
Assuming state is an object containing locks array.
My suggestion to you is do a double for loop (as you've already figured) to get the user object that you need.
Consider the following snippet (adjust to your data structure):
let state = {
locks: [
{
users: [
{ _id: '123' },
{ _id: '456' }
]
},
{
users: [
{ _id: '678' },
{ _id: '789' }
]
},
]
};
function getUserObjByID(stateObj, userID) {
for (let usersObject of state.locks) {
for (let user of usersObject.users) {
if (user._id === userID) {
return user;
}
}
}
}
let myObj = getUserObjByID(state, '678');
console.log(myObj);
So it works now. What I had to do with my reducer was this:
case 'REMOVE_USER':
return {
...state,
locks: state.locks.map(lock => {
return {
...lock,
users: lock.users
? lock.users.filter(
user => user._id != action.payload.customerPayload.accessid
)
: []
}
})
}

Categories