Pick random array item from response in Paw - paw-app

Given the following API response, I'd like to use the "Response Parsed Body" dynamic variable and select a random id from the array:
[
{
"id": 1
},
{
"id": "2"
}
]
Using [0].id gives me 1, but there's no way to select a random item. This is likely a problem with JSONPath, but it would be nice to have Paw implement a way to do this.

The best way to do this is to create a custom dynamic value.
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
function evaluate(context) {
var request = context.getRequestByName('OtherRequestName')
var lastExchange = request.getLastExchange()
var body = JSON.parse(lastExchange.responseBody)
var list = body // path to list within body
var i = getRandomInt(0, list.length)
return list[i].id
}

let list = JSON.parse(responseBody);
let random_num = _.random(list.length);
let randomId= list[random_num].id;
postman.setEnvironmentVariable("randomId", randomId);

Related

Kibana Server-Side Plugin in 7.16.3, Response.Body is always empty and fails validation step

The plugin I'm working on is for Kibana 7.16.3.
The server side code currently looks like the following:
import { schema } from '#kbn/config-schema';
import { logger } from 'elastic-apm-node';
import { IRouter } from '../../../../src/core/server';
import { ComplexityAndChurnFactory } from "../resources/cxchquery";
import { validateBody, linearmap } from "../resources/utility";
let elasticSearchHost = ""
export function defineHosts(host: string) {
elasticSearchHost = host
}
export function defineRoutes(router: IRouter) {
router.get(
{
path: '/api/complexity_and_churn/agg',
validate: {
params: schema.object({}),
body: schema.object({
Size: schema.number({}),
Index: schema.string({}),
StartDate: schema.string({}),
EndDate: schema.string({}),
FileTypeFilters: schema.arrayOf(schema.string({}), {})
}, { })
},
},
async (context, request, response) => {
console.log(`Recv Req: ${JSON.stringify(request.body)}`);
let reqBody = request.body;
validateBody(reqBody);
let query = ComplexityAndChurnFactory(reqBody.Index, reqBody.StartDate, reqBody.EndDate, reqBody.FileTypeFilters, 10000);
let resultSize = reqBody.Size;
let minScore = 0;
let maxScore = 50;
// If the user needs to scan over 10 million files after date range and filtering, there is likely a bigger problem.
const MAX_QUERIES = 1000;
let topXScores: Array<Object> = []
/**Strategy for getting top scores in one pass of the dataset
* Composite aggreggation returns subset of data => update global min/max complexity/churn based on this data.
* Based on global min/max complexity/churn, calculate the score of the composite aggregation subset.
* Based on global min/max complexity/churn, update the score of the previously saved top scores.
* Join the current aggregation subset and previously saved top scores into one dataset.
* Remove all but the top x scores.
* Repeat with previous composite aggregation after key until data is exhausted.
*/
let minComplexity = Number.POSITIVE_INFINITY;
let maxComplexity = Number.NEGATIVE_INFINITY;
let minChurn = Number.POSITIVE_INFINITY;
let maxChurn = Number.NEGATIVE_INFINITY;
let i = 0;
for (i=0; i<MAX_QUERIES; i++)
{
let resp = await context.core.elasticsearch.client.asCurrentUser.search(
query
);
logger.info(`query responded with: ${resp}`);
// Check for completion
let buckets = resp.body.aggregations.buckets.buckets;
if (buckets.length == 0 || !query?.after_key) {
break;
}
// Set up next query if buckets were returned.
query.after_key = resp.body.aggregations.buckets.after_key;
minComplexity = buckets.reduce((p: Object, v: Object)=>p.complexity.value < v.complexity.value? p.complexity.value : v.complexity.value, minComplexity);
maxComplexity = buckets.reduce((p: Object, v: Object)=>p.complexity.value > v.complexity.value? p.complexity.value : v.complexity.value, maxComplexity);
minChurn = buckets.reduce((p: Object, v: Object)=>p.churn.value < v.churn.value? p.churn.value : v.churn.value, minChurn);
maxChurn = buckets.reduce((p: Object, v: Object)=>p.churn.value > v.churn.value? p.churn.value : v.churn.value, maxChurn);
// Recalculate scores for topXScores based on updated min and max complexity and churn.
topXScores.forEach(element => {
let complexityScore = linearmap(element.complexity.value, minComplexity, maxComplexity, minScore, maxScore);
let churnScore = linearmap(element.churn.value, minChurn, maxChurn, minScore, maxScore);
element.score = complexityScore + churnScore;
});
// For new data, calculate score and add to topXScores array.
buckets.forEach(element => {
let complexityScore = linearmap(element.complexity.value, minComplexity, maxComplexity, minScore, maxScore);
let churnScore = linearmap(element.churn.value, minChurn, maxChurn, minScore, maxScore);
element.score = complexityScore + churnScore;
topXScores.push(element);
});
// Sort the topXScores by score.
topXScores = topXScores.sort((a, b) => a.score - b.score);
// Remove all but the top x scores from the array.
let numberBucketsToRemove = Math.max(topXScores.length - resultSize, 0);
topXScores.splice(0, numberBucketsToRemove);
}
if (i == MAX_QUERIES) {
throw new Error(`[ERROR] Exceeded maximum allowed queries (${MAX_QUERIES}) for composite aggregations please reach out to an administrator to get this amount changed or limit your query's date range and filters.`)
}
return response.ok({
body: {
buckets: topXScores
}
});
}
);
}
When I make a request to the endpoint like in the following:
curl --request GET 'http://localhost:5601/api/complexity_and_churn/agg' --header 'kbn-xsrf: anything' --header 'content-type: application/json; charset=utf-8' --header 'Authorization: Basic <Auth>' -d '{
"Size": 100,
"Index": "mainindexfour",
"StartDate": "2010/10/10",
"EndDate": "2022/10/10",
"FileTypeFilters": ["xml"]
}'
I get the response:
{
"statusCode": 400,
"error": "Bad Request",
"message": "[request body.Size]: expected value of type [number] but got [undefined]"
}
If I remove the validation on the body and print out JSON.stringify(request.body), I see that it is an empty object, regardless of what data I send. If I try to use params or query, they also end up being undefined.
Is my server side code or the request I'm sending incorrect?

C# ASP.NET json objects to array

I am stuck on this error. This is my JSON data:
{
"date": "2016-08-26",
"time_of_day": "14:19",
"request_time": "2016-08-26T14:19:59+01:00",
"station_name": "Derby",
"station_code": "DBY",
"departures": {
"all": [
{
"mode": "train",
"service": "22152000",
"train_uid": "C65080"
},
{
"mode": "train",
"service": "22150000",
"train_uid": "C65145"
},
{
"mode": "train",
"service": "22180008",
"train_uid": "C70700"
}
]
}
}
What I am trying to do is add the service json object to an array, after this I want to increment through the service list and add each service to a separate API call. This is my code:
dynamic content = JsonConvert.DeserializeObject(json);
dynamic departures = content.departures;
dynamic DepartTimes = departures.all;
JArray items = ((JArray)DepartTimes["service"]);
int serviceLength = items.Count;
for (int i = 0; i < serviceLength; i++)
{
//next api call containing array increment
}
The error seems to be here :
JArray items = ((JArray)DepartTimes["service"]);
Any help is much appreciated, thank you!
One possible way is -
var json = "json";
dynamic d = JsonConvert.DeserializeObject(json);
JArray arr = new JArray();
foreach(JObject o in d.departures.all){
arr.Add(o["service"]);
}
Console.Write(arr.Count); //output : 3
Provided Json data is not proper. Second and third items must have open curly braces ({)
In addition, an example working code may be:
dynamic content = JsonConvert.DeserializeObject(json));
JArray items = (JArray)content.departures.all;
var newArray = items.Select(x=>x["service"].ToString()).ToArray();

How to make TagsInput to work with both auto complete & free text

UPDATE
This issue is already discussed in github here
I am using tagsinput with typeahead in bootstrap 3. The problem which I am experiencing is with the value in case if user selects the existing tag. Display text shows it right but .val() returns its actual object. Below is the code
$('#tags').tagsinput({
//itemValue: 'value',
typeahead: {
source: function (query) {
//tags = [];
//map = {};
return $.getJSON('VirtualRoomService.asmx/GetTags?pid=' + $("#<%=hdnPID.ClientID%>").val() + '&tok=' + query)
//, function (data) {
// $.each(data, function (i, tag) {
// map[tag.TagValue] = tag;
// tags.push(tag.TagValue);
// });
// return process(tags);
//});
}
}
//freeElementSelector: "#freeTexts"
});
The problem with above code is that it results as below while fetching tags from web method
This happens when user select the existing tag. New tags no issues. I tried setting itemValue & itemText of tagsinput but not worked. Hence I decided a work-around of this problem. Since I could able get the json string as ['IRDAI", Object], if can somehow parse these object & get the actual tag value then I get the expected result of the code I am looking at.
Below is what it appears in tags input as [object Object] for text selected by user from auto populated drop down
[![enter imt
If I i specify TagId & TagValue to itemValue & itemText as below code
$('#tags').tagsinput({
itemValue: 'TagId',
itemText: 'TagValue',
typeahead: {
source: function (query) {
//tags = [];
//map = {};
return $.getJSON('VirtualRoomService.asmx/GetTags?pid=' + $("#<%=hdnPID.ClientID%>").val() + '&tok=' + query)
//, function (data) {
// $.each(data, function (i, tag) {
// //map[tag.TagValue] = tag;
// tags.push(tag.TagValue);
// });
//});
// return process(tags);
}
}
//freeElementSelector: "#freeTexts"
});
Then the result is displaying as below when below code is executed
var arr = junit.Tags.split(',');
for (var i = 0; i < arr.length; i++) {
$('#tags').tagsinput('add', arr[i]);
}
Given your example JSON response from your data source:
[
{"TagId":"1", "TagValue":"eSign"},
{"TagId":"2", "TagValue":"eInsurance Account"}
]
You'll need to tell tagsinput how to map the attributes from your response objects using itemValue and itemText in your tagsinput config object. It looks like you may have started down that path, but didn't reach the conclusion, which should look something like:
$('#tags').tagsinput({
itemValue: 'TagId',
itemText: 'TagValue',
typeahead: {
source: function (query) {
return $.getJSON('VirtualRoomService.asmx/GetTags?pid=' + $("#<%=hdnPID.ClientID%>").val() + '&tok=' + query);
}
}
});
Be sure to checkout the tagsinput examples.
This may not be the clean solution but I got around this issue through below parsing method. Hope this helps someone.
var items = $('#tags').tagsinput("items");
var tags = '';
for(i = 0; i < items.length; i++)
{
if(JSON.stringify(items[i]).indexOf('{') >= 0) {
tags += items[i].TagValue;
tags += ',';
} else {
tags += items[i];
tags += ',';
}
}

Meteor Heavy Helper Design Pattern

So, meteor reruns helper code every time it is called in the template, right? My issue is that I have a heavy helper that returns a large object. I'm iterating over a list of these objects and then over some nested objects which is resulting in a really big lag.
So, are there any design patterns that prevent recalling the whole helper every time? Or do I just need to break up my object?
Template.deliveries.helpers({
current_delivery: function() {
var delivery_id = Template.instance().data.current_delivery_id;
var delivery = Deliveries.findOne({'_id': delivery_id});
var project = Projects.findOne({'_id':Session.get('current_project_id')});
var secondary_profile_names = [];
if (Session.get('delivery_include_secondaries')) {
for (var n in project.delivery_profiles) {
if (project.delivery_profiles[n].name === delivery.delivery_profile) {
if (project.delivery_profiles[n].secondary_deliverables) {
secondary_profile_names = project.delivery_profiles[n].secondary_deliverables;
}
break;
}
}
}
$("#delivery-profile").val(delivery.delivery_profile);
var elements = $.map(delivery.elements, function(id, idx) {
i_el = InternalElements.findOne({'_id': id});
i_el.source_element = SourceElements.findOne({'_id':i_el.source_element});
if (secondary_profile_names) {
i_el.secondary_elements = InternalElements.find({
'source_element':i_el.source_element._id,
'name':{'$in': secondary_profile_names},
"$or": [{'is_primary':false}, {'is_primary': {'$exists':false}}]
},{
'sort':{'version':-1},
'limit':1
}).fetch();
} else {
i_el.secondary_elements = [];
}
return i_el;
});
delivery.elements = elements.sort(function(a,b) { return (a.shot_name > b.shot_name) - (a.shot_name < b.shot_name); });
return delivery;
},
});
A pattern I've used successfully is to cache the results of expensive computations in a local collection.
MyLocalCache = new Mongo.Collection();
I like to make objects in this collection 1:1 with the original object so I reuse the _id from the original along with any keys and values that don't require transformation then extend the object with the transformed values.

Google Embed API format data before calling .execute()

I need to format the response I get from Analytics before showing it inside a Google Chart, I tried editing the response when the on("success"... method gets fired but I found that it gets called after the .execute().
Is there any way to edit the response after receiving it and before it populates the chart?
This is my function:
var dataChart5 = new gapi.analytics.googleCharts.DataChart({
reportType: 'ga',
query: {
'ids': 'ga:***', // My ID
'start-date': '31daysAgo',
'end-date': 'yesterday',
'metrics': 'ga:users,ga:percentNewSessions,ga:sessions,ga:bounceRate,ga:avgSessionDuration,ga:pageviews,ga:pageviewsPerSession',
'prettyPrint':'true',
},
chart: {
'container': 'chart-5-container',
'type': 'TABLE',
'options': {
'width': '100%',
'title': 'test'
}
}
});
dataChart5.on('success', function(response) {
response.data.cols[0].label = "test1"; //here I edit the response
console.log(response);
});
dataChart5.execute();
Using the console.log(response); I can see that the record label gets modified but the chart gets populated before the edit.
I think a have a workaround. It has problems, but might be useful. While handling the success event, call a function that will recursively walk through the child elements of $('#chart-5-container') and apply your formatting there.
One problem with that approach is that the positions of the elements won't be recalculated. Therefore, with different string sizes you might get overlapping strings. Moreover, it seems not to be affecting the tooltip.
I'm using this approach to translate to Portuguese.
function recursiveTranslate(e) {
var key = e.html(),
dict = {};
dict['Date'] = 'Data';
dict['Users'] = 'Visitantes';
dict['Sessions'] = 'Visitas';
dict['Pageviews'] = 'Visualizações';
if (key in dict) {
e.html(dict[key]);
}
for (var i = 0; i < e.children().length; i++) {
recursiveTranslate($(e.children()[i]));
}
}
Then I call recursiveTranslate inside the success event:
dataChart5.on('success', function h(obj) {
recursiveTranslate($('#chart-5-container'));
});
It is not elegant and has a lot of issues. I would really like to get my hands on the proper solution.

Resources