how to test function with Fuse.js dependency - node.js

I want to implement .test file for the following function:
import Fuse from 'fuse.js';
import data from '../dal/data';
async function search(searchedData) {
// In case searched name is null
if (!searchedData) {
return [];
}
// load data
const dataload = await data();
// set options to filter data
const options = {
includeMatches: true,
// Search in `name'
keys: ['name'],
};
const fuse = new Fuse(dataload, options);
const matchedList = fuse.search(searchedData)
.map((element) => element.item);
return { matchedList };
}
export default search;
How to do for Fuse.js, shall I implement a mock data?

Fuse.js is library that provides fuzzy search based on dynamic configurations pass to it. There are different library which are available such as fuzzy-search.
Now, the question is how you can mock similar library as fuzzy search as per the comment. This is a good example of how you can start building your own fuzzy search engine, but to do so, you need to understand what fuzzy search is and how it works.
Now, if you want to know how Fuse.js works with any mock data. I can add some code which might help you to integrate with your existing project.
Mock Data
// it could be of any type, I am using Array of objects
const mockData = [{
name : 'Jason Durham',
age : 28,
address : 'Newyork city, New York, USA'
}, {
name : 'Jameson Durham',
age : 28,
address : 'Cleveland, Ohio, USA'
},
{
name : 'Tintan Groofs',
age : 28,
address : 'Ohio city, Ohio, USA'
}]
const options = {
includeScore: true, // the o/p will include the ranking based on weights
keys: [
{
name: 'name',
weight: 0.3 // what weightage you want this key in your search result
},
{
name: 'address',
weight: 0.7 // what weightage you want this key in your search result
}
]
}
// Create a new instance of Fuse
const fuse = new Fuse(mockData, options)
// Now search for 'Man'
const result = fuse.search('nwyork');
console.log(result);
You can check fuse.js documentation to check how to advance your search and configurable.
You can test the fuzzy search results using a test framework provided that you know the expected values, but it will contain a very broad set of o/p based on your configuration. As of now Fuse.js doesn't provide direct way in their documentation to check for it, but you can create a method based on the ranking you get and create a threshold value for it. This is the way we are testing our search results using Fuse.js, all the results below this threshold is discarded.

Related

Using Suitescript, can I find a record related to my current without N/search?

I'm trying to delete a record that is related to the current record loaded using a workflow action script. I've seen other answers to similar questions saying that I can find the internal ID of the related record with a search using the value in the key field as the search parameter. Problem: The N/search module is not available in a workflow action script, which is where I'm currently coding.
To make the question more concrete, my user has pulled up a general ledger account record ('account'), and they're going to delete it. I have an NS bundle that functions as an integrator with a 3rd party piece of software. The NS bundle has a value transformation table that maps the NS account codes to the 3rd party software codes. Each value transformation lives in a custom record ('icvt') created by the bundle, and I need the value transformation record deleted. I was hoping to use N/search to look up the icvt record, but I CAN'T USE SEARCH in a workflow action script.
Help.
Edit: Just realized I think I misunderstood the documentation, and that N/search is available, so my error must be somewhere else.
Edit to add code. This is the search code I was using before I realized the module wasn't available to me:
/**
*#NApiVersion 2.x
* #NScriptType WorkflowActionScript
*/
// Deletes Value Transform related to account deleted.
define(['N/record', 'N/search', 'N/log'], function (record, search, log) {
function wfDeleteICVT(scriptContext) {
var deleted_account = scriptContext.newRecord;
var da_internalID = deleted_account.getValue("id");
var v_da_internalID = da_internalID.toString();
var mySearch = search.create({
type: 'icvt',
filters: [
search.createFilter({
name: 'sourcevalue',
operator: search.Operator.IS,
values: v_da_internalID
})
]
});
//log.debug(v_da_internalID)
var myResultSet = mySearch.run();
var myResultRange = myResultSet.getRange(0,1);
log.debug({title: myResultRange.length, details: myResultSet})
var deleted_icvt = mySearch[0];
var di_icvt = deleted_icvt.id;
deleted_icvt.delete({
type: 'icvt',
id: di_internalID
});
}
return {
onAction : wfDeleteICVT
}
});
If you are able to fetch the ID of the record that needs to be deleted, you can use N/record module with record.delete(options) to delete the desired record.
// Delete a sales order.
var salesOrderRecord = record.delete({
type: record.Type.SALES_ORDER,
id: 88,
});
// Delete an instance of a custom record type with the ID customrecord_feature.
var featureRecord = record.delete({
type: 'customrecord_feature',
id: 3,
});
EDIT-
N/search module works on Workflow Action Scripts. I myself have used it many times. Why don't you create the Saved Search in UI on Netsuite and then Export it as script and use it in your script?
To export - You can use the Chrome extension below.
NetSuite Search Export
Also, I can see you have created filters in your search.create method but you didn't add any columns. That might be the issue.
Let me know.
Here's the final solution that worked. I was completely wrong in my initial assumptions that I couldn't use search, so that was the ultimate solution, although I did take #sayeesh's advice and use a search created through the UI:
/**
*#NApiVersion 2.x
* #NScriptType WorkflowActionScript
*/
// Deletes Value Transform related to account deleted.
define(['N/record', 'N/search', 'N/log'], function (record, search, log) {
function icvtDelete(scriptContext) {
var deleted_account = scriptContext.newRecord; //gets the account record of the current account
var da_internalID = deleted_account.getValue("id"); //gets the internal id of the current account
var v_da_internalID = da_internalID.toString(); //converts the internal id to a string for comparison later
var mySearch = search.load({
id: "customsearch_icvt_search"}); //loads a saved search
var mySearchResult = mySearch.run(); //runs the saved search
var myResults = mySearchResult.getRange(0,1000); //gets the range of results
for(var i in myResults){ //iterates through the results
var result = myResults[i]; //load a result
if(result.getValue('custrecordictransformationvalue') == v_da_internalID) //if the result is the one we're looking for
{
var icvt_to_delete = record.load({
type: 'mycustomrecord',
id: result.getValue('id')
});
log.debug(v_da_internalID, result.getValue('id'));
icvt_to_delete.setValue({fieldId: 'deletionreason', value: 1});
icvt_to_delete.setValue({fieldId: 'deletionreasonmemo', value: 'SS2.0 Delete reason saved using Load/Save'});
icvt_to_delete.save();
record.delete({
type: 'mycustomrecord',
id: result.getValue('id')
});
}
}
}
return {
onAction : icvtDelete
}
});

Optional parameters on sequelize query

Good morning.
I'm quite new to NodeJS / sequelize world and I'm currently facing a problem while trying to display a dashboard on screen.
This dashboard has three filters: two dates (period), client name, and employee name. The user can select none, one, two, or all the filters and my database needs to work accordingly.
That being said, my problem is with Sequelize because I don't know how to treat this problem of parameters not being "always" there.
I've seen this question:
Sequelize optional where clause parameters?
but this answer doesn't work anymore. I also tried another way of building the where clause, but I failed on it as well (mainly due to sequelize operators).
The last thing I tried was to make a single query with all parameters included but try to find some value (or flag) that would make sequelize ignore the parameter, for the case when the parameter was no there*, but it looks like Sequelize doesn't have anything like that.
* I've read a question here that has an answer saying that {} would do the trick but I tried that as well but didn't work.
In summary: I need to make a query that can "change" over time, for example:
Foo.findAll({
where: {
id : 1,
}
});
Foo.findAll({
where: {
id {
[Op.in] : [1,2,3,4,5]
},
name: "palmeiira",
}
});
Do you know a way of doing it without the need of using a lot if / switch statements?
I'm currently using Sequelize v. 5.5.1.
Update
I tried doing as suggested by #Anatoly and created a function to build the parameters. It was something like that. (I tried a "smaller" version just to test)
async function test() {
const where = {};
where[Op.and] = [];
where[Op.eq].push({
id: {
[Op.in]: [1,2,3]
}
});
return where;
}
I setted the return value to a const:
const query = await test()
And tried console.log(query)
The result was: { [Symbol(and)]: [ { id: [Object] } ] }, which made me believe that the problem was parsing the Op part so i tried using 'Op.and' and 'Op.in' to avoid that and it solved this problem, but led to another on sequelize that said Invalid value
Do you have any idea where is my error ?
P.S.: #Anatoly very nice idea you gave me on original answer. Thank you very much.
If these three conditions should work together then you can use Op.and with an array of conditions:
const where = {}
if (datesFilter || clientNameFilter || employeenameFilter) {
where[Op.and] = []
if (datesFilter) {
where[Op.and].push({
dateField: {
[Op.between]: [datesFilter.start, datesFilter.finish]
}
})
}
if (clientNameFilter) {
where[Op.and].push({
name: {
[Op.iLike]: `%${clientNameFilter.value}%`
}
})
}
if (employeenameFilter) {
where[Op.and].push({
employeeName: {
[Op.iLike]: `%${employeenameFilter.value}%`
}
})
}
}
const dashboardItems = await DashboardItem.findAll({ where }, {
// some options here
})
If the conditions should work as alternatives then just replace Op.and with Op.or

how can i search database table with latitude and longitude using sequelize

I have a store table that has a column lat_long which stores each store latitude/longitude as geometry point in sequelize. I want to be searching for stores based on proximity of the latitude/longitude from the request body from the front end. Have tried to exploit many related topics(answer) concerning this in Stackoverflow but what have learned is that the query can be written like this.
const location = sequelize.literal(`ST_GeomFromText('POINT(${lng} ${lat})', 4326)`);
User.findAll({
attributes: [[sequelize.fn('ST_Distance_Sphere', sequelize.literal('geolocation'), location),'distance']],
order: 'distance',
limit: 10,
logging: console.log
})
.then(function(instance){
console.log(instance);
})
Have tried it and it works fine but the challenge am having is that; in this example and almost all the sample I saw sofar they made use of the attribute which return only distance but I want to return all the fields in the table. Then I tried removing the attribute then the response I got was not searched based on the geometry(distance) and I also tried to use where but am getting an error.
Shown below is my latest query which is returning error please how can I go about this? thank you.
export const getNearestStoreWithCategory = async (lat, long) => {
try {
const location = sequelize.literal(`ST_GeomFromText('POINT(${lat} ${long})')`);
const distance = sequelize.fn('ST_Distance_Sphere', sequelize.literal('lat_long'), location)
const storeArr = await Store.findAll({
order: distance,
where: sequelize.where(distance),
limit: 20
})
return storeArr
} catch (error) {
return error
}
}
From the doc at https://sequelize.org/v4/manual/tutorial/querying.html, I'm guessing what you are missing in the sample code you had is including the distance to the fields to be returned as the response from the db rather that only computing the distance and returning;
const location = sequelize.literal(`ST_GeomFromText('POINT(${lng} ${lat})', 4326)`);
User.findAll({
attributes: {
include: [[sequelize.fn('ST_Distance_Sphere', sequelize.literal('geolocation'), location),'distance']]
},
order: 'distance',
limit: 10,
logging: console.log
})
.then(function(instance){
console.log(instance);
})
This should give a query like;
SELECT id, OTHER_FIELDS_OF_THIS_TABLE, ST_Distance_Sphere(geolocation) AS distance ...
Now, this is untested code, but you could give it a try and comment if it helps or not.

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);

SQL Server : lower camelcase for each result

I am creating an API with SQL Server as the database. My tables and columns are using Pascal case (CountryId, IsDeleted, etc) that cannot be changed.
So when I do this:
const mssql = require('mssql');
var sqlstr =
'select * from Country where CountryId = #countryId';
var db = await koaApp.getDb();
let result = await db.request()
.input('countryId', mssql.Int, countryId)
.query(sqlstr);
My resulting object is
{
CountryId: 1,
CountryName: "Germany"
}
But I want it to be
{
countryId: 1,
countryName: "Germany"
}
I know there is a "row" event, but I wanted something more performant (since I may be returning several rows from the query, above is just an example).
Any suggestions?
PS: I want to avoid the FOR JSON syntax
Posting this as an actual answer, as it proved helpful to the OP:
if it's viable, you may try simply specifying the columns in the query as such:
select
CountryID countryId,
CountryName countryName
from
Country
where
CountryId = #countryId
Typically it's not best practice to use select * within queries anyways because of performance.
A simple explanation, putting a space and a new name (or perhaps better practice, within square brackets after each column name, such as CountryName [countryName] - this allows for characters such as spaces to be included within the new names) is aliasing the name with a new name of your choosing when returned from SQL.
I'd suggest using the lodash utility library to convert the column names, there is a _.camelCase function for this:
CamelCase documentation
_.camelCase('Foo Bar');
// => 'fooBar'
_.camelCase('--foo-bar--');
// => 'fooBar'
_.camelCase('__FOO_BAR__');
// => 'fooBar'
You can enumerate the result keys using Object.entries then do a reduce, e.g.
let result = {
CountryId: 1,
CountryName: "Germany"
};
let resultCamelCase = Object.entries(result).reduce((obj,[key,value]) => {
obj[_.camelCase(key)] = value;
return obj;
}, {});
console.log(resultCamelCase);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>

Resources