I've been trying to modify the sample dashboard widget at this location
https://learn.microsoft.com/en-us/vsts/extend/develop/add-dashboard-widget?view=vsts#part-2-hello-world-with-vsts-rest-api
However, reluctantly have to admit I simply can't understand the structure required to extend it
Near the end, it uses "load: function" and returns the outputs of a REST API call, which I can consume however I want
However, I need to make more than one different REST call, and I simply cannot figure out how to get that info usable in my function
I modified the code so it starts like this:
VSS.require(["TFS/Dashboards/WidgetHelpers", "TFS/Work/RestClient","VSS/Service", "TFS/WorkItemTracking/RestClient" ],
I then created a handle for the other call I want to make like this:
var queryClient = VSS_Service.getCollectionClient(TFS_Wit_QueryAPI.WorkItemTrackingHttpClient);
var queryResults = queryClient.getQuery(projectId, "Shared Queries/My Bugs");
However, I cannot consume the contents of queryResults - I know it's working up to a point as if I put in an invalid URL it will error as it knows it can't access anything there. If the URL is correct, no matter what I've tried - even stringify just to see what comes back - I get 'undefined' or something similar (it's definitely a valid JavaScript object)
The key seems to be right at the end when you have "load: function" except that only allows one thing to be returned? The reason I know this is if I change the function that it returns to be the one I've written rather than the one from the sample, it works fine - but the problem remains the same in that I can only process the results of one API call.
You can call more than one APIs, the code in that article is just the simple sample.
For Widget extension, you just need to return the status (e.g. Success()) in load function, so you can return status at the end of the function. For example:
var getQueryInfo = function (widgetSettings) {
// Get a WIT client to make REST calls to VSTS
return TFS_Wit_WebApi.getClient().getQuery(projectId, "Shared Queries/Feedback")
.then(function (query) {
// Create a list with query details
var $list = $('<ul>');
$list.append($('<li>').text("Query ID: " + query.id));
$list.append($('<li>').text("Query Name: " + query.name));
$list.append($('<li>').text("Created By: " + (query.createdBy ? query.createdBy.displayName: "<unknown>") ));
// Append the list to the query-info-container
var $container = $('#query-info-container');
$container.empty();
$container.append($list);
// Use the widget helper and return success as Widget Status
return true;
}, function (error) {
// Use the widget helper and return failure as Widget Status
console.log(error);
return false;
});
}
var getAnOhterQueryInfo = function (widgetSettings) {
// Get a WIT client to make REST calls to VSTS
return TFS_Wit_WebApi.getClient().getQuery(projectId, "Shared Queries/Bug")
.then(function (query) {
// Create a list with query details
var $list = $('<ul>');
$list.append($('<li>').text("Query ID: " + query.id));
$list.append($('<li>').text("Query Name: " + query.name));
$list.append($('<li>').text("Created By: " + (query.createdBy ? query.createdBy.displayName: "<unknown>") ));
// Append the list to the query-info-container
var $container = $('#query-info-container');
$container.empty();
$container.append($list);
// Use the widget helper and return success as Widget Status
return true;
}, function (error) {
// Use the widget helper and return failure as Widget Status
console.log(error);
return false;
});
}
return {
load: function (widgetSettings) {
// Set your title
var $title = $('h2.title');
$title.text('Hello World');
var r1= getQueryInfo(widgetSettings);
var r2=getAnOhterQueryInfo(widgetSettings);
if(r1==true && r2==true){
return WidgetHelpers.WidgetStatusHelper.Success();
}else{
return WidgetHelpers.WidgetStatusHelper.Failure("failed, check error in console");
}
}
Related
I have only used Iron-router vaguely, and as it stands I want to convert from a project I found very useful that used iron-router. What's the best approach to adapt this code to use Flowrouter?
CourseController = AppController.extend({
waitOn: function() {
// course ID
var courseID = this.params._id;
// Course subscriptions
return [
// Wait for the course to be available
this.subscribe("singleCourse", courseID),
];
},
data: function () {
// Return the course
return Courses.findOne();
},
onAfterAction: function () {
// Get the course ID from the URL parameters
var courseID = this.params._id;
// TODO: uncomment the following and figure out why it is generating a browser console error
// Get the course from the database
//var course = Courses.find(courseID).fetch()[0]; // select the zeroeth array item
// Set the site title for SEO
//Meta.setTitle(course.title);
}
});
So,I am trying to use the twitch API:
https://codepen.io/sterg/pen/yJmzrN
If you check my codepen page you'll see that each time I refresh the page the status order changes and I can't figure out why is this happening.
Here is my javascript:
$(document).ready(function(){
var ur="";
var tw=["freecodecamp","nightblue3","imaqtpie","bunnyfufuu","mushisgosu","tsm_dyrus","esl_sc2"];
var j=0;
for(var i=0;i<tw.length;i++){
ur="https://api.twitch.tv/kraken/streams/"+tw[i];
$.getJSON(ur,function(json) {
$(".tst").append(JSON.stringify(json));
$(".name").append("<li> "+tw[j]+"<p>"+""+"</p></li>");
if(json.stream==null){
$(".stat").append("<li>"+"Offline"+"</li>");
}
else{
$(".stat").append("<li>"+json.stream.game+"</li>");
}
j++;
})
}
});
$.getJSON() works asynchronously. The JSON won't be returned until the results come back. The API can return in different orders than the requests were made, so you have to handle this.
One way to do this is use the promise API, along with $.when() to bundle up all requests as one big promise, which will succeed or fail as one whole block. This also ensures that the response data is returned to your code in the expected order.
Try this:
var channelIds = ['freecodecamp', 'nightblue3', 'imaqtpie', 'bunnyfufuu', 'mushisgosu', 'tsm_dyrus', 'esl_sc2'];
$(function () {
$.when.apply(
$,
$.map(channelIds, function (channelId) {
return $.getJSON(
'https://api.twitch.tv/kraken/streams/' + encodeURIComponent(channelId)
).then(function (res) {
return {
channelId: channelId,
stream: res.stream
}
});
})
).then(function () {
console.log(arguments);
var $playersBody = $('table.players tbody');
$.each(arguments, function (index, data) {
$playersBody.append(
$('<tr>').append([
$('<td>'),
$('<td>').append(
$('<a>')
.text(data.channelId)
.attr('href', 'https://www.twitch.tv/' + encodeURIComponent(data.channelId))
),
$('<td>').text(data.stream ? data.stream.game : 'Offline')
])
)
})
})
});
https://codepen.io/anon/pen/KrOxwo
Here, I'm using $.when.apply() to use $.when with an array, rather than list of parameters. Next, I'm using $.map() to convert the array of channel IDs into an array of promises for each ID. After that, I have a simple helper function with handles the normal response (res), pulls out the relevant stream data, while attaching the channelId for use later on. (Without this, we would have to go back to the original array to get the ID. You can do this, but in my opinion, that isn't the best practice. I'd much prefer to keep the data with the response so that later refactoring is less likely to break something. This is a matter of preference.)
Next, I have a .then() handler which takes all of the data and loops through them. This data is returned as arguments to the function, so I simply use $.each() to iterate over each argument rather than having to name them out.
I made some changes in how I'm handling the HTML as well. You'll note that I'm using $.text() and $.attr() to set the dynamic values. This ensures that your HTML is valid (as you're not really using HTML for the dynamic bit at all). Otherwise, someone might have the username of <script src="somethingEvil.js"></script> and it'd run on your page. This avoids that problem entirely.
It looks like you're appending the "Display Name" in the same order every time you refresh, by using the j counter variable.
However, you're appending the "Status" as each request returns. Since these HTTP requests are asynchronous, the order in which they are appended to the document will vary each time you reload the page.
If you want the statuses to remain in the same order (matching the order of the Display Names), you'll need to store the response data from each API call as they return, and order it yourself before appending it to the body.
At first, I changed the last else condition (the one that prints out the streamed game) as $(".stat").append("<li>"+jtw[j]+": "+json.stream.game+"</li>"); - it was identical in meaning to what you tried to achieve, yet produced the same error.
There's a discrepancy in the list you've created and the data you receive. They are not directly associated.
It is a preferred way to use $(".stat").append("<li>"+json.stream._links.self+": "+json.stream.game+"</li>");, you may even get the name of the user with regex or substr in the worst case.
As long as you don't run separate loops for uploading the columns "DisplayName" and "Status", you might even be able to separate them, in case you do not desire to write them into the same line, as my example does.
Whatever way you're choosing, in the end, the problem is that the "Status" column's order of uploading is not identical to the one you're doing in "Status Name".
This code will not preserve the order, but will preserve which array entry is being processed
$(document).ready(function() {
var ur = "";
var tw = ["freecodecamp", "nightblue3", "imaqtpie", "bunnyfufuu", "mushisgosu", "tsm_dyrus", "esl_sc2"];
for (var i = 0; i < tw.length; i++) {
ur = "https://api.twitch.tv/kraken/streams/" + tw[i];
(function(j) {
$.getJSON(ur, function(json) {
$(".tst").append(JSON.stringify(json));
$(".name").append("<li> " + tw[j] + "<p>" + "" + "</p></li>");
if (json.stream == null) {
$(".stat").append("<li>" + "Offline" + "</li>");
} else {
$(".stat").append("<li>" + json.stream.game + "</li>");
}
})
}(i));
}
});
This code will preserve the order fully - the layout needs tweaking though
$(document).ready(function() {
var ur = "";
var tw = ["freecodecamp", "nightblue3", "imaqtpie", "bunnyfufuu", "mushisgosu", "tsm_dyrus", "esl_sc2"];
for (var i = 0; i < tw.length; i++) {
ur = "https://api.twitch.tv/kraken/streams/" + tw[i];
(function(j) {
var name = $(".name").append("<li> " + tw[j] + "<p>" + "" + "</p></li>");
var stat = $(".stat").append("<li></li>")[0].lastElementChild;
console.log(stat);
$.getJSON(ur, function(json) {
$(".tst").append(JSON.stringify(json));
if (json.stream == null) {
$(stat).text("Offline");
} else {
$(stat).text(json.stream.game);
}
}).then(function(e) {
console.log(e);
}, function(e) {
console.error(e);
});
}(i));
}
});
I'm building right now simple game with Angular JS and Parse.com cloud as my database.
My goal is in the and of the game, to store user score inside Parse cloud.
But how can i do this securly, when anyone can get access to my Parse keys, becouse they are visible in my js file, and simply recreate Parse Object with some fake data, and then store it in my database ?
ACL's it's not the point in this particular case - right now i just turn of write acl before save, to prevent users from changing they scores before save.
In my game i don't have any Parse Users - i want to all peaople play my game, without logging in.
What do you think about idea to make 'fake' user like in first answer in this post ( becouse Anonymous anonymous can't be create in JS parse SDK ), and then track the session and the user ?
Is it even helpful in my case ?
Maybe i should make some check in Cloude Code - like comparison Cookies or local storage data before saving in Parse ( it will make cheating in game harder but still possible ) ?
Below i present my whole service to show you what is all about:
angular.module('Parsedb', [])
.provider('Parsedbmanager', function() {
this.$get = function($q, $http) {
// new Parse constructor
var ParseHighScore = Parse.Object.extend('ParseHighScore');
// create new obj
var parseHighScore = new ParseHighScore();
this.parseInit = function() {
Parse.initialize('myKey', 'myKey');
};
this.setParsedb = function(newScore) {
// set val
parseHighScore.set('HighScore', newScore);
// save score to cloud
parseHighScore.save(null, {
success: function (parseHighScore) {
// protect from change saved obj
var acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(false);
parseHighScore.setACL(acl);
return parseHighScore.save();
},
error: function (parseHighScore, error) {
console.log('Failed to create new object, with error code: ' + error.message);
}
});
};
this.getParsedb = function() {
// need q to get this asynch
var deferred = $q.defer();
var query = new Parse.Query(ParseHighScore);
query.limit(5);
query.descending("HighScore");
query.find({
success: function(results) {
console.log("Successfully retrieved " + results.length + " scores.");
// resolve, if you have results
deferred.resolve(results);
},
error: function(error) {
deferred.reject(error.message);
}
});
return deferred.promise;
};
return this;
};
});
If you let the user to write to db then there will always be a situation where user can change data .. i think all you can do is, to abstract it from user
I have a EF Code-First DB, with 3 important Tables. Share, ContactDetail and Owner. The Share and the Owner Tables both have the Contact Detail as navigational property, but the Share Table has both ContactDetail and Owner as navigational properties.
I have a simple controller Get that retrieves the Shares
[HttpGet]
public IQueryable<Share> VesselShares()
{
return _cp.Context.Shares;
}
and the breeze call is:
getVesselShares = function (vId) {
var query = EntityQuery.from('VesselShares')
.expand('Owner, ContactDetail')
.where('VesselId', filterQueryOp.Equals, vId)
.orderBy(orderBy.share);
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
return vesselShares(data.results);
}
}
When I load my page, with some other data:
return ctx.getVesselById(vId, vessel).then(function () {
ctx.getVesselShares(vId).then(function() {
logger.log('Data Loaded: ' + ctx.vesselShares().length, 'shipdetail', 'Ship', true);
});
////Load all the details before activating the other child pages
//return Q.all([
// //ctx.getVesselShareOwners(vId, dbShareOwners),
// //ctx.getVesselVesselBuilders(vId),
// //ctx.getVesselAlterations(vId),
// //ctx.getVesselInsurers(vId),
// //ctx.getVesselClassifSocieties(vId),
// ctx.getVesselShares(vId)
//]).then(function () {
// //shareOwners(utils.consolidateShares(dbShareOwners()));
// logger.log('Data Loaded: ' + ctx.vesselShares().length, 'shipdetail', 'Ship', true);
// //register.shareOwners(shareOwners());
// //ctx.vesselShareOwners(shareOwners());
// ////Can only Register when all 64 Shares are occupied
// //ctx.canRegisterVessel(dbShareOwners().length === 64);
//});
});
The first call to retrieve the Shares work fine, but oddly the 2nd call and subsequent calls throw an error on the ctx.getVesselShares method.
"TypeError: object is not a function" .
I was able to track down the error to this line of code in the breeze.debug.js...
proto.getProperty = function (propertyName) {
return this[propertyName]();
};
This method is expecting a ContactDetail observable but strangely breeze doesn't come with an observable on those subsequent calls.
This error happens whenever I want to saveChanges or retrieve records on the Share Table. I'm running the latest breeze, MVC 5, Web Api 2.
Any help will be greatly appreciated, this is driving me crazy.
Thanks.
I am building a dashboard that graphs some data. Using splunk js charts to display the data.
My problem is not with the charting. But I would suspect my logic.
What is happening:
Client - requests chart data from server
Server - returns this object:
a
Object { splunkResults: Array[3],
metricHTML: "<div id="MetricOne" class="metric"><p class="lead"…s="metric">
<p class="lead">Metric Three</p></div>", metricID: Array[3]}
Client - model.js calls view.insertMetricContainers via callback and inserts object.metricHTML into the correct container
Client - model.js calls view.graphsReady in for loop via callback and creates a chart and inserts it into the correct metric div based on object.metricID[i]
Client - charts are displayed
My problem is that only one of the charts is being displayed, the first one.
If you look at this function in view.js:
splunkjs.UI.ready(chartToken, function() {});
I have a console.log("test"); Which is only printed once.
What is my mistake here?
Let me know if I can provide any more information, I have tried a few approaches and have run out of ideas.
Model.js
dashboard.getSystemMetrics = function(callback) {
var url = "/services/systemMetrics/?";
if (dashboard.url[1]) {
url = url + dashboard.url[1];
}
if (dashboard.limit) {
url = url + "&limit=" + dashboard.limit;
}
$.get(url, function(response) {
callback(response.metricHTML, null);
for (var i = 0; i < response.splunkResults.length; i++) {
console.log(i);
callback(null, response.splunkResults[i], response.metricID[i]);
}
});
};
View.js
view.graphsReady = function (data, id) {
console.log(id);
var chart = null;
var chartToken = splunkjs.UI.loadCharting('/javascripts/splunk.ui.charting.js', function() {
// Once we have the charting code, create a chart and update it.
chart = new splunkjs.UI.Charting.Chart($("#" + id), splunkjs.UI.Charting.ChartType.AREA, false);
});
splunkjs.UI.ready(chartToken, function() {
chart.setData(data, {
"chart.stackMode": "stacked",
"legend.placement": "top",
"axisTitleX.text": "Time",
"axisTitleY.text": "Data"
});
chart.draw();
});
};
Controller.js
$(function(){
// Grab user params
dashboard.url = window.location.href;
dashboard.url = dashboard.url.split("/?");
dashboard.limit = "day";
// Get Service Statuses
//dashboard.getQuickStatus(controller.quickStatusReady);
// TODO listener events
dashboard.getSystemMetrics(controller.systemMetricsReady);
});
controller.systemMetricsReady = function(html, data, id) {
if (html) {
view.insertMetricContainers(html);
} else if (data && id) {
view.graphsReady(data, id);
}
};
If you would like to suggest a better question title, please by all means go ahead.
EDIT - i have checked the logs yes. No errors, I wouldn't ask the question if I had errors.
I did a console.log in the set data function (view.js) and it was only logged once not 3 times like it should have. Although the function graphReady is called 3 times
UPDATE - I have found in the view.js code that this function is only run once. Despite the function view.graphsReady being called 3 times.
var chartToken = splunkjs.UI.loadCharting('/javascripts/splunk.ui.charting.js', function() {
// Once we have the charting code, create a chart and update it.
chart = new splunkjs.UI.Charting.Chart($("#" + id), splunkjs.UI.Charting.ChartType.AREA, false);
console.log(chart);
});
console.log(chartToken);
So, chartToken is printed 3 times. chart however is only printed once.