When is Microsoft Graph API finished calculating in Excel? - microsoft-graph-api

I can see how to trigger the calculation of a spreadsheet using Microsoft Graph API here...
https://learn.microsoft.com/en-us/graph/api/workbookapplication-calculate?view=graph-rest-1.0&tabs=http
But, when I pull the results from the calculations they don't seem to be updated. However, I pull a second or third time, it is usually updated.
I assume this means that calculation hasn't finished b/c of the size of the file or complexity of the calcs.
However, being asynchronous, I'm not finding any way to check to see when the calculations have finished.
Any idea how to do this?
UPDATE 1:
This is the code I'm (now) using to create the session (per #UJJAVAL123-MSFT)
var persistChanges = false;
var wrkbk = await graphClient.Me.Drive.Items[strItemId].Workbook
.CreateSession(persistChanges)
.Request()
.PostAsync();
This will give me a value for 'id' like this...
cluster=US5&session=15.SN3PEPF000074ED1.A82.1.V24.7737Nwad4oPuVafaO%2fpAkiay14.5.en-US5.en-US26.10037ffe965d2cf2-Unlimited1.A1.N16.16.0.12904.3505114.5.en-US5.en-US1.V1.N0.1.A&usid=1b230e8f-3bd0-cdaa-2da6-47db74154075
I'm not exactly sure how/where to use this, or whether I use the entire thing (it looks like a query string) or if I'm supposed to parse and pull out one of the values...
cluster=US5
session=15.SN3PEPF000074ED1.A82.1.V24.xxxxxxxxxxxxxxxxx%2fpAkiay14.5.en-US5.en-US26.10037ffe965d2cf2-Unlimited1.A1.N16.16.0.12904.3505114.5.en-US5.en-US1.V1.N0.1.A
usid=1b230e8f-3bd0-cdaa-2da6-xxxxxxxxxxxx
and this is the code i'm using to trigger the calculation, but not sure how to connect the two...
var calculationType = "FullRebuild";
await graphClient.Me.Drive.Items[strItemId].Workbook.Application
.Calculate(calculationType)
.Request()
.PostAsync();
Also, I'm seeing that it is possible to create, refresh and close a session, but not entirely sure how to check on a specific async process inside that session.
Here is the code I'm using to check a specific range for a value, not sure where we pass the session-id here either...
var result = await graphClient.Me.Drive.Items[strItemId].Workbook.Worksheets[strSheetName]
.Range(strRangeName)
.Request()
.GetAsync();
UPDATE 2:
I can run an API call (presumably) in the same session successfully by passing the workbook-session-id (which is the ENTIRE string shown above) and I get the expected 204 No Content response. However, it is not clear from the c# Code Snippet in the Microsoft Graph Explorer how to pass the workbook-session-id in the request.
Here is the code it provides...
GraphServiceClient graphClient = new GraphServiceClient( authProvider );
await graphClient.Me.Drive.Items["{item-id}"].Workbook.Application
.Calculate(null)
.Request()
.PostAsync();
So the question remains, how can I do a PostAsync or GetAsync and reference the workbook-session-id?
This code does NOT give me an error...
await graphClient.Me.Drive.Items[strItemId].Workbook.Application
.Calculate(calculationType)
.Request()
.Header("workbook-session-id",wrkbk.Id)
.PostAsync();
So now, the question is WHEN do I get the workbook-session-id? Do I get it when I initially open the workbook and then pass it to every call?

You should create a session and pass the session Id with each request. The presence of a
session Id in the requests ensures that you are using the Excel API in the most efficient
way possible.
Check here for API call to get a session

So after a decent amount of testing, i figured it out.
The answer is that you use the CreateSession method (https://learn.microsoft.com/en-us/graph/api/workbook-createsession?view=graph-rest-1.0&tabs=http) to get the workbook info, and you set the persistChanges setting, then you get back info about the workbook session.
Like this...
using Microsoft.Graph;
// strItemId = the id from the microsoft graph api of the item
// strUserId = the id of the user from the microsoft graph api (note: must have permissions set correctly)
public static async Task<WorkbookSessionInfo> GetWorkbookSessionId(string strItemId, string strUserId)
{
// true = you can see changes in the workbook
// false = don't update the workbook, just do calculations
var persistChanges = true;
try
{
var wrkbk = await graphClient.Users[strUserId].Drive.Items[strItemId].Workbook
.CreateSession(persistChanges)
.Request()
.PostAsync();
var result = wrkbk;
return result;
}
catch (Exception ex)
{
Console.WriteLine($"Error getting items: {ex.Message}");
return null;
}
}
And you are returned a WorkbookSessionInfo object, which includes the SessionId for use in subsequent calls. This way, it keeps all your calls in the same session!
https://learn.microsoft.com/en-us/graph/api/resources/workbooksessioninfo?view=graph-rest-1.0

Related

Microsoft Graph API SDK for SharePoint to query a file

I try to use Graph API SDK to query a file in a SharePoint site
var site = await graphClient.Sites["myDomain"]
.SiteWithPath("relativePath").Request()
.GetAsync().ConfigureAwait(false);
var drive = await graphClient.Sites["myDomain]
.SiteWithPath("relativePath").Lists["mylib"].Drive
.Request().GetAsync().ConfigureAwait(false);
var file = await graphClient.Sites[site.Id]
.Drives[drive.Id].Root.ItemWithPath("/folder1").Children["myfile.txt"]
.Request().GetAsync().ConfigureAwait(false);
This is working and I get the file.
I try to combine the three steps into one,
var file = await graphClient.Sites["myDomain"]
.SiteWithPath("relativePath").Lists["mylib"].Drive
.Root.ItemWithPath("/folder1").Children["myfile.txt"]
.Request().GetAsync().ConfigureAwait(false);
But it gives Bad Request error. What's wrong? What is the best way to do this?
The navigation you are using is not accepted by Graph.
As per the get files docs, you need the site-id.
# Valid
GET /sites/mydomain.sharepoint.com:/relativePath/lists/mylib/drive
# Invalid addition to above url
GET /sites/mydomain.sharepoint.com:/relativePath/lists/mylib/drive/root:/myfile.txt:
If you don't have the site id, you can expand the list relationship in the get list drive call and use the site-id to request for the file. This will be two requests instead.
var drive = await graphServiceClient
.Sites["mydomain.sharepoint.com"]
.SiteWithPath(relativePath)
.Lists["mylib"]
.Drive
.Request()
.Expand("list")
.GetAsync()
.ConfigureAwait(false);
var file = await graphServiceClient
.Sites[drive.List.ParentReference.SiteId]
.Drives[drive.Id]
.Root.ItemWithPath("/Folder 1")
.Children["myfile.txt"]
.Request().GetAsync().ConfigureAwait(false);

How to add or update multiple events to a calendar via the C# Microsoft.Graph SDK?

Given the response on batching given in this StackOverflow question at Can I add multiple events to a calendar?
It's not clear how to use the C# SDK to and pass parameters into the requests. The examples I see are about retrieving data via batch, but I don't see a clear way to pass parameters in.
My previous code (before patch), I would just call AddAsync or UpdateAsync on the Request() method. But with batch, I can't have those Add/Update methods. So this was my code:
await _graphClient.Me.Calendars[_calendarID].Events
.Request()
.AddAsync(#event);
But I don't see a clear why to add the updated event object to the calendar. How to pass in the parameter correctly?
var batch = new BatchRequestContent();
// The response given is a GET METHOD
var request = _graphClient.Me.Calendars[_calendarID].Events
.Request()
.GetHttpRequestMessage();
// UPDATE the request to POST along with data in content
// (Am I wrong here in doing it this way?)
request.Method = HttpMethod.Post;
request.Content = new StringContent(JsonConvert.SerializeObject(#event));
batch.AddBatchRequestStep(request);
var response = await _graphClient.Batch.Request().PostAsync(batch);
The above code also doesn't like the JSON that it's given into the request.Content property. Please do let me know what is the correct way to supply data to the request, and if I'm completely wrong on writing a post. Thank you!
OK, solved it! Realized that I had to change the HttpMethod to Patch but there was no enumeration rather you had to explicitly set it with a string.
var batch = new BatchRequestContent();
var serializer = new Microsoft.Graph.Serializer();
Code to ADD an Event though a batch:
var request = this.GraphClient.Me.Calendars[_calendarID].Events
.Request()
.GetHttpRequestMessage();
request.Method = HttpMethod.Post;
request.Content = serializer.SerializeAsJsonContent(outlookEvent);
batch.AddBatchRequestStep(request);
Code to UPDATE an Event through a batch:
var request = this.GraphClient.Me.Calendars[_calendarID].Events[outlookEvent.Id]
.Request()
.GetHttpRequestMessage();
request.Method = new HttpMethod("PATCH");
request.Content = serializer.SerializeAsJsonContent(outlookEvent);
batch.AddBatchRequestStep(request);
Once you've added all your adds and updates, you can just call
await this.GraphClient.Batch.Request().PostAsync(batch);
You are mostly on the right path.
Instead of using the direct JsonConvert.SerializeObject, you should create an instance of the Microsoft.Graph.Serializer class and use that to do the serialization. It is configured to create JSON objects that are compatible with Microsoft Graph.

gratAttributeFrom() method is not returning value. It returns only object,promise

How to get value using grabAttributeFrom in codecept. It returns only object,promise. How to get the attribute value from it.
let userid_xpath = await I.grabAttributeFrom("//div[#class='mat-form-field-infix']/input[contains(#id,'mat-input')]","id");
var userid = "//*[#id='"+userid_xpath+"']";
I.fillField(userid,"username")
If i use like above and execute the test, i dont get any error. but i saw the debug panel displays like below
Emitted | step.after (I grab attribute from "//mat-label[contains(text(),'UserId')]//ancestor::label//ancestor::span//ancest...
Emitted | step.before (I fill field "//*[#id='[object Promise]']", "username")
How to get the attribute value and use it in a string. If i pass the userid_xpath variable in assert ; it works. but my requirement is to pass it in a string and then use it.
Look carefully in documentation
It returns what it said to return.
Returns Promise<string> attribute value.
Communication between codeceptjs and browser asynchronous.
Codeceptjs looks for synchronisation of actions sent to browser. But it can't look for browser response, so you should manually tell for codeceptjs to wait for result.
In CodeceptJS such implementation is made by Promises
To get value from Promise you should await it by let userid_xpath = await I.grabAttributeFrom(someSelector);
But awaiting of Promise will end up execution due to inner implementation of Codeceptjs. So, as said in docs:
Resumes test execution, so should be used inside async with await operator.
mark you test function as async:
Scenario("should log viewability point, when time and percent are reached", async (I) => {
I.amOnPage(testPage);
const userid_xpath = await I.grabAttributeFrom("//div[#class='mat-form-field-infix']/input[contains(#id,'mat-input')]","id");
const userid = "//*[#id='" + userid_xpath + "']";
I.fillField(userid, "username")
});

Cannot build up a string using StringBuffer and a server response

I'm trying to fetch data from this link via Dart. Since I'm making use of dart:io library's HttpClientResponse based instance to listen to the data obtained from the above link, therefore I thought that an instance of StringBuffer would be the best option to capture the received data. It would help me to build the response string in an incremental fashion. But it seems like I'm not making proper use of StringBuffer, because in the end the response string (stored in receivedBuffer) remains empty.
Code:
import 'dart:io';
import 'dart:convert';
void main() async
{
StringBuffer receivedBuffer = new StringBuffer("");
String url = "https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty";
HttpClient client = new HttpClient();
HttpClientRequest request = await client.getUrl(Uri.parse(url));
HttpClientResponse response = await request.close();
print("[info] Fetch successful. Proceeding to transform received data ...");
response.transform(utf8.decoder).listen((contents) => receivedBuffer.write(contents));
print("[info] Done. Contents:\n");
print(receivedBuffer);
}
Output :
[info] Fetch successful. Proceeding to transform received data ...
[info] Done. Contents:
Also, instead of receivedBuffer.write(contents), if I were to write print(contents), then all the
required data is printed as one would expect it to. But while trying to write the contents to recievedBuffer it seems like receivedBuffer wasn't even updated once.
I read this article, and tried to incorporate the answer present over there in my code. To be precise, I made use of Completer instance to take care of my issue, but it didn't help.
What's the issue in the above provided code?
You are not waiting for the stream to complete.
In the listen call, you set up a receiver for stream events, but you don't wait for the events to arrive.
You can either add an onDone parameter to the listen call and do something there, but more likely you will want to just wait for it here, and then I recommend:
await response.transform(utf8.decoder).forEach(receivedBuffer.write);
Using forEach is usually what you want when you are calling listen, but not remembering the returned subscription. Alternatively use an await for:
await for (var content in response.transform(utf8.decoder)) {
receivedBuffer.write(content);
}
(which corresponds to a forEach call in most ways).

AuthFlow.CreateCredentialsFromVerifierCode throws error only on load balancer

I am using Tweetinvi for posting images to Twitter.
From our App servers its working fine to post to Twitter.
But, When tried from our load balancer getting this error -
Error:The credentials are required as the URL does not contain the
credentials identifier.
Stack Trace: at Tweetinvi.AuthFlow.CreateCredentialsFromVerifierCode(String
verifierCode, String authorizationId, IAuthenticationContext
authContext)
My code snippet is like this -
var verifierCode = Request.Params.Get("oauth_verifier");
var authorizationId = Request.Params.Get("authorization_id");
var userCreds = AuthFlow.CreateCredentialsFromVerifierCode(verifierCode, authorizationId);
I see these parameters(oauth_verifier, authorization_id,..) being passed to the callback page. But still seeing the above error in the call back page.
Note: this issue is only when I try posting to Twitter on our loadbalancer (using the individual servers working fine).
Should I use a different overloaded function?
So the problem comes from the fact that you are actually using a load balancer. But let me explain how the authentication works and how you can solve your problem.
var appCredentials = new ConsumerCredentials("", "");
var authContext = AuthFlow.InitAuthentication(appCredentials, "");
When you call AuthFlow.InitAuthentication, it returns an IAuthenticationContext. This context contains all the information required to process the callback from Twitter.
But in addition to this, Tweetinvi adds a parameter authorization_id to the callback so that it can map the callback request to an actual IAuthenticationContext.
var authorizationId = Request.Params.Get("authorization_id");
var userCreds = AuthFlow.CreateCredentialsFromVerifierCode(verifierCode, authorizationId);
When you call AuthFlow.CreateCredentialsFromVerifierCode with an authorization_id as a parameter it will look into the local dictionary and try to get the IAuthenticationContext.
Because you are using a load balancer, the server executing the AuthFlow.InitAuthentication can be different from the server your receiving the callback request.
Because your callback arrives at a different server, it actually result in the AuthenticationContext being null.
This is what I tried to explain in the documentation.
How to solve this?
What you need to do is to store the IAuthenticationContext information required for the CreateCredentialsFromVerifierCode to continue its work when it receives the callback. I would suggest you store this in your database.
When you receive your callback you will have to get back these information from your db. To do that I would suggest that when you initally call the `` you add to the callback url a parameter with the value storing the authentication id in your database (e.g. my_auth_db_id=42).
var authContext = AuthFlow.InitAuthentication(appCredentials, "http://mywebsite.com?my_auth_db_id=42");
When your callback arrives you will be able to do :
var myDBAuthId = Request.Params.Get("my_auth_db_id");
With this value you can now create a new token with the required information (stored in the db).
var token = new AuthenticationToken()
{
AuthorizationKey = "<from_db>",
AuthorizationSecret = "<from_db>",
ConsumerCredentials = creds
};
Now you are ready to complete the operation:
var userCreds = AuthFlow.CreateCredentialsFromVerifierCode(verifierCode, token );
I realize this is a big post, but I wanted to explain how it works.
Please let me know if anything does not makes sense.

Resources