I'm trying to keep track within the activities list, but the pageToken is really useless!!
Just to test, I ran this (javascript) code:
function Get(Url)
{
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", Url, false );
xmlHttp.send( null );
return JSON.parse(xmlHttp.responseText);
}
pageToken = "";
do {
console.log(pageToken = (x = Get("https://www.googleapis.com/appsactivity/v1/activities?\
access_token=***ACCESSTOKEN***\
&source=drive.google.com\
&drive.ancestorId=0B8dK0Hw8TTYoX2pmOTZBcnlzVFE\
&pageSize=10\
&fields=*\
&pageToken="+pageToken)).nextPageToken);
} while (pageToken);
I get the same Activities list (x) everytime, with a different and random nextPageToken. There's not following like with the Drive.Changes API.
Should I just result to using the Changes API? I've spent 2 days trying to get this working!
Related
I am in the need of listing the users data belonging to a specific group within the organization. The documentation does not specify if this is possible. I was really hoping there could be some kind of query that would allow this. For example email in (1#domain.com,2#domain.com). However, I don't see that being possible. The only way I could think to accomplish this would be:
Get a list of all the members in the group (https://developers.google.com/admin-sdk/directory/reference/rest/v1/members/list)
Get each user data by email (https://developers.google.com/admin-sdk/directory/reference/rest/v1/users/get)
The problem with the above approach is that if a group contains 50+ members, this means that I have to make all that amount of requests, which is counter productive. Imagine how long that would take.
Any ideas? Greatly appreciate it.
Unfortunately I don’t think you can skip this two step process, but you can speed it up using batch requests. This
allows you to request up to 1000 calls in a single request. The steps would be:
Make a batch request to get all the members of all the groups you want (using members.list).
Make a batch request to get all the user info that you need using their id (using user.get).
Notice that the data in the result won’t be sorted, but they will be tagged by Content-ID.
References
Sending Batch Requests (Directory API)
Method: members.list (Directory API)
Method: users.get (Directory API)
I thought about the batching request a couple of hours after I posted the question. The problem with Node JS is that it does not has built in support for batch requests, unlike the php client library for example; Therefore, I had to spent some time implementing support for it on my own since I was not able to find any example. I'll share the solution in case it helps someone else or for my future reference.
async function getGroupMembersData(){
const groupEmail = "group#domain.com"; //google group email
const groupMembers = await getGroupMembers(groupEmail).catch(error=>{
console.error(`Error querying group members: ${error.toString()}`);
});
if(!groupMembers){ return; }
const url = "https://www.googleapis.com/batch/admin/directory_v1";
const scopes = ["https://www.googleapis.com/auth/admin.directory.user.readonly"];
const requests = [];
for(let i=0; i<groupMembers.length; ++i){
const user = groupMembers[i];
const request = {
email: user,
endpoint: `GET directory_v1/admin/directory/v1/users/${user}?fields=*`
};
requests.push(request);
}
const batchRequestData = await batchProcess(url, scopes, requests).catch(error=>{
console.error(`Error processing batch request: ${error.toString()}`);
});
if(!batchRequestData){ return; }
const usersList = batchRequestData.map(i=>{
return i.responseBody;
});
console.log(usersList);
}
//get group members using group email address
async function getGroupMembers(groupKey){
const client = await getClient(scopes); //function to get an authorized client, you have to implement on your own
const service = google.admin({version: "directory_v1", auth: client});
const request = await service.members.list({
groupKey,
fields: "members(email)",
maxResults: 200
});
const members = !!request.data.members ? request.data.members.map(i=>i.email) : [];
return members;
}
//batch request processing in groups of 100
async function batchProcess(batchUrl, scopes, requests){
const client = await getClient(scopes); //function to get an authorized client, you have to implement on your own
let results = [];
const boundary = "foobar99998888"; //boundary line definition
let batchBody = ""; const nl = "\n";
const batchLimit = 100; //define batch limit (max supported = 100)
const totalRounds = Math.ceil(requests.length / batchLimit);
let batchRound = 1;
let batchItem = 0;
let roundLimit = batchLimit;
do{
roundLimit = roundLimit < requests.length ? roundLimit : requests.length;
//build the batch request body
for(batchItem; batchItem<roundLimit; batchItem++){
const requestData = requests[batchItem];
batchBody += `--${boundary}${nl}`;
batchBody += `Content-Type: application/http${nl}`;
batchBody += `Content-Id: <myapprequest-${requestData.email}>${nl}${nl}`;
batchBody += `${requestData.endpoint}${nl}`;
}
batchBody += `--${boundary}--`;
//send the batch request
const batchRequest = await client.request({
url: batchUrl,
method: "POST",
headers: {
"Content-Type": `multipart/mixed; boundary=${boundary}`
},
body: batchBody
}).catch(error=>{
console.log("Error processing batch request: " + error);
});
//parse the batch request response
if(!!batchRequest){
const batchResponseData = batchRequest.data;
const responseBoundary = batchRequest.headers["content-type"].split("; ")[1].replace("boundary=", "");
const httpResponses = batchResponseParser(batchResponseData, responseBoundary);
results.push(...httpResponses);
}
batchRound++;
roundLimit += batchLimit;
} while(batchRound <= totalRounds);
return results;
};
//batch response parser
function batchResponseParser(data, boundary){
const nl = "\r\n";
data = data.replace(`--${boundary}--`,"");
const responses = data.split(`--${boundary}`);
responses.shift();
const formattedResponses = responses.map(i=>{
const parts = i.split(`${nl}${nl}`);
const responseMetaParts = (parts[0].replace(nl, "")).split(nl);
let responseMeta = {};
responseMetaParts.forEach(part=>{
const objectParts = part.split(":");
responseMeta[objectParts[0].trim()] = objectParts[1].trim();
});
const responseHeadersParts = parts[1].split(nl);
let responseHeaders = {};
responseHeadersParts.forEach(part=>{
if(part.indexOf("HTTP/1.1") > -1){
responseHeaders.status = part;
} else {
const objectParts = part.split(":");
responseHeaders[objectParts[0].trim()] = objectParts[1].trim();
}
});
const reg = new RegExp(`${nl}`, "g");
const responseBody = JSON.parse(parts[2].replace(reg, ""));
const formatted = {
responseMeta: responseMeta,
responseHeaders: responseHeaders,
responseBody: responseBody
};
return formatted;
});
return formattedResponses;
}
I have a million records in my table. I want to call a soap service and i need to do process in all the records in less than one hour. and besides i should update my table , insert the requests and responses in my other tables. but the code below works on less than 10 records every time i run my app.
I know My code is wrong,, I want to know what is the best way to do it.
static async Task Send( )
{
var results = new ConcurrentDictionary<string, int>();
using (AppDbContext entities = new AppDbContext())
{
var List = entities.Request.Where(x => x.State == RequestState.InitialState).ToList();
Parallel.ForEach(Enumerable.Range(0, List.Count), async index =>
{
var selected = List.FirstOrDefault();
List.Remove( selected );
var res1 = await DoAsyncJob1(selected); ///await
// var res = CallService(selected);
var res2 = await DoAsyncJob2(selected); ///await
var res3 = await DoAsyncJob3(selected); ///await
// var responses = await Task.WhenAll(DoAsyncJob1, DoAsyncJob2, DoAsyncJob3);
// results.TryAdd(index.ToString(), res);
});
}
}
static async Task<int> DoAsyncJob1(Request item)
{
using (AppDbContext entities = new AppDbContext())
{
var bReq = new BankRequest();
bReq.Amount = Convert.ToDecimal(item.Amount);
bReq.CreatedAt = DateTime.Now;
bReq.DIBAN = item.DIBAN;
bReq.SIBAN = item.SIBAN;
entities.BankRequest.Add(bReq);
entities.SaveChanges();
}
return item.Id;
}
static async Task<int> DoAsyncJob2(Request item)
{
using (AppDbContext entities = new AppDbContext())
{
}
return item.Id;
}
static async Task<int> DoAsyncJob3(Request item)
{
using (AppDbContext entities = new AppDbContext())
{
}
return item.Id;
}
Maybe the below lines are wrong :
var selected = List.FirstOrDefault();
List.Remove( selected );
Thanks in advance..
First, it is a bad practice to use async-await within Parallel.For - you introduce only more load to the task scheduler and more overhead.
Second, you are right:
var selected = List.FirstOrDefault();
List.Remove( selected );
is very, very wrong. Your code will behave in a totally unpredictable way, due to the race conditions.
Using a HTTP GET request, how would you only get the classes that are active. Could you add a parameter to the Google API URL that only returns a list of active classes? Or do you have to search through the returned array and delete any classes are archived using a for loop?
var classroom = new XMLHttpRequest();
var accessToken = localStorage.getItem('accessToken');
classroom.open('GET',
'https://classroom.googleapis.com/v1/courses');
classroom.setRequestHeader('Authorization',
'Bearer ' + accessToken);
classroom.send();
classroom.onload = function () {
if (classroom.readyState === classroom.DONE) {
if (classroom.status === 200) {
var response = JSON.parse(classroom.response);
vm.classes = response.courses;
console.log(response);
for (var i = 0; i < response.courses.length; i++){
var courses = response.courses[i];
console.log(courses.name);
}
} else {
console.log("Error Unknown");
}
}
};
Any help would be much appreciated.
Thanks!
There's no filter option yet like with User objects. (That's documented for at least as far as I can tell). So yes you'll have to pull all of the courses and then just filter out the archived courses. https://developers.google.com/classroom/reference/rest/v1/courses there's a CourseState section that lists the 5 possible states a course can be in. [COURSE_STATE_UNSPECIFIED, ACTIVE, ARCHIVED, PROVISIONED, DECLINED]
Reading through the docs, courses.list returns a list of courses that the requesting user is permitted to view. It does not state a direct way of retrieving active classes only. You may have to resort to your said implementation.
Try this:
function get_courses(student) {
var optionalArgs = {
studentId: student
};
var response = Classroom.Courses.list(optionalArgs);
var courses = response.courses;
var active_courses = [];
if (courses && courses.length > 0) {
for (i = 0; i < courses.length; i++) {
var course = courses[i];
if(course.courseState == "ACTIVE"){
active_courses.push(course);
Logger.log('%s (%s)', course.name, course.id);
}
}
} else {
Logger.log('No courses found.');
}
return active_courses;
}
For data visualization request in my application, I am sending multiple AJAX requests to a servlet in order to get the data in chunks and on callback of each request, the data received is rendered over map.
For this request, I am trying to calculate:
Request Time (how much total time it took for client to get data from server)
Processing Time (how much total time it took for client to render the data on client side)
In order to do this, I am capturing start time of each request before sending it to server (using jquery "beforeSend") and "onSuccess" event of each request, the end time is captured.
Once the all requests are completed, I am deducting the "start time" of first request from the "end time" of last request in order to calculate the total time the client took for fetching records from server. (Similarly for Processing Time)
But somehow my calculation doesn't produce correct results. Could any one please provide me some suggestions for this issue?
for explaining my question in more better way:
var dataProviderRequestsStartTime = [];
var dataProviderRequestsEndTime = [];
var dataParsingStartTime = [];
var dataParsingEndTime = [];
getResults(ids);
var getResults = function(totalIds) {
for(var i=0; i<10; i++;) {
requestResultForOneChunk(totalIds[i]);
}
};
var requestResultForOneChunk = function(streetIds) {
$.ajax({
beforeSend: function() {
var requestStartTime = new Date().getTime();
dataProviderRequestsStartTime.push(requestStartTime);
},
type : 'POST',
url : "myServlet",
contentType : "application/x-www-form-urlencoded",
data : {
"ids" : streetIds,
},
success : function(response) {
//Request Finished
var dataProvideRequestEndTime = new Date().getTime();
dataProviderRequestsEndTime.push(dataProvideRequestEndTime);
addFeaturesToMap(response);
},
error : function(x, e) {
alert("Something went wrong in the request" + e);
}
});
};
var addFeaturesToMap = function(measurements) {
//Parsing Started
var featureParsingStartTime = new Date().getTime();
dataParsingStartTime.push(featureParsingStartTime);
doParsing(measurements);
//Parsing Finished
featureParsingEndTime = new Date().getTime();
dataParsingEndTime.push(featureParsingEndTime);
};
$("#loading").bind(
"ajaxStart",
function(options) {
ajaxStartTime = options.timeStamp;
}).bind("ajaxStop", function(options) {
var ajaxEndTime = options.timeStamp;
var totalTime = (ajaxEndTime - ajaxStartTime);
calculateTimeBreakDown();
});
var calculateTimeBreakDown = function() {
var totalValues = dataProviderRequestsEndTime.length;
var lastValueIndex = totalValues - 1;
// Request Time calculation
var endTimeOfLastRequest = dataProviderRequestsEndTime[lastValueIndex];
var startTimeOfFirstRequest = dataProviderRequestsStartTime[0];
var totalRequestTime = (endTimeOfLastRequest - startTimeOfFirstRequest);
// Parsing Time Calculation
var endTimeOfLastParsing = dataParsingEndTime[lastValueIndex];
var startTimeOfFirstParsing = dataParsingStartTime[0];
var totalParsingTime = (endTimeOfLastParsing - startTimeOfFirstParsing);
};
Finally, I have requestTime(totalRequestTime) and parsingTime(totalParsingTime). But the problem is adding these both doesn't produce value near to total time which is calculated using ajax start and stop.
look at the .ajaxStart() and .ajaxStop() events for "total time", (<- those are also great for progressbars)
http://api.jquery.com/ajaxStart/
http://api.jquery.com/ajaxStop/
and .ajaxSend() and .ajaxComplete() events for "cumulative time" calculations.
http://api.jquery.com/ajaxSend/
http://api.jquery.com/ajaxComplete/
look at this code:
var totalTime = null;
var cachedTime = null;
function alertLoadingTime() {
if(!totalTime) return;
var loadingTime = totalTime / 1000;
console.log("loaded " + loadingTime + " seconds");
}
function timingStart() {
cachedTime = new Date;
}
function timingEnd() {
var endTime = new Date;
totalTime += endTime - cachedTime;
cachedTime = null;
alertLoadingTime();
}
$(document).ajaxStart(timingStart);
$(document).ajaxStop(timingEnd);
note that it will only account for time spent doing ajax calls and won't include the initial page loading time.
to time the parsing:
use the same functions as before but change totalTime to totalParsingTime. (Note: you can achieve this by changing totalTime to reference some other variable)
Call timingStart() right before you append the result of the ajax call to the dom tree.
Have the server add timingEnd() to the end of every response.
totalTime will then be set to the time it took to add everything to the DOM tree.
solution for you would to rely on jquery ajax callback methods
ajaxStart : Register a handler to be called when the first Ajax request begins.
ajaxStop : Register a handler to be called when all Ajax requests have completed including success and error callbacks
I have used the below code snippet in my application and it works perfectly fine to report page rendering time including ajaxs.
var startTime = 0,endTime = 0;
$(document).ajaxStart(function(){
startTime = new Date();
});
$(document).ajaxStop(function(){
endTime = new Date();
console.log("total time required : ",endTime - startTime); //Total time in milliseconds including time spent in success or error callbacks
});
I am trying to implement a customize Magento Advanced Search, using jQuery and Ajax.
The search works something like this:
1) There are 2/3 options to select size
2) 6 options to narrow down search result
3) A text box to enter keywords etc
There is no submit button, that means, search starts when user clicks on any size and/or Options or enter more than 3 char in search text box, search starts, an ajax request is sent to server.
If user clicks on another option or say type in another character, I use to abort previous search using search_request.abort() and set previous search to null.
When you search for first couple of times, it works fine, search returns result and if no result is found, proper message is displayed.But, after after couple of requests, it starts failing. Like if I click on options in short interval (I mean, search is fired frequently)). Some times, it fails without any reason and sometimes it fails, and when I click on the same option a couple of seconds later, it display the results.
Sometimes, when the search fails or there is no result.
It displays the incomplete message in result area like:
Where as it should look like :
Search when it returns result:
Search when it fails
My question is:
1) do you think it might be failing because too many requests are submitted too frequently? I mean is there something related to Magento settings?
2) What can I do to correct this?
Here is the jQuery code:
<script type="text/javascript">
function search(textfield)
{
j('#slides').html("<img src='<?php echo Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB); ?>skin/frontend/luckybloke/luckybloke/images/ajax-loader.gif' class='no-results-found' style='width:100px !important; padding-top:10px !important;text-align:center;margin-left: 43%;' />");
var form = j("#form-validate");
var action = form.attr("action");
var fieldAdded = Array();
var searchData = '';
var searchVal = '';
var sizeSelected = '';
var searchArray = new Array();
var searchCtr = 0;
var tmp = new Array();
if(typeof search_request =='object' && search_request!=null){
search_request.abort();
search_request = null;
j('#slides').html("<img src='<?php echo Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB); ?>skin/frontend/luckybloke/luckybloke/images/ajax-loader.gif' class='no-results-found' style='width:100px !important; padding-top:10px !important;text-align:center;margin-left: 43%;' />");
}
j('.input-search').each(function(index,domEle){
var eleID = "#"+j(domEle).attr("id");
var curEle= j(domEle).attr("name");
fieldAdded.push(curEle.replace('[]',''));
////consloe.log(eleID);
if((j(eleID).val()!='' || !jQuery.inArray(curEle,fieldAdded))&& eleID!='#name'){
if(searchVal==''){
searchVal=searchVal+j(eleID).val();
}
if(searchData==''){
searchData = searchData+""+curEle+"="+j(eleID).val();
}else{
searchData = searchData+"&"+curEle+"="+j(eleID).val();
}
searchArray[searchCtr] = j(eleID).val();
searchCtr++;
}
//add description field to search query
tmp[curEle] = j(eleID).val();
}
});
if(searchVal==''){
}
if(j("#name").val()=='brand, style, keyword'){
var val = '';
}else{
val = j("#name").val();
}
searchData = searchData+"&search_keywords="+val;
//toggleFields(sizeSelected, searchArray);
search_request = j.ajax({
type:'get',
url:action,
data:searchData,
cache: false,
dataType:'html',
success:searchComplete
});
return;
}
function searchComplete(responseText, statusText, xhr, $form)
{
var no_item_msg = 'No product found matching your search criteria.';
if(statusText=='success'){
var isFound = responseText.toString().search(new RegExp(/no items found/i));
//alert(isFound+''+responseText.toString());
if((isFound>0 || responseText.length == 0) && search_request ){
j("div#slides").html("<span class='no-results-found'>"+no_item_msg+"</span>");
// j("div#slides").html("<span class='no-results-found'>"+no_item_msg+"</span>");
/*j("div#lb-product-list").block({message:no_item_msg});
setTimeout('j("div#lb-product-list").unblock()', 2000);*/
}else{
var dataToFill = j("div#lb-product-list");
var isFound = responseText.toString().search(new RegExp(/JUST_ONE/i));
if(isFound>=0){
responseText = responseText.replace(/just_one/i,'');
}else{
j.getScript("<?php echo Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB); ?>js/carousol.js", function() {});
}
dataToFill.html(j(responseText).children());
search_request = null;
}
//reset the css attribute's position value, as ui blocking seems to be affecting positioning of elements are unblocking
j("div#lb-product-list").css("position",'');
return true;
}
}
</script>