I created a function to return the outcomes using Microsoft Graph Education API. I want to print feedback and points but I can’t, they don’t exist. Here is the code:
public static async Task<IEnumerable<EducationOutcome>> GetOutcomesForSubmission()
{
var outcomes = await graphClient
.Education
.Classes["8557483b-a233-4710-82de-e1bdb03bb9a9"]
.Assignments["1b09cd43-cf87-4cef-a043-ae3d6160c200"]
.Submissions["d4486e20-1b47-4b5b-720c-0fe0038d4882"]
.Outcomes
.Request()
.GetAsync();
return outcomes;
}
public static void ListOutcomes()
{
var outcomes = GetOutcomesForSubmission().Result;
Console.WriteLine("Outcomes:\n");
foreach (var v in outcomes)
{
Console.WriteLine($"User id: {v.LastModifiedBy.User.Id}, Submission id: {v.Id}");
}
Console.WriteLine("\n");
}
Your issue is that GetAsync() isn't returning a collection of EducationOutcome objects. It is returning an EducationSubmissionOutcomesCollectionPage instead. To get the actual results, you need to return the CurrentPage property.
public static async Task<IEnumerable<EducationOutcome>> GetOutcomesForSubmission()
{
var response = await graphClient
.Education
.Classes["8557483b-a233-4710-82de-e1bdb03bb9a9"]
.Assignments["1b09cd43-cf87-4cef-a043-ae3d6160c200"]
.Submissions["d4486e20-1b47-4b5b-720c-0fe0038d4882"]
.Outcomes
.Request()
.GetAsync();
return response.CurrentPage.ToList();
}
Note that this will only return the first page of data. If you want to grab all of the data, you'll need to page through using the response.NextPageRequest property:
var outcomes = response.CurrentPage.ToList();
while (response.NextPageRequest != null)
{
response = await response.NextPageRequest.GetAsync();
outcomes.AddRange(response.CurrentPage);
}
return outcomes;
Keep in mind that EducationOutcome is a base class, so it will only contain properties common across all "Outcome" types (which in this case is pretty little). If you want a specific type of Outcome, you'll need to cast it to the specific type first:
foreach (var v in outcomes)
{
if (v is EducationRubricOutcome)
{
var outcome = v as EducationRubricOutcome;
Console.WriteLine($"User id: {outcome.LastModifiedBy.User.Id}, Submission id: {outcome.Id}, Feedback Count: {outcome.RubricQualityFeedback.Count}");
}
else if (v is EducationPointsOutcome)
{
var outcome = v as EducationPointsOutcome;
Console.WriteLine($"User id: {outcome.LastModifiedBy.User.Id}, Submission id: {outcome.Id}, Points: {outcome.Points.Points}");
}
else if (v is EducationFeedbackOutcome)
{
var outcome = v as EducationFeedbackOutcome;
Console.WriteLine($"User id: {outcome.LastModifiedBy.User.Id}, Submission id: {outcome.Id}, Feedback: {outcome.Feedback.Text}");
}
}
Related
Currently I have a service that fetches posts from an API:
Future<List<Post>> fetchPosts(int? limit, int? page) async {
final http.Response response = await http.get(Uri.parse('$BACKEND_URL/posts?page=$page&limit=$limit'));
if (response.statusCode == 200) {
List body = jsonDecode(response.body);
List<Post> posts = body.map((var post) => Post.fromJson(post)).toList();
return posts;
}
return [];
}
Currently, when the statusCode is NOT 200, it just returns an empty List.
But when it came to error handling, I thought of returning the List of posts inside another List with an error item:
Looks something like this:
return [data, error];
So if the posts are fetched successfully, it should return:
return [[Post(), Post(), Post()], null];
And if it causes an error, it should return:
return [null, "Failed to get posts"];
I tried a lot of solutions, like changing the method's return type to Future<dynamic>.
But is there a way to type the values for return the two items?
Something like:
Future<List<List<Post>?, String?>> fetchPosts(int? limit, int? page) async {}
Can anyone help?
There are much better ways to handle errors in dart.
Solution #1
Create a class FetchPostsResponse with two properties for message and list of posts. Then change your function to return this class.
class FetchPostsResponse {
final List<Post>? posts;
final String? message;
FetchPostsResponse({
this.posts,
this.message,
});
}
Future<FetchPostsResponse> fetchPosts(int? limit, int? page) async {
final http.Response response =
await http.get(Uri.parse('$BACKEND_URL/posts?page=$page&limit=$limit'));
if (response.statusCode == 200) {
List body = jsonDecode(response.body);
List<Post> posts = body.map((var post) => Post.fromJson(post)).toList();
return FetchPostsResponse(posts: posts);
}
return FetchPostsResponse(message: "Failed to get posts");
}
Solution #2 more maintainable and extensible.
Create an abstract class FetchPostsState and create a new class that extends the abstract class for each state, such as success and failure.
abstract class FetchPostsState {}
class FetchPostsSuccess extends FetchPostsState {
final List<Post> data;
FetchPostsSuccess(this.data);
}
class FetchPostsFail extends FetchPostsState {
final String message;
FetchPostsFail(this.message);
}
Future<FetchPostsState> fetchPosts(int? limit, int? page) async {
final http.Response response = await http.get(Uri.parse('$BACKEND_URL/posts?page=$page&limit=$limit'));
if (response.statusCode == 200) {
List body = jsonDecode(response.body);
List<Post> posts = body.map((var post) => Post.fromJson(post)).toList();
return FetchPostsSuccess(posts);
}
return FetchPostsFail("Failed to get posts");
}
I try to display live data in an MPAndroidChart hosted in an AndroidView.
I get the graph but an UnsupportedOperationException happens when I call addEntry() to update the graph dynamically. Am I doing something wrong?
You find a demo repo in the comments.
#Composable
fun MyLineChart() {
val mainViewModel = viewModel()
val sensorData = mainViewModel.sensorFlow.collectAsState(SensorModel(0F,0F)).value
AndroidView(modifier = Modifier.fillMaxSize(),
factory = { context ->
val lineChart = LineChart(context)
var entries = listOf(Entry(1f,1f))
val dataSet = LineDataSet(entries, "Label").apply { color = Color.Red.toArgb() }
val lineData = LineData(dataSet)
lineChart.data = lineData
lineChart.invalidate()
lineChart
}){
try {
Log.d("TAG", "MyLineChart: Update --- Current thread id: ${Thread.currentThread()}")
it.data.dataSets[0].addEntry(Entry(sensorData.x, sensorData.y))
it.lineData.notifyDataChanged()
it.notifyDataSetChanged()
it.invalidate()
} catch(ex: Exception) {
Log.d("TAG", "MyLineChart: $ex")
}
}
}
The data is sent to the view via the following ViewModel:
#HiltViewModel
class MainViewModel #Inject constructor(#ApplicationContext var appContext: Context) : ViewModel() {
private var rand: Random = Random(1)
val sensorFlow: Flow<SensorModel> = flow<SensorModel> {
while (true) {
delay(1000L)
Log.d("TAG", "sensorFlow: Current thread id: ${Thread.currentThread()}")
emit(SensorModel(rand.nextFloat(), rand.nextFloat()))
}
}
}
You pass entries to LineDataSet, which is an immutable list.
This library seems to have a pretty bad API, because it doesn't ask for a modifiable list as a parameter, but at the same time it doesn't make it modifiable on its side. This causes you to try to modify the immutable list, which leads to an exception.
Replace
var entries = listOf(Entry(1f,1f))
with
val entries = mutableListOf(Entry(1f,1f))
p.s. I can't advise you another graph library as I haven't worked with any, but I would advise you to look for a library with a better API.
try this code:
// it.data.dataSets[0].addEntry(Entry(sensorData.x, sensorData.y))
val entryList = mutableListOf<Entry>()
entryList.add(Entry(sensorData.x, sensorData.y))
val dataSet = LineDataSet(entryList, "Label").apply {
color = Color.Red.toArgb()
}
it.data = LineData(dataSet)
I have an Activity function that reads child elements of a parent in an organization as follows:
[FunctionName("ChildReaderFunction")]
public async Task<List<User>> GetChildren([ActivityTrigger] User parent)
{
var children = await GetChildrenAsync(parent);
return children;
}
public async Task<List<User>> GetChildrenAsync(User parent)
{
var allUsers = new List<User> { parent };
List<User> children = null;
children = await ExecuteQueryAsync("tableName", $"Parent eq '{parent.Id}'");
var taskIndex = 0;
var readTasks = new Task<List<User>>[children.Count(x => x.Childcount > 0)];
foreach (var child in children)
{
if (child.Childcount > 0)
{
readTasks[taskIndex++] = GetChildrenAsync(child);
}
else
{
allUsers.Add(child);
}
}
var validTasks = readTasks.Where(task => task != null).ToList();
if (validTasks.Count > 0)
{
foreach (var result in await Task.WhenAll(validTasks))
{
allUsers.AddRange(result);
}
}
Console.WriteLine($"Got {allUsers.Count} children for {parent.Id}");
return allUsers;
}
This works perfectly when I use premium plan with a timeout of 2 hours. I'm trying to convert this to a consumption plan with a timeout of 10 min. On testing out, I see timeout exception. Is there a way to breakdown this durable function and complete execution in 10 min?
I tried to update this logic by using a queue as follows:
[FunctionName("ChildReaderFunction")]
public async Task<List<User>> GetChildren([ActivityTrigger] User parent)
{
var allUsers = new List<User>();
var directReportEntities = new List<User>();
Queue<User> myQueue = new Queue<Person>();
myQueue.Enqueue(request.Parent);
while (myQueue.Any())
{
var current = myQueue.Dequeue();
if (current.Childcount > 0)
{
var children = await GetChildrenAsync(parent);
foreach (var child in children)
{
myQueue.Enqueue(child);
}
}
allUsers.Add(current);
}
Console.WriteLine($"Got {allUsers.Count} children for {parent.Id}");
return allUsers;
}
public async Task<List<User>> GetChildrenAsync(User parent)
{
return await ExecuteQueryAsync("tableName", $"Parent eq '{parent.Id}'");
}
This also gives a timeout exception. Any suggestions on what other approach I could try?
You might think about trying to figure out which parts of this method are slow. Perhaps it isn't the method itself that is slow but the query to the database. How many rows are you trying to download?
Also, you have a recursive call in your method. That may lead to many queries being executed. Can you think of a different way to grab the data all at once instead of a little bit at a time?
I have an Azure Function with Premium Plan where users from multiple AzureAD groups are read and put to a queue. Currently, I'm looking into converting this function to Durable Function and use consumption plan. Here is my code:
Orchestrator:
public async Task RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var groups = await context.CallActivityAsync<List<AzureADGroup>>("GroupsReaderFunction"), null);
if (groups != null && groups.Count > 0)
{
var processingTasks = new List<Task>();
foreach (var group in groups)
{
var processTask = context.CallSubOrchestratorAsync("SubOrchestratorFunction", group);
processingTasks.Add(processTask);
}
await Task.WhenAll(processingTasks);
}
}
SubOrchestrator:
public async Task RunSubOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var request = context.GetInput<Group>();
var users = await context.CallActivityAsync<List<AzureADUser>>("UsersReaderFunction", request.objectId);
return users;
}
Here is the function that gets users from AzureAD group:
public async Task<List<AzureADUser>> GetUsersInGroup(Guid objectId)
{
IGroupTransitiveMembersCollectionWithReferencesPage members;
members = await GraphServiceClient.Groups[objectId.ToString()].TransitiveMembers.Request().Select("id").GetAsync();
var toReturn = new List<AzureADUser>(ToUsers(members.CurrentPage));
while (members.NextPageRequest != null)
{
members = await members.NextPageRequest.GetAsync();
toReturn.AddRange(ToUsers(members.CurrentPage));
}
return toReturn;
}
private IEnumerable<AzureADUser> ToUsers(IEnumerable<DirectoryObject> fromGraph)
{
foreach (var users in fromGraph)
{
return new AzureADUser { ObjectId = Guid.Parse(user.Id) };
}
}
Number of users in groups vary - one group contains 10 users and another group contains ~500k users. Timeout occurs when reading users from larger groups (> 10 minutes). Is there a faster way to get users from AzureAD group (for example, get users in batches) so that I should be able to use Consumption Plan? Or is there a different way to use Durable Functions (fan in - fan out pattern or some other patterns) for a faster performance?
UPDATE:
var users = new List<AzureADUser>();
public async Task RunSubOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var request = context.GetInput<Group>();
var response = await context.CallActivityAsync<(List<AzureADUser> users, nextPageLink link)>("UsersReaderFunction", request.objectId);
users.AddRange(response.users);
return users;
}
Here response contains 2 values - users from current page and link to next page. I need to keep calling "UsersReaderFunction" activity function until link to next page is null.
var users = new List<AzureADUser>();
public async Task RunSubOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var request = context.GetInput<Group>();
var response = await context.CallActivityAsync<(List<AzureADUser> users, nextPageLink link)>("UsersReaderFunction", request.objectId);
users.AddRange(response.users);
while (response.link != null) {
var response = await context.CallActivityAsync<(List<AzureADUser> users, nextPageLink link)>("UsersReaderFunction", request.objectId);
users.AddRange(response.users);
}
return users;
}
But this is not working. What am I missing?
I am trying to execute query with parameters against local cache or server (if nothing is found in cache).
public async Task<List<T>> Get(IDictionary<string, object> parameters, string resourceName = "", FetchSource fetchSource = FetchSource.None)
{
try
{
var query = resourceName == string.Empty ? EntityQuery.From<T>().WithParameters(parameters) : EntityQuery.From<T>(resourceName).WithParameters(parameters);
var queryResult = await this.ExecuteQuery(query, fetchSource);
var result = queryResult.ToList();
return result;
}
catch (Exception e)
{
return new List<T>(); // return empty result instead
}
}
FetchSource is our enum:
public enum FetchSource
{
None = 0,
FromServer = 1,
FromCache = 2,
FromCacheOrServer = 3
}
And here is ExecuteQuery method:
protected async Task<IEnumerable<T>> ExecuteQuery(EntityQuery<T> query, FetchSource fetchSource = FetchSource.None)
{
//...
if (fetchSource == FetchSource.FromCacheOrServer)
{
var result = query.ExecuteLocally(this.EntityManager); // Throws error
if (result != null && result.Any())
{
return result;
}
return await query.Execute(this.EntityManager);
}
//...
}
When I try to execute query locally this exception is thrown:
{"Unable to cast object of type
'WhereEnumerableIterator`1[StanleySteemer.Nimbus.Client.Common.Model.Proxy.RouteOrder]'
to type
'DataServiceOrderedQuery[StanleySteemer.Nimbus.Client.Common.Model.Proxy.RouteOrder]'."}
Although I couldn't find anything in docs specifically regarding to this subject, I have implemented similar functionality in BreezeJS which was working without issue(UPDATE: it doesn't work correctly):
findWithParametersInCacheOrServer = function (parameters, recordsLimit) {
var query = breeze.EntityQuery
.from(resourceName)
.withParameters(parameters);
var r = executeCacheQuery(query);
if (r) {
if (r.length > recordsLimit) {
return Q.resolve(r);
}
}
return executeQuery(query);
};
function executeCacheQuery(query) {
return entityManagerProvider.manager().executeQueryLocally(query);
}
Data architecture in JavaScript is similar to TempHire example.
Is this a known issue? Is there any workaround for it?
Not sure I understand, neither breeze.js nor breeze.sharp can automatically perform a 'local cache query' that involves parameters. This is because the interpretation of the parameters is only really defined on the server and not on the client.
It sounds as though what you have done is define a custom implementation of your specific 'with parameters' query in breeze.js that completely bypasses Breeze's internal implementation. Is this correct?