I'm trying to add a SharePoint Library tab to a Microsoft Team Channel programmatically through the Microsoft Graph.
Here is the payload I'm sending through the Graph Explorer POST
{
"teamsAppId": "com.microsoft.teamspace.tab.files.sharepoint",
"name": "Documents3",
"sortOrderIndex": "10300",
"configuration": {
"siteUrl": "https://baywet.sharepoint.com/sites/customerhub",
"libraryServerRelativeUrl": "/sites/customerhub/Shared Documents",
"selectedDocumentLibraryTitle": "Documents",
"selectedSiteTitle": "customerhub",
"dateAdded": "2018-10-05T16:56:59.169Z"
}
}
I get a 201 status response, my tab is added to the channel. However whenever somebody tries to upload a file from the Teams UI, they get the following error message The File {filename} is missing. If they click on Open in SharePoint and then upload the file, it works.
If I compare with a tab created through the UI (which works properly) here is the description I get.
{
"id": "a68e34db-9d43-4821-953b-2dec938ce785",
"name": "Document%20Library",
"teamsAppId": "com.microsoft.teamspace.tab.files.sharepoint",
"sortOrderIndex": "10200",
"webUrl": "https://teams.microsoft.com/l/channel/19%3ab2e05a0aae42487485b13e088d5d2f0f%40thread.skype/tab%3a%3aa63916e6-f252-477d-9696-7934980e7e47?label=Document%2520Library&groupId=71ed6a2e-67ca-4930-a3c2-abb25ca29fbf&tenantId=bd4c6c31-c49c-4ab6-a0aa-742e07c20232",
"configuration": {
"entityId": null,
"contentUrl": null,
"removeUrl": null,
"websiteUrl": null,
"siteUrl": "https://baywet.sharepoint.com/sites/customerhub",
"libraryServerRelativeUrl": "/sites/customerhub/Shared Documents",
"libraryId": "706FAD5678484E7B93B0855E52A0BCD9",
"selectedDocumentLibraryTitle": "Documents",
"selectedSiteImageUrl": "https://baywet.sharepoint.com/sites/customerhub/_api/GroupService/GetGroupImage?id='f9d430ca-4de3-42f1-9474-1427bfdb16b0'&hash=636743460492415245",
"selectedSiteTitle": "customerhub",
"dateAdded": "2018-10-05T16:56:59.169Z"
}
}
The only difference being the libraryId configuration value. (you're not supposed to send in the webUrl and id).
This library id doesn't match the library id in SharePoint, or the drive item id in the Graph so my question is: what value am I supposed to set for the libraryId? Is there anything else I am missing?
The following code Creates the team for a known Group ID (365 that When created, created a team site) and adds 3 tabs on the existing channel.
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(GraphClientId)
.WithTenantId(GraphTenantId)
.WithClientSecret(GraphClientSecret)
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var team = new Team
{
MemberSettings = new TeamMemberSettings
{
AllowCreateUpdateChannels = true
},
MessagingSettings = new TeamMessagingSettings
{
AllowUserEditMessages = true,
AllowUserDeleteMessages = true
},
FunSettings = new TeamFunSettings
{
AllowGiphy = true,
GiphyContentRating = GiphyRatingType.Moderate
}
};
Team addedTeam = await graphClient.Groups[GroupID].Team
.Request()
.PutAsync(team);
ECGTeam ecgTeam = new ECGTeam {ProjectNumber= ProjectNumber ,GroupID = GroupID, TeamID = addedTeam.Id };
string channelID = string.Empty;
var channels = await graphClient.Teams[addedTeam.Id].Channels.Request().GetAsync();
channelID = channels[0].Id;
ecgTeam.ChannelID = channelID;
TeamsTab newTab = addTab(targetWebUrl, "WorkingPapers", "Working Papers");
var addedTab = await graphClient.Teams[addedTeam.Id].Channels[channelID].Tabs.Request().AddAsync(newTab);
ecgTeam.TabWorkingPapersID = addedTab.Id;
//DPC documents
newTab = addTab(targetWebUrl, "DPCdocuments", "DPC documents");
addedTab = await graphClient.Teams[addedTeam.Id].Channels[channelID].Tabs.Request().AddAsync(newTab);
ecgTeam.TabDPCdocumentsID=addedTab.Id;
//ContractDocuments //
newTab = addTab(targetWebUrl, "ContractDocuments", "Contract Documents");
addedTab = await graphClient.Teams[addedTeam.Id].Channels[channelID].Tabs.Request().AddAsync(newTab);
ecgTeam.TabContractDocumentsID = addedTab.Id;
//log.LogInformation(addedTab.Id);
Now, If you can help me create a Library for the said site that uses a custom content type, I will buy you coffee :-)
The wealth of good documentation for MS Graph in .NET Core makes me want to cry :-(
I found the solution a while after, sorry for not posting here earlier.
POST https://graph.microsoft.com/beta/teams/{groupId}/channels/{channelId}/tabs
{
"teamsApp#odata.bind": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/com.microsoft.teamspace.tab.files.sharepoint",
"name": "test",
"sortOrderIndex": "10400",
"configuration": {
"contentUrl": "https://baywet.sharepoint.com/sites/NYC/test"
}
}
Where contentUrl is the URL of the document library
Related
I want to create a specific class of assignments.
At Graph Explorer,
https://graph.microsoft.com/beta/education/classes/{classId}/assignments
This request works well.
But in my C# code,
var assignInfo = new EducationAssignment
{
DisplayName = "test",
DueDateTime = DateTimeOffset.Parse("2020-09-20T18:00:00Z"),
Instructions = new EducationItemBody
{
ContentType = BodyType.Html,
Content = "hi"
},
Status = EducationAssignmentStatus.Draft,
AllowStudentsToAddResourcesToSubmission = true,
AssignTo = new EducationAssignmentClassRecipient
{
},
Grading = new EducationAssignmentPointsGradeType()
{
MaxPoints = 100
},
CreatedDateTime = DateTimeOffset.Parse("2020-09-20T12:00:00Z"),
AssignDateTime = DateTimeOffset.Parse("2020-09-20T13:00:00Z"),
AssignedDateTime = DateTimeOffset.Parse("2020-09-20T13:00:00Z"),
CloseDateTime = null,
AllowLateSubmissions = true
};
await graphClient.Education.Classes[pClassId].Assignments
.Request()
.AddAsync(assignInfo);
It occured error:
{"Code: 20132\r\nMessage: The content of the request is invalid. Common causes are an invalid Content-Type header or no content in the body.\r\nInner error:\r\n\tAdditionalData:\r\n\tdate: 2020-09-20T07:25:14\r\n\trequest-id: d2181119-9116-4f1d-9ed4-d007e2e406d0\r\n\tclient-request-id: d2181119-9116-4f1d-9ed4-d007e2e406d0\r\nClientRequestId: d2181119-9116-4f1d-9ed4-d007e2e406d0\r\n"}
Why is this happening? I've been thinking and trying all day.
I tried
await graphClient.Education.Classes[pClassId].Assignments
.Request()
.Header("Content-Type", "application/json")
.AddAsync(assignInfo);
But there was the same error.
If only the displayname element was requested, the results were the same.
The Permissions has been dealt with.
EduAssignments.ReadWriteBasic, EduAssignments.ReadWrite.. etc
And the dll(NuGet pakage) is also prepared in beta version.
I referred to this document.
I'm desperate for help..
Thanks.
adding this assignInfo.ODataType = null; fixes the problem.
We are creating a team group (with the beta Graph API) and we want the emailaddress to contain another value than the value that's based on what's provided in displayName.
While searching through the documentation it seems that this is possible by providing a value for mailNickname in AdditionalData (https://learn.microsoft.com/en-us/graph/teams-create-group-and-team).
So I implemented that. Unfortunately the mailaddress and the alias were still like TestGroup#domain.nl instead of TestMailNickname#domain.nl.
var graphApiServiceClient = new GraphServiceClient(this.authenticationProvider)
{
BaseUrl = "https://graph.microsoft.com/beta"
};
var owner = "valueForOwner";
var teamTemplate = teamTemplateType == TeamTemplateType.Staff
? "educationStaff"
: "educationClass";
var team = new Team
{
AdditionalData = new Dictionary<string, object>
{
{ "template#odata.bind", $"https://graph.microsoft.com/beta/teamsTemplates('{teamTemplate}')" },
{ "owners#odata.bind", new[]{$"https://graph.microsoft.com/beta/users('{owner}')"}},
{ "displayName", "TestGroup" },
{ "description", "This is a testgroup" },
{ "mailNickname", "TestMailNickname" }
}
};
await graphApiServiceClient.Teams.Request().AddAsync(team);
The MailNickname does change when I update the MailNickname property afterwards with an update request like await graphApiServiceClient.Groups[objectId].Request().UpdateAsync(new Group { MailNickname = mailNickname});.
This is confirmed with a graphApiServiceClient.Groups[objectId].Request().GetAsync()
Unfortunately it still shows TestGroup#domain.nl as the alias in the admin at https://admin.microsoft.com/AdminPortal/Home#/groups.
But, updating the value like this doesn't work for the Mail property because it states it's readonly in the update request.
Does anyone know what I am doing wrong in my original create/add request?
Plus does anyone know why the old alias value is still shown instead of the updated alias at https://admin.microsoft.com/AdminPortal/Home#/groups?
This is possible by creating a group via the Graph api first and then using the group ID to create a team for this group.
Create the group via "https://graph.microsoft.com/v1.0/groups".
{
"displayName": "TestGroup",
"mailNickname": "TestMailNickname",
"mailEnabled": true,
"securityEnabled": false,
"description": "This is a testgroup",
"groupTypes": [
"Unified"
],
"owners#odata.bind": [
"https://graph.microsoft.com/v1.0/users/OWNEDID"
]
}
Once the group has been made it'll output an ID which you'll then use to create a team via "https://graph.microsoft.com/v1.0/groups/YOURIDHERE/team"
Body would look something like;
{
"memberSettings": {
"allowCreateUpdateChannels": false,
"allowAddRemoveApps": false,
"allowCreateUpdateRemoveTabs": false,
"allowCreateUpdateRemoveConnectors": false
}
}
It looks like you are using an EDU tenant, since you are referencing the particular templates.
I have tested this previously, and the method suggested above will not work with the "EducationClass" template.
The way I got it to work was:
Create the Team with the template
$Teamdata =
'{
"displayName":"' + $newteamName + '",
"description":"' + $newteamName + '",
"template#odata.bind": "https://graph.microsoft.com/v1.0/teamsTemplates(\u0027educationClass\u0027)",
"hideFromOutlookClients": "true",
"hideFromAddressLists": "true",
"members":[
{
"#odata.type":"#microsoft.graph.aadUserConversationMember",
"roles":[
"owner"
],
"user#odata.bind":"'+ $DefaultOwnerURL + '"
}
]
}'
Wait for the group to be created (usually about 20 seconds)
patch the group mailnickname
$Body_SetGroupSDSSettings =
'{
"mailNickname": "' + $newteamMailNickname + '"
}'
It isn't perfect, but it is the best way I could find to do this.
I am developing a Angular based website in which a user is required to login using a custom OAuth2 third party authentication provider. .Net core web API is the backend. The response received from the user end point is in JSON and it is having the following format:
{
"dataSources": {
"profile": {
"username": "xyz"
}
},
"profile": {
"id": "87dfkajdfd998df"
},
"errors": {}
}
The code I am currently using is as follows:
builder.AddOAuth(oauth2Configuration.Issuer,
options => {
options.ClientId = oauth2Configuration.ClientId;
options.ClientSecret = oauth2Configuration.ClientSecret;
options.Scope.Add(oauth2Configuration.Scope);
options.ClaimsIssuer = oauth2Configuration.Issuer;
options.CallbackPath = new PathString(oauth2Configuration.ResponseType);
options.AuthorizationEndpoint = oauth2Configuration.Authority;
options.TokenEndpoint = oauth2Configuration.EndSessionEndpoint;
options.UserInformationEndpoint = oauth2Configuration.UserInfoEndpoint;
options.SaveTokens = true;
// Below mapping does not seem to work
options.ClaimActions.MapJsonSubKey(ClaimTypes.Name, "dataSources", "profile.username");
options.ClaimActions.MapJsonKey(ClaimTypes.SerialNumber, "profile.id");
// Remaining code
})
After authenticating with the above code, the claims list is always empty.
Has anyone encountered a similar situation in which claim mapping was done for custom JSON data?
That seems the OAuth authentication handler itself won't help call the endpoint , you need to manually make a call to obtain use's profile from UserInfo endpoint in OnCreatingTicket event :
OnCreatingTicket = async context =>
{
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var user = JObject.Parse(await response.Content.ReadAsStringAsync());
context.RunClaimActions(user);
}
And make claim mapping manually based on your scenario - parse/read the json using JSON.NET and add to user's princple . Please refer to below articles for code samples :
https://www.jerriepelser.com/blog/authenticate-oauth-aspnet-core-2/
https://stackoverflow.com/a/46064936/5751404
I am wondering if the /v1.0/me/sendMail has the ability to delay sending an email. In the Outlook client, you can specify that you want your email sent at a later date and time. I've snooped around to see if there is a property that can be set on the message object to indicate this.
Did anyone find a way to get this working? Of course, I could implement something in my software to handle the delayed sending, but why re-create something if it is already there.
You can achieve delayed sending of emails using extended properties. These can be set on the Graph API request payload using the "singleValueExtendedProperties" attribute.
The property to use is PidTagDeferredSendTime which has the ID 0x3FEF and type SystemTime.
The id attribute of "singleValueExtendedProperties" takes different formats depending on the property you are setting.
For the deferred send time you would use SystemTime 0x3FEF.
Example using a HTTP JSON POST Payload:
{
"message": {
"subject": "Meet for lunch?",
"body": {
"contentType": "Text",
"content": "The new cafeteria is open."
},
"toRecipients": [
{
"emailAddress": {
"address": "bob#contoso.com"
}
}
],
"singleValueExtendedProperties":
[
{
"id":"SystemTime 0x3FEF",
"value":"2019-01-29T20:00:00"
}
]
}
}
Example using the Microsoft Graph API client library:
var client = /* Create and configure GraphServiceClient */;
var msg = new Message();
msg.ToRecipients = List<Recipient>();
msg.ToRecipients.Add(new Recipient() {
EmailAddress = new EmailAddress() { Address ="bob#contoso.com" }
};
msg.Subject = "Meet for lunch?";
msg.Body = new ItemBody()
{
Content = "The new cafeteria is open.",
ContentType = BodyType.Text,
};
msg.SingleValueExtendedProperties = new MessageSingleValueExtendedPropertiesCollectionPage();
msg.SingleValueExtendedProperties.Add(new SingleValueLegacyExtendedProperty()
{
Id = "SystemTime 0x3FEF",
Value = DateTime.UtcNow.AddMinutes(5).ToString("o")
});
await client.Me.SendMail(msg, true).Request().PostAsync();
https://gallery.technet.microsoft.com/office/Send-Emails-until-a-9cee20cf
You set the deferred send time extended prop when creating the item.
For an End Screens report, how can I determine which target is represented by the end_screen_element_id on each record?
For example: on my channel, let's say I have "Video 1" set up with two video end screen elements "Video 2" and Video 3". I want to know how many clicks at the end of "Video 1" went to "Video 2" and how many went to "Video 3".
The data returned for this report gives me a video_id field indicating which video was watched and an end_screen_element_clicks field indicating how many times a viewer clicked through to the end screen video...
...HOWEVER, the only identifier for which video the viewer clicked through to is an end_screen_element_id field, which looks like a GUID and apparently somehow refers to the full end screen element definition, and therefore presumably what video is represented by that definition.
I'm unable to find any reports or other API calls for getting detail information on that end_screen_element_id field, particularly which video it represents.
How can I use that field or otherwise figure out what end screen video the viewer clicked through to?
More Information
The data returned looks like this:
Data returned in the End Screens report
Here's a screenshot that may help explain what I'm trying to do with the data: YouTube Analytics screen shot
The following C# code demonstrates how the report is requested:
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets
{
ClientId = CLIENT_ID,
ClientSecret = CLIENT_SECRET
},
new[] { YouTubeReportingService.Scope.YtAnalyticsReadonly },
"user",
CancellationToken.None,
new FileDataStore("Drive.Auth.Store")).Result;
YouTubeReportingService reportingService = new YouTubeReportingService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = APPLICATION_NAME
});
// Submit a report job to obtain the latest End Screen statistics.
Job channelEndScreenJob = new Job();
channelEndScreenJob.ReportTypeId = "channel_end_screens_a1";
channelEndScreenJob.Name = JOB_NAME;
Job createdJob =
reportingService.Jobs.Create(channelEndScreenJob).Execute();
A separate service retrieves the report like this:
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets
{
ClientId = CLIENT_ID,
ClientSecret = CLIENT_SECRET
},
new[] { YouTubeReportingService.Scope.YtAnalyticsReadonly },
"user",
CancellationToken.None,
new FileDataStore("Drive.Auth.Store")).Result;
YouTubeReportingService reportingService = new YouTubeReportingService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = APPLICATION_NAME
});
// Retrieve data from jobs that were previously submitted.
ListJobsResponse jobList = reportingService.Jobs.List().Execute();
if (jobList.Jobs != null)
{
foreach (Job job in jobList.Jobs)
{
ListReportsResponse reportList = reportingService.Jobs.Reports.List(job.Id).Execute();
if (reportList.Reports != null)
{
foreach (Report report in reportList.Reports)
{
MediaResource.DownloadRequest getRequest = reportingService.Media.Download("");
// Download the report data.
using (MemoryStream stream = new MemoryStream())
{
getRequest.MediaDownloader.Download(report.DownloadUrl, stream);
stream.Flush();
stream.Position = 0;
using (StreamReader reader = new StreamReader(stream))
{
// Parse report...
DataTable parsedReport = ReportToDataTable(reader.ReadToEnd());
// ...and get data on videos watched and videos clicked to.
foreach (DataRow row in parsedReport.Rows)
{
string videoWatched = row["video_id"].ToString();
string videoClickedToFromEndScreen = **WHAT???**
}
}
}
}
}
}
}