I'm trying to fetch all user stories that belong to certain feature, but which have children.
Here's how I created query for that using rally-node.
async.map(features, function(feature, cb) {
self.restApi.query({
type: 'hierarchicalrequirement',
limit: Infinity,
order: 'Rank',
fetch: ['FormattedID', 'Name', 'Children'],
parent: feature.ObjectID,
query: queryUtils.where('DirectChildrenCount', '>', 0)
}, cb);
}, function(err, results) {
//user stories
});
And here's how my feature looks like:
{ _rallyAPIMajor: '2',
_rallyAPIMinor: '0',
_ref: 'https://rally1.rallydev.com/slm/webservice/v2.0/portfolioitem/feature/18846103932',
_refObjectUUID: 'c01d7f828-a6d6-4efc-8160-c0c19ad0fabc',
_objectVersion: '7',
_refObjectName: 'Dashboard Widgets',
ObjectID: 18836103932,
FormattedID: 'F1',
DirectChildrenCount: 2,
Name: 'Dashboard Widgets',
UserStories:
{ _rallyAPIMajor: '2',
_rallyAPIMinor: '0',
_ref: 'https://rally1.rallydev.com/slm/webservice/v2.0/PortfolioItem/Feature/18846103932/UserStories',
_type: 'HierarchicalRequirement',
Count: 2 },
_type: 'PortfolioItem/Feature' },
I'm new to rally, so any further help regardind documentation, etc, is really appreciated.
Here is a full example where Feature is queried and its UserStories collection is fetched and then hydrated.
v2.0 removed the ability to return child collections in the same response for performance reasons. Now fetching a collection will return an object with the count and the url from which to get the collection data. Separate request is needed to hydrate a collection.
This change is documented here
I do not see a question in your post, I am not sure what problem you encounter, but his code gets user stories based on feature, filtered by ('DirectChildrenCount', '>', 0)
var rally = require('rally'),
queryUtils = rally.util.query;
mySettings = {
apiKey: '_secret',
server: 'https://rally1.rallydev.com', //this is the default and may be omitted
requestOptions: {
headers: {
'X-RallyIntegrationName': 'My cool node.js program',
'X-RallyIntegrationVendor': 'My company',
'X-RallyIntegrationVersion': '1.0'
},
}
},
restApi = rally(mySettings);
function queryFeature() {
return restApi.query({
type: 'portfolioitem/feature',
fetch: ['FormattedID', 'Name', 'UserStories'],
query: queryUtils.where('FormattedID', '=', 'F7')
});
}
function queryChildren(result) {
return restApi.query({
ref: result.Results[0].UserStories,
limit: Infinity,
order: 'Rank',
fetch: ['FormattedID', 'Name'],
query: queryUtils.where('DirectChildrenCount', '>', 0)
});
}
function onSuccess(result) {
console.log('Success!', result);
}
function onError(errors) {
console.log('Failure!', errors);
}
queryFeature()
.then(queryChildren)
.then(onSuccess)
.fail(onError);
Related
I am making an API that shows a collection of ads with MongoDB and Node.js
I need to display the list of collection tags in a JSON string.
Example: 'home', 'mobile', 'motor'
This is the API initializer code:
const readline = require('readline');
const Product = require('./models/Product');
async function main() {
const advance = await question('Are you sure to continue with the deletion of the database? (yes or no) ')
if (!advance) {
process.exit();
}
const connection = require('./lib/connectMongoose')
await initProducts();
connection.close();
}
async function initProducts() {
const deleted = await Product.deleteMany();
console.log(`Remove ${deleted.deletedCount} products.`);
const inserted = await Product.insertMany([
{name: 'Table', sale: true, price: 150, photo: 'Table.png', tags: ['home']},
{name: 'Iphone 13 pro', sale: false, price: 950, photo: 'Iphone 13 pro.png', tags: ['mobile']},
{name: 'Car Mini Cooper', sale: true, price: 1000, photo: 'Car Mini Cooper.png', tags: ['motor']}
]);
console.log(`Create ${inserted.length} products.`)
}
main().catch(err => console.log('Hubo un error', err))
function question(text) {
return new Promise((resolve, reject) => {
const interface = readline.createInterface({
input: process.stdin,
output: process.stdout
});
interface.question(text, answer => {
interface.close();
if (answer.toLowerCase() === 'yes') {
resolve(true);
return;
}
resolve(false);
})
})
}
I need to find a MongoDB method that allows me to show when the API route calls the list that shows in JSON format all the tags that the collection includes
If I've understood correctly, one option is $unwind the tags array to get all tags as strings and be able to $group adding to a set to avoid duplicates.
db.collection.aggregate([
{
"$unwind": "$tags"
},
{
"$group": {
"_id": null,
"tags": {
"$addToSet": "$tags"
}
}
}
])
I think this works but $unwind and $group the entire collection is not always a good idea. It may be a slow process.
Example here
I want to make a telegram bot about algebra. I need to send code to http://api.mathjs.org/v4/?expr=2*(7-3) after the expr part. I want to send numbers with inline query, but how can I do it?
The original example uses context object deconstruction, which doesn't seem to work and spits out error: TypeError: Cannot read property 'assert' of undefined
Here is the code without object deconstruction, which works for me (I've made it superfluously verbose for better understanding ):
bot.on('inline_query', async (ctx) => {
const offset = parseInt(ctx.inlineQuery.offset) || 0;
let items = [];
for(var i = 0; i < 100; i++) {
items.push({ title: 'Item '+i, desc: 'item '+i+' desc', id: '0000'+i, moreinfo: 'More info about item'+i+', mucho importante information'})
}
let results = items.slice(offset, offset+10).map((item) => ({
type: "article",
id: item.id,
title: item.title,
description: item.desc,
input_message_content: {
message_text: '*'+item.title+'*\n'+item.desc,
parse_mode: 'Markdown'
},
reply_markup: {
inline_keyboard: [
[{ text: 'More info', callback_data: 'moreinfo' }]
]},
hide_url: true,
url: 'http://www.domain.se/'+item.id,
}));
console.log('hello');
let ourReturn = ctx.answerInlineQuery(results, {is_personal: true, next_offset: offset+results.length, cache_time: 10});
return ourReturn;
});
Here is the article that helped me to solve this problem.
You can find multiple examples of Telegraf usage at https://github.com/telegraf/telegraf/tree/develop/docs/examples
Here is an example of a bot utilizing an inline query
I am trying to sync data from one system to another using a NodeJS Application to help track the sync process transactionally. Below is a modified snippet of what I am trying to accomplish. The interval time and tap is just for logging and testing purposes. So far it is doing what I am trying to do but doesn't feel right or seem correct.
The overall diagram of what I am trying to do:
1. Note: The far left items are kick starter actions (UI/End User Action & an internal interval to call getActiveCourses)
*Edit: My main question is, how do I use RxJS Observables to create a queue management-state-like object allowing for the individual items to self-sync and self-dispose once completed.
*Edit 2: I am not asking for help on designing an entire system but in how properly setup an Observable that can manage a course listing, then have another Observable monitor it to filter out specific courses. On those filtered courses, each course should be an observable of its own to sync data pertaining to itself then self unsubscribe.
Should the main getActiveCourses be a BehaviorSubject or a Subject?
Keeping current value or only push upon retrieving the latest group
of courses.
Should the processCourses be a Subject? I my thought was that this would be a capture of the latest subscriptions to the filteredCourses and process them like a queue. I am having a hard time detecting if this is true.
Lastly, do I need to create a third Observable to truly capture and isolate the actual courses to be synced?
It was pointed out to me that having a tap and subscribe instead of subscription is not a reliable way of performing this task. A suggestion was to use concatMap() but I am finding that to be still linear and I would rather to have the data be more organic in flow as some data may need more updates than others.
Please note that most of the application is done and the only thing I am really trying to do is efficiently write the code.
const courseProcessQueue = new BehaviorSubject({});
const processQueuedCourses = new Subject();
processQueuedCourses
.subscribe((data: any) => // problematic area!!!!!!
data.pipe(
filter((d: any) => d.length <= 2),
tap(loadCourseEnrollments),// end of problematic area
)
.subscribe(d => console.log('[processQueuedCourses]', d))
);
processQueuedCourses.next(courseProcessQueue);
interval(500)
.pipe(
map(loadActiveCourses),
flatMap(data => data),
map(courseSyncQueue),
)
.subscribe(() => console.log('pushed new course queue state'));
courseProcessQueue.subscribe((courses: any) =>
console.log('[courseProcessQueue]', courses.length)
);
function loadActiveCourses() {
let limit = Math.floor(Math.random() * (10 - 1 + 1) + 1)
return from(getActiveCourses('externalId id', limit));
}
function courseSyncQueue(courses: any) {
courseProcessQueue.next(courses);
}
async function loadCourseEnrollments(courses: any) {
console.log('PROCESSING ENROLLMENTS!!!!!!!!')
courses.map(course => console.log('PROCESSED:', course.externalId));
}
Output:
[getActiveCourses()]
PROCESSING ENROLLMENTS!!!!!!!!
PROCESSED: Course-001
PROCESSED: Course-002
[ processQueuedCourses] [ { id: '1',
externalId: 'Course-001' },
{ id: '2',
externalId: 'Course-002' } ]
[courseProcessQueue] 2
pushed new course queue state
[getActiveCourses()]
[courseProcessQueue] 8
pushed new course queue state
[courseProcessQueue] 9
pushed new course queue state
So after researching flatten strategies, I was able to create a working solution to the problem I was facing. The overall problem was that I was subscribing to an observable within a observable. This was basically a mergeMap strategy as was not was I was going for. Turns out I needed to do a switchMap strategy along with using the scan operator to check the incoming data against the current/accumulated data.
This is the article that really helped me understand what was going on: A Super Ninja Trick To Learn RxJS’s “switchMap”, “mergeMap”, “concatMap” and “exhaustMap”, FOREVER!
import * as mongoose from 'mongoose';
import * as bluebird from 'bluebird';
import { interval, from, BehaviorSubject, timer } from 'rxjs';
import { map, switchMap, scan } from 'rxjs/operators';
import { mongooseConfig } from './config';
import { getActiveCourses, getCourseById } from './logic/data-service';
import { Course } from './models/course';
// import { inspect } from 'util';
(<any>mongoose).Promise = bluebird;
mongoose.connection.once('open', () => {
mongoose.connection.on('error', (err: any) => { console.log(err); });
});
mongoose.connect(mongooseConfig.database, mongooseConfig.options);
/**
* Test for updates to a course
*/
Course.findOneAndUpdate(
{id: '_69107_1'},
{'availability.available': 'N'},
{upsert: true}
)
.select('-_id id externalId updatedAt')
.exec()
.then(console.log);
Course.findOneAndUpdate(
{id: '_69107_1'},
{'availability.available': 'Y'},
{upsert: true}
)
.select('-_id id externalId updatedAt')
.exec()
.then(console.log);
/**
* END TEST
*/
/**
* getActiveCoursesFromDB => from() => switchMap() => BehaviorSubject()
*/
// just use 3 seconds for now, update to 15 * (60 * 1000) later
const refresh15MinutesAgo = 3000;
const filteredCourses = new BehaviorSubject([]);
// get the active courses that are stale test
interval(1000).pipe(
map(() => from(getActiveCourses({select: '-_id id externalId updatedAt', limit: 3}))),
switchMap(courses => courses),
map(courses => courses.filter(course => course.updatedAt < Date.now() - refresh15MinutesAgo))
).subscribe(setFilteredCourses);
filteredCourses.pipe(
scan((acc, curr) => {
// ensure there is a current obj
if (curr) {
// if the current obj is not an array then check it directly
if (!Array.isArray(curr)) {
if (!containsObject(curr, acc)) {
return [...new Set([...acc, curr])];
}
} else {
// otherwise process only the ones that are not in the current stack
return [...new Set([...acc, ...curr.filter(c => !containsObject(c, acc))])];
}
}
// either first iteration or curr was undefined
return acc;
})
).subscribe(logOut);
//test to inject a course and update, simulating a front end override call
timer(6000).pipe(
map(() => from(getCourseById('4', {select: '-_id id externalId updatedAt'}))),
switchMap(c => c)
).subscribe(setFilteredCourses);
// helper function to push data into the BehaviorSubject
function setFilteredCourses(data) {
filteredCourses.next(data);
}
function logOut(output: any) {
console.log('[logOut]', output);
}
// helper function to test if an object is in our list
function containsObject(obj, list) {
const ids = list.filter(c => c.id === obj.id);
if (ids.length > 0) {
return true;
}
return false;
}
Output:
//creating an update for test
{ id: '3',
externalId: 'Course-003',
updatedAt: 2018-09-11T02:00:39.986Z }
{ id: '3',
externalId: 'Course-003',
updatedAt: 2018-09-11T02:01:31.710Z }
//time passes
[getActiveCourses()]
[logOut] [ { id: '1',
externalId: 'Course-001',
updatedAt: 2018-09-07T16:45:58.295Z },
{ id: '2',
externalId: 'Course-002',
updatedAt: 2018-09-07T16:45:58.295Z } ]
[logOut] [ { id: '1',
externalId: 'Course-001',
updatedAt: 2018-09-07T16:45:58.295Z },
{ id: '2',
externalId: 'Course-002',
updatedAt: 2018-09-07T16:45:58.295Z },
{ id: '3',
externalId: 'Course-003',
updatedAt: 2018-09-11T02:01:31.710Z } ]
//more time passes, 4 was injected into the stack/queue
[getActiveCourses()]
[logOut] [ { id: '1',
externalId: 'Course-001',
updatedAt: 2018-09-07T16:45:58.295Z },
{ id: '2',
externalId: 'Course-002',
updatedAt: 2018-09-07T16:45:58.295Z },
{ id: '3',
externalId: 'Course-003',
updatedAt: 2018-09-11T02:01:31.710Z },
{ id: '4',
externalId: 'Course-004',
updatedAt: 2018-09-07T16:45:58.295Z } ]
const commando = require('discord.js-commando');
const config = require(__dirname + "/../../config/settings.json");
const google = require('googleapis');
const ytdl = require('ytdl-core');
const youtube = google.youtube({
version: 'v3',
auth: config.gapi
});
class PlayCommand extends commando.Command {
constructor(client) {
super(client, {
name: 'play',
group: "music",
memberName: 'play',
description: 'Plays music from youtube',
});
}
async run(message, args) {
youtube.search.list({
maxResults: '5',
part: 'snippet',
type: 'video',
q: args
},
function (err, request) {
if (err) {
console.error('Error: ' + err);
}
if (request) {
console.log(request)
}
});
}
}
module.exports = PlayCommand
Here's my play command so far, the problem is whenever i type >play args,
I get this:
{ kind: 'youtube#searchListResponse',
etag: '"S8kisgyDEblalhHF9ooXPiFFrkc/C1WPrLHrbjq3iYDhZhQ4QT0ahKc"',
nextPageToken: 'CAUQAA',
regionCode: 'PL',
pageInfo: { totalResults: 1000000, resultsPerPage: 5 },
items:
{ kind: 'youtube#searchResult',
etag: '"S8kisgyDEblalhHF9ooXPiFFrkc/HI2NmsFJ4wN7_PF5bDbDq-1kshA"',
id: [Object],
snippet: [Object] },
Only the id of the video is important for now, but as you can see it returns as [Object]
I really don't know what to do, any help would be great.
When logging the results of YouTube-Search, it will usually not show any deep objects. Due to this, the object does exist, you just need to log it in specific. For example,
console.log(request.items[0]);
This should output the entire first result, including the object found. Another good method to get results quickly is the YouTube-Search module. From my experience with the Google API, it can be quite weird sometimes and change constantly, so using modules helps out a lot with keeping up with Google's changes.
I'm making calls to a mongodb database - pulling data out... reading it, and then making further requests based on that data. Once all the data has been received, I wish to process it.
I've been using Q.promises library but don't know what I'm doing. I thought that q.all would only trigger once everything has completed? However, my processPlaylist function runs twice. I've commented the code below:
Thanks,
Rob
var PlaylistCollection = require('./models/playlist');
var AssetCollection = require('./models/asset');
var screenID = '############';
var playerData = [];
// array continaing playlistys which have been synced
var alreadySynced = [];
// Process our playlist once downloaded
var processPlaylist = function (playerData) {
console.log('----Processing Playerlist-----')
console.log(playerData);
// DO STUFF
}
// Get playlist by id. Return playlist Data
var getSubLists = function (id) {
return PlaylistCollection.findById(id);
}
// Get sub-playlist function
function getSubListRecursive(id) {
return getSubLists(id).then(function (playlist) {
// store all our returned playlist data into a playlist array
playerData.push(playlist)
// an Array to keep tabs on what we've already pulled down
alreadySynced.push(playlist.id)
// get all our playlist.resources, and only return those which are unique
var playlistResources = _.uniq(playlist.resources, 'rid');
// console.log('Playlist Resources: ', playlistResources)
// console.log(alreadySynced);
var sublists = _.pluck(_.filter(playlistResources, { 'type': 'playlist' }), 'rid');
// remove playlists which have already been synced. We don't want to pull them down twice
sublists = _.difference(sublists, alreadySynced);
// console.log('sublists: ', sublists)
// Get the next playlist and so on...
var dbops = sublists.map(function (sublist) {
// console.log(sublist)
return getSubListRecursive(sublist)
});
q.all(dbops).then(function () {
console.log('All Done - so process the playlist')
return processPlaylist(playerData);
});
})
}
// Trigger the whole process..... grab our first playlist / ScreenID
getSubListRecursive(screenID);
and I get the following output:
----Processing Playerlist-----
[ { _id: 554d1df16ce4c438f8e2225b,
title: 'list 1',
__v: 29,
daily: true,
endTime: '',
startTime: '',
resources:
[ { rid: '55650cebef204ab70302a4d9',
title: 'list 4',
type: 'playlist' },
{ rid: '554d1df16ce4c438f8e2225b',
title: 'list 1',
type: 'playlist' } ] },
{ _id: 55650cebef204ab70302a4d9,
title: 'list 4',
__v: 1,
daily: false,
endTime: '',
startTime: '',
resources:
[ { rid: '55650647ef204ab70302a4d8',
title: 'list 3',
type: 'playlist' } ] } ]
All Done - so process the playlist
----Processing Playerlist-----
[ { _id: 554d1df16ce4c438f8e2225b,
title: 'list 1',
__v: 29,
daily: true,
endTime: '',
startTime: '',
resources:
[ { rid: '55650cebef204ab70302a4d9',
title: 'list 4',
type: 'playlist' },
{ rid: '554d1df16ce4c438f8e2225b',
title: 'list 1',
type: 'playlist' } ] },
{ _id: 55650cebef204ab70302a4d9,
title: 'list 4',
__v: 1,
daily: false,
endTime: '',
startTime: '',
resources:
[ { rid: '55650647ef204ab70302a4d8',
title: 'list 3',
type: 'playlist' } ] },
{ _id: 55650647ef204ab70302a4d8,
title: 'list 3',
__v: 5,
daily: false,
endTime: '',
startTime: '',
resources:
[ { rid: '55650637ef204ab70302a4d7',
title: 'list 2',
type: 'playlist' },
{ rid: '554d1df16ce4c438f8e2225b',
title: 'list 1',
type: 'playlist' },
{ rid: '55650cebef204ab70302a4d9',
title: 'list 4',
type: 'playlist' } ] } ]
EDIT
There were a number of things wrong with what I wrote. I discussed it with a buddy of mine - who pointed out that getSubListRecursive is being invoked recursively several times so the q.all statement is being executed several times...
So I refactored...
// Get sub-playlist function
function getSubListRecursive(id) {
console.log(id)
return getSubLists(id).then(function (playlist) {
if (playlist) {
// store all our returned playlist data into a playlist array
playerData.push(playlist)
// an Array to keep tabs on what we've already pulled down
alreadySynced.push(playlist.id)
// get all our playlist.resources, and only return those which are unique
var playlistResources = _.uniq(playlist.resources, 'rid');
// console.log('Playlist Resources: ', playlistResources)
// console.log(alreadySynced);
var sublists = _.pluck(_.filter(playlistResources, { 'type': 'playlist' }), 'rid');
// remove playlists which have already been synced. We don't want to pull them down twice
sublists = _.difference(sublists, alreadySynced);
// console.log('sublists: ', sublists)
return sublists.map(function (sublist) {
// console.log(sublist)
if (sublists.length > 0) {
return getSubListRecursive(sublist)
} else {
return processPlaylist(playerData);
}
});
} else {
return processPlaylist(playerData);
}
});
}
this works. I'm basically using promises to control the flow here - which probably isn't the best way of doing it? I no longer use an all statement, and ultimately end up with array populated with all the playlist data - which I can manipulate in my processPlaylist function.
However, I've not marked the question as solved, as I'd really like to know how I can do this with Q.all (properly use promises)
Thanks,
Rob
I think you were just confused about when the entire process was finished. You need to wait until the entire recursive promise chain has resolved. I think you could use the original code with a slight change to where processPlaylist() is called:
var PlaylistCollection = require('./models/playlist');
var AssetCollection = require('./models/asset');
var screenID = '############';
var playerData = [];
// array continaing playlistys which have been synced
var alreadySynced = [];
// Process our playlist once downloaded
var processPlaylist = function (playerData) {
console.log('----Processing Playerlist-----')
console.log(playerData);
// DO STUFF
}
// Get playlist by id. Return playlist Data
var getSubLists = function (id) {
return PlaylistCollection.findById(id);
}
// Get sub-playlist function
function getSubListRecursive(id) {
return getSubLists(id).then(function (playlist) {
// store all our returned playlist data into a playlist array
playerData.push(playlist)
// an Array to keep tabs on what we've already pulled down
alreadySynced.push(playlist.id)
// get all our playlist.resources, and only return those which are unique
var playlistResources = _.uniq(playlist.resources, 'rid');
// console.log('Playlist Resources: ', playlistResources)
// console.log(alreadySynced);
var sublists = _.pluck(_.filter(playlistResources, { 'type': 'playlist' }), 'rid');
// remove playlists which have already been synced. We don't want to pull them down twice
sublists = _.difference(sublists, alreadySynced);
// console.log('sublists: ', sublists)
// Get the next playlist and so on...
var dbops = sublists.map(function (sublist) {
// console.log(sublist)
return getSubListRecursive(sublist)
});
return q.all(dbops);
});
}
// Trigger the whole process..... grab our first playlist / ScreenID
getSubListRecursive(screenID).then(function() {
console.log('All Done - so process the playlist')
return processPlaylist(playerData);
});