VSTS / TFS REST API - Fetch Work Items and their linked ones - tfs

I am retrieving some User Stories using the the VSTS / TFS Web API and the code below:
var getWorkItemsHttpRequestMessage = new HttpRequestMessage(new HttpMethod("GET"), uri + "/_apis/wit/workitems?ids=736,731&&api-version=4.1");
var getWorkItemsHttpResponse = client.SendAsync(getWorkItemsHttpRequestMessage).Result;
if (getWorkItemsHttpResponse.IsSuccessStatusCode)
{
var workItems = getWorkItemsHttpResponse.Content.ReadAsAsync<HttpWorkItems>().Result;
// ...
The query returns all the fields of the work items (user stories, in this case), but not the other items that is linked to them.
I would like to retrieve the Tasks related to these User Stories.
How can it be done ?
Is there another better way to do it ?

You can use the $expand parameter in the URL with the value relations:
https://dev.azure.com/{organization}/{project}/_apis/wit/workitems/4?$expand=relations&api-version=4.1
In the results, you will get the work item links in the section relations:
"relations":[
{
"rel":"System.LinkTypes.Hierarchy-Forward",
"url":"https://dev.azure.com/shaykia/_apis/wit/workItems/5",
"attributes":{
"isLocked":false
}
In the example above, we check work item 4 in the API, and in the results, we can see that work item 5 linked to him with a type of System.LinkTypes.Hierarchy-Forward that work item 4 the parent of 5 (5 he is the child, a task in this case).
You can read here about the relations types.

Related

How can I retrieve all members when querying for appRoleAssignedTo in Microsoft Graph?

I am attempting to programmatically retrieve a list of users (principalType = "User") and their associated appRoleId values for an enterprise app using itsresourceId value from Azure AD. There is a total of ten Users with a combined total of twenty appRoleId values associated with the app. However, when I run my query I receive data for just two users and a combined total of four appRoleId values.
Here's my C# code:
GraphServiceClient myGraphClient = GetGraphServiceClient([scopes]);
// Retrieve the [Id] value for the app. Note [Id] is a pseudonym for the [resourceId] required to retrieve users and app roles assigned.
var servPrinPage = await myGraphClient.ServicePrincipals.Request()
.Select("id,appRoles")
.Filter($"startswith(displayName, 'Display Name')")
.GetAsync()
.ConfigureAwait(false);
// Using the first [Id] value from the [ServicePrincipals] page, retrieve the list of users and their assigned roles for the app.
var appRoleAssignedTo = await myGraphClient.ServicePrincipals[servPrinPage[0].Id].AppRoleAssignedTo.Request().GetAsync().ConfigureAwait(false);
The query returns a ServicePrincipalAppRoleAssignedToCollectionPage (as expected) but the collection only contains four pages (one per User/appRoleId combination).
As an aside, the following query in Microsoft Graph Explorer produces an equivalent result:
https://graph.microsoft.com/v1.0/servicePrincipals/[resourceId]/appRoleAssignedTo
What am I missing here? I need to be able to retrieve the complete list of users and assigned app roles. Any assistance is greatly appreciated.
The issue I was confronting has to do with the pagination feature employed by Azure AD and MS Graph. In a nutshell, I was forced to submit two queries in order to retrieve all twenty records I was expecting.
If you have a larger set of records to be retrieved you may be faced with submitting a much larger number of successive queries. The successive queries are managed using a "skiptoken" passed as a request header each time your query is resubmitted.
Here is my revised code with notation....
// Step #1: Create a class in order to strongly type the <List> which will hold your results.
// Not absolutely necessary but always a good idea when working with <Lists> in C#.
private class AppRoleByUser
{
public string AzureDisplayName;
public string PrincipalDisplayName;
}
// Step #2: Submit a query to acquire the [id] for the Service Principal (i.e. your app).
// Note the [ServicePrincipals].[id] property is synonymous with the [resourceId] needed to
// retrieve [AppRoleAssignedTo] values from Microsoft Graph in the next step.
// Initialize the Microsoft Graph Client.
GraphServiceClient myGraphClient = GetGraphServiceClient("Directory.Read.All");
// Retrieve the Service Principals page containing the app [Id].
var servPrinPage = await myGraphClient.ServicePrincipals.Request().Select("id,appRoles").Filter($"startswith(displayName, 'Your App Name')").GetAsync().ConfigureAwait(false);
// Store the app [Id] in a local variable (for readability).
string resourceId = servPrinPage[0].Id;
// Step #3: Using the [Id]/[ResourceId] value from the previous step, retrieve a list of AppRoleId/DisplayName pairs for your app.
// Results of the successive queries are typed against the class created earlier and are appended to the <List>.
List<AppRoleByUser> appRoleByUser = new List<AppRoleByUser>();
// Note, unlike "Filter" or "Search" parameters, it is not possible to
// add a "Skiptoken" parameter directly to your query in C#.
// Instead, it is necessary to insert the "skiptoken" as request header using the Graph QueryOption class.
// Note the QueryOption List is passed as an empty object on the first pass of the while loop.
var queryOptions = new List<QueryOption>();
// Initialize the variable to hold the anticipated query result.
ServicePrincipalAppRoleAssignedToCollectionPage appRoleAssignedTo = new ServicePrincipalAppRoleAssignedToCollectionPage();
// Note the number of user/role combinations associated with an app is not always known.
// Consequently, you may be faced with the need to acquire multiple pages
// (and submit multiple consecutive queries) in order to obtain a complete
// listing of user/role combinations.
// The "while" loop construct will be utilized to manage query iteration.
// Execution of the "while" loop will be stopped when the "bRepeat" variable is set to false.
bool bRepeat = true;
while (bRepeat == true)
{
appRoleAssignedTo = (ServicePrincipalAppRoleAssignedToCollectionPage) await myGraphClient.ServicePrincipals[resourceId].AppRoleAssignedTo.Request(queryOptions).GetAsync().ConfigureAwait(false);
foreach (AppRoleAssignment myPage in appRoleAssignedTo)
{
// I was not able to find a definitive answer in any of the documents I
// found but it appears the final record in the recordset carries a
// [PrincipalType] = "Group" (all others carry a [PrincipalType] = "User").
if (myPage.PrincipalType != "Group")
{
// Insert "User" data into the List<AppRoleByUser> collection.
appRoleByUser.Add(new AppRoleByUser{ AzureDisplayName = myPage.PrincipalDisplayName, AzureUserRole = myPage.AppRoleId.ToString() });
}
else
{
// The "bRepeat" variable is initially set to true and is set to
// false when the "Group" record is detected thus signaling
// task completion and closing execution of the "while" loop.
bRepeat = false;
}
}
// Acquire the "nextLink" string from the response header.
// The "nextLink" string contains the "skiptoken" string required for the next
// iteration of the query.
string nextLinkValue = appRoleAssignedTo.AdditionalData["#odata.nextLink"].ToString();
// Parse the "skiptoken" value from the response header.
string skipToken = nextLinkValue.Substring(nextLinkValue.IndexOf("=") + 1);
// Include the "skiptoken" as a request header in the next iteration of the query.
queryOptions = new List<QueryOption>()
{
new QueryOption("$skiptoken", skipToken)
};
}
That's a long answer to what should have been a simple question. I am relatively new to Microsoft Graph but it appears to me Microsoft has a long way to go in making this API developer-friendly. All I needed was to know the combination of AppRoles and Users associated with a single, given Azure AD app. One query and one response should have been more than sufficient.
At any rate, I hope my toil might help save time for someone else going forward.
Could you please remove "Filter" from the code and retry the operation. Let us know if that worked.

Finding a message across multiple folder trees with one call with microsoft-graph java sdk

In converting from an older bit of code that uses the EWS (ews-java-api v 2.0) SDK/API/Scope to Graph (microsoft-graph v5.4.0), I found that I could search (say by InternetMessageId) across multiple folder hierarchies at once in EWS with (simplifying how to get FolderId values a bit):
SearchFilter.SearchFilterCollection filter =
new SearchFilter.SearchFilterCollection(LogicalOperator.And);
filter.add(new SearchFilter.IsEqualTo(EmailMessageSchema.InternetMessageId, msgId));
List<FolderId> folders = Arrays.asList(new FolderId("AllItems"), new FolderId("Deletions"));
ItemView view = new ItemView(10);
ServiceResponseCollection<FindItemResponse<Item>> findResultsCollection =
service.findItems(searchFolders, filter, null, view, null, ReturnErrors);
With that EWS search whether my message of interest is in the Inbox, some user-created sub-folder, JunkEmail, DeletedItems, RecoverableItemsDeletions I find it by InternetMessageId in one go.
With Graph I issue two calls to be able to ensure the message does not exist
UserRequestBuilder u = GraphServiceClient
.builder()
.authenticationProvider(authenticationProvider)
.buildClient()
.users(user);
for (String folderTree : Arrays.asList("AllItems", "RecoverableItemsDeletions")) {
MessageCollectionPage mcp = u.mailFolders(folderTree)
.messages()
.buildRequest()
.filter("internetMessageId eq '" + msgId + "'")
.get();
Is there a way to search multiple trees in one go with Graph to be more like the EWS path that took a List?
Use List Messages endpoint to get the messages in the signed-in user's mailbox (including the Deleted Items and Clutter folders).
Depending on the page size and mailbox data, getting messages from a mailbox can incur multiple requests. The default page size is 10 messages. Use $top to customize the page size, within the range of 1 and 1000.
To improve the operation response time, use $select to specify the exact properties you need. Refer documentation here.
Java Code Snippet -
GraphServiceClient graphClient = GraphServiceClient.builder().authenticationProvider( authProvider ).buildClient();
MessageCollectionPage messages = graphClient.me().messages()
.buildRequest()
.select("sender,subject")
.get();

Retrieving chatinfo and attendees using MS Graph SDK

Starting with a sample oauth app I am trying to retrieve info about an online meeting that occurred.
Create the client:
var graphClient = _graphServiceClientFactory.GetAuthenticatedGraphClient((ClaimsIdentity)User.Identity);
Make request:
var returnObj = await graphClient.Me.OnlineMeetings.CreateOrGet(meetingID).Request().PostAsync();
The problem is the lack of information returned. I am trying to retrive the chat from the meeting, which seems like it should be in returnObj.ChatInfo but this is all I get back:
{
"threadId":"19:meeting_SOMELONGUNIQUESTRINGHERE#thread.v2",
"messageId":"0",
"#odata.type":"microsoft.graph.chatInfo"
}
Also missing are the attendees in Participants (count=0). I know there are non zero attendees and that a chat log exists.
Trying Select or Expand does not help. Select returns nothing new,and expand gives an error along the lines of Message: Parsing OData Select and Expand failed: Property 'participants' on type 'microsoft.graph.onlineMeeting' is not a navigation property or complex property. Only navigation properties can be expanded., and similarly for chatinfo.
Also, using the threadId I thought maybe I could do this:
var groups = await graphClient.Groups.Request().GetAsync();
Group group = groups[0];
ConversationThread chat;
chat = await graphClient.Groups[group.Id].Threads[chatId].Request().GetAsync();
where for chatId I used the threadId from chatinfo, wholey and parsed out in different ways but I get Not Found.
No idea if what I'm trying to do is even possible as the documentation is rather lacking in terms of tying different pieces together (Like what is the threadId for? where is it used?).
Also, here are the various scopes I am requesting
"GraphScopes": "User.Read User.ReadBasic.All Mail.Send OnlineMeetings.ReadWrite Group.Read.All Team.ReadBasic.All"

firebase session for two users [duplicate]

In my main page I have a list of users and i'd like to choose and open a channel to chat with one of them.
I am thinking if use the id is the best way and control an access of a channel like USERID1-USERID2.
But of course, user 2 can open the same channel too, so I'd like to find something more easy to control.
Please, if you want to help me, give me an example in javascript using a firebase url/array.
Thank you!
A common way to handle such 1:1 chat rooms is to generate the room URL based on the user ids. As you already mention, a problem with this is that either user can initiate the chat and in both cases they should end up in the same room.
You can solve this by ordering the user ids lexicographically in the compound key. For example with user names, instead of ids:
var user1 = "Frank"; // UID of user 1
var user2 = "Eusthace"; // UID of user 2
var roomName = 'chat_'+(user1<user2 ? user1+'_'+user2 : user2+'_'+user1);
console.log(user1+', '+user2+' => '+ roomName);
user1 = "Eusthace";
user2 = "Frank";
var roomName = 'chat_'+(user1<user2 ? user1+'_'+user2 : user2+'_'+user1);
console.log(user1+', '+user2+' => '+ roomName);
<script src="https://getfirebug.com/firebug-lite-debug.js"></script>
A common follow-up questions seems to be how to show a list of chat rooms for the current user. The above code does not address that. As is common in NoSQL databases, you need to augment your data model to allow this use-case. If you want to show a list of chat rooms for the current user, you should model your data to allow that. The easiest way to do this is to add a list of chat rooms for each user to the data model:
"userChatrooms" : {
"Frank" : {
"Eusthace_Frank": true
},
"Eusthace" : {
"Eusthace_Frank": true
}
}
If you're worried about the length of the keys, you can consider using a hash codes of the combined UIDs instead of the full UIDs.
This last JSON structure above then also helps to secure access to the room, as you can write your security rules to only allow users access for whom the room is listed under their userChatrooms node:
{
"rules": {
"chatrooms": {
"$chatroomid": {
".read": "
root.child('userChatrooms').child(auth.uid).child(chatroomid).exists()
"
}
}
}
}
In a typical database schema each Channel / ChatGroup has its own node with unique $key (created by Firebase). It shouldn't matter which user opened the channel first but once the node (& corresponding $key) is created, you can just use that as channel id.
Hashing / MD5 strategy of course is other way to do it but then you also have to store that "route" info as well as $key on the same node - which is duplication IMO (unless Im missing something).
We decided on hashing users uid's, which means you can look up any existing conversation,if you know the other persons uid.
Each conversation also stores a list of the uids for their security rules, so even if you can guess the hash, you are protected.
Hashing with js-sha256 module worked for me with directions of Frank van Puffelen and Eduard.
import SHA256 from 'crypto-js/sha256'
let agentId = 312
let userId = 567
let chatHash = SHA256('agent:' + agentId + '_user:' + userId)

Get meetings by organizer or attendee email ID using GoToMeeting SDK

I am using .Net sdk of GoToMeeting.
I want to get meetings organized by particular organizer.
I have tried using
MeetingsApi.getHistoryMeetings but it does not return me OrganizerKey so I can not filter on particular Organizer.
Is there any way to get meeting(s) based on organizer or even by Attendee email ID by using .Net SDK?
What is the problem you are facing with MeetingsApi.getHistoryMeetings();?
why you need to filter the method, the MeetingsApi.getHistoryMeetings(accessToken,true,date1,date2); itself filtered for a particular user right?
Look on the arguments we are passing in the method?
accessToken - This token is generated as a result of successful authentication of a gotoproduct account. (In API call it can be generated using directlogin orOauth method.
true - this represents whether the meetings returned are past or not.
date1 - Start date range for the meetings.
date2 - End date range for the meetings.
below code is the sample for getting history meetings.
DateTime sdt=DateTime.Parse("07/01/2015");
DateTime edt=DateTime.Parse("07/30/2015");
List<MeetingHistory> historymeets = new System.Collections.Generic.List<MeetingHistory>();
historymeets=meeting.getHistoryMeetings(accesToken, true, sdt, edt);
foreach (var item in historymeets)
{
Console.WriteLine(item.subject);
}
try it out... The above code will store the meetings in historymeets collection object.
You can do the filter function in that collection object.
UPDATE :
List<MeetingHistory> historymeets = new System.Collections.Generic.List<MeetingHistory>();
historymeets=meeting.getHistoryMeetings(accesToken, true, sdt, edt);
List<AttendeeByMeeting> lstAttendee = new System.Collections.Generic.List<AttendeeByMeeting>();
foreach (var item in historymeets)
{
Console.WriteLine(item.meetingId);
lstAttendee=meeting.getAttendeesByMeetings(accesToken, item.meetingId);
foreach (var itemattendee in lstAttendee)
{
Console.WriteLine(itemattendee.attendeeEmail);
}
}
for comment - It is possible, but not directly because there is no api calls, which supports the meeting by attendee . the above code which i have written is for meeting by organizer . Now you have two options,
get the getHistoryMeetings, now you got the meeting details right? , then get the attendees by meeting id using getAttendeesByMeetings(), filter the two different collection objects with join using LINQ. OR
get the meetingdetails and attendees by executing two different fuinction calls, and store it in database or somewhere else, so that you can access it for doing the filter

Resources