Getting Audit Record Details from Dynamics 365 to Power BI - dynamics-crm

I have been able to pull down an audit table from Dynamics 365 and load it into Power BI by selecting Get Data, choosing the odata option and using url/api/data/v9.1/audits. I see the column RetrieveAuditDetails, but I don't understand why all the values say Function. Is there a way to extend this to show the old value/new value in the same way you can change, for example, UserIDs to be extended to the full name?

When it comes to audit data, OData/Web API REST endpoint is not so friendly in PowerBI due to the reason that the audit data is stored as delimited values in database. Refer my answer in this SO thread.
If it's a javascript or .net application you can do iterative call using RetrieveAuditDetails function to fetch full details after getting full list using https://crmdev.crm.dynamics.com/api/data/v9.1/audits. This is why you are seeing as Function in there.
For example:
var parameters = {};
var entity = {};
entity.id = "5701259e-59b8-e911-bcd0-00155d0d4a79";
entity.entityType = "audit";
parameters.entity = entity;
var retrieveAuditDetailsRequest = {
entity: parameters.entity,
getMetadata: function() {
return {
boundParameter: "entity",
parameterTypes: {
"entity": {
"typeName": "mscrm.audit",
"structuralProperty": 5
}
},
operationType: 1,
operationName: "RetrieveAuditDetails"
};
}
};
Xrm.WebApi.online.execute(retrieveAuditDetailsRequest).then(
function success(result) {
if (result.ok) {
var results = JSON.parse(result.responseText);
}
},
function(error) {
Xrm.Utility.alertDialog(error.message);
}
);
Update:
On further analysis - there is no big difference between the output schema from the above RetrieveAuditDetails query targeting single auditid or the below filtered audits query targeting single recordid.
https://crmdev.crm.dynamics.com/api/data/v9.1/audits?$filter=_objectid_value eq 449d2fd8-58b8-e911-a839-000d3a315cfc
The fact is either web api or fetchxml, the resultset cannot fetch the important column changedata which contains the changed field values - due to the restriction: Retrieve can only return columns that are valid for read. Column : changedata. Entity : audit
I get this in FetchXML builder:
There is another approach but not PowerBI compatible anyway, using RetrieveRecordChangeHistory to target the recordid to get all the audit collections with old & new values. Example below:
https://crmdev.crm.dynamics.com/api/data/v9.0/RetrieveRecordChangeHistory(Target=#Target)?#Target={%22accountid%22:%22449d2fd8-58b8-e911-a839-000d3a315cfc%22,%22#odata.type%22:%22Microsoft.Dynamics.CRM.account%22}

Related

How to query relational data in ascending order in strapi?

I have this query that works
async find(ctx) {
let { _start, _limit } = ctx.request.query;
console.log(ctx.request.query)
_limit ? 0 : (_limit = 10);
const entities = await strapi.services["course-series"].find({});
return entities.map((entity) => {
// Do I sort them here or in the url query (and how)
entity.courses = entity.courses.slice(_start, _limit);
return sanitizeEntity(entity, { model: strapi.models["course-series"] });
});
}
The idea is that I can load 10 courses from each series at first and then get the next 10...
I just realized that the first 10 I am getting are not the recent ones.
As I commented // Do I sort them here or in the url query (and how)
What version of Strapi do you use?
What does this line do strapi.services["course-series"].find({})? How did you build this find method in the service? What does it do? Does it accept params?
Personally I'd do something like that (assuming you're working with Strapi version > 4:
const entities = await strapi.entityService.findMany('api::course-series.course-series', {
fields: [/* list the course-series fields you want to populate */],
populate: {
courses: {
fields: [/* list the course fields you want to populate */],
sort: 'createdAt:desc', // You can use id, publishedAt or updatedAt here, depends on your sorting prefrences
offset: _start,
limit: _limit // I must admit I haven't tested `offset` and `limit` on the populated related field
}
}
})
// ...the rest of your code, if needed
Read more about Entity Service API here.
Doing it the way you did it, you will always first retrieve the full list of courses for each course-series, and then run costly operations like mapping (the lesser of 2 evils) and above all sorting.

Dynamics 365 API link between ActivityPointer and activitytypecode global option set

I am reading data from the ActivityPointer entity in Dynamics 365 via the API and I want to link the activitytypecode field value to the activitypointer_activitytypecode global option set, which I believe is the correct one. However the values don't seem to match. In the ActivityPointer.activitytypecode field I have values such as:
phonecall
bulkoperation
email
appointment
task
But those values don't appear in the option set definition, using this query: GlobalOptionSetDefinitions(Name='activitypointer_activitytypecode')
The option set has the code values (e.g. 4202 for Email) and the different descriptions in all languages, but nothing matches back to the values on ActivityPointer
Optionset is just key value pairs (4202: Email and so on), If you want to get the formatted text value of optionset (Email, Fax, etc) from your web api query results - then you have to use activitytypecode#OData.Community.Display.V1.FormattedValue to get it. Read more
I recommend this article for complete understanding of CRM activities.
If you are looking for the code integer value in your resultset, that seems to be an issue and the result is not the expected one - old SO thread
The problem is that if you are reading activitytypecode in code, then you will know that you get a string value. This is the logical name of the activity entity, e.g. "email", "phonecall" etc.
If you look at the definition of activitytypecode in Power Apps then it shows it as "Entity name" (i.e. text) but using the classic solution editor it shows as the global activitypointer_activitytypecode option set, which contains values for "Email", "Phone Call" etc.
I am sure that there should be a simple way of converting from activitytypecode (i.e. entity name) to activitypointer_activitytypecode (i.e. option set), but I've yet to find it.
What I am doing is retrieving the global activitypointer_activitytypecode option set, so I have access to all of the text values. Then retrieve details about the entity indicated by activitytypecode, specifically what is of interesting is the display name. Then loop through the option set looking for a case-insensitive match on display name.
This is my C# code:
public int? GetActivityType(IOrganizationService service, string activityTypeCode)
{
// Get all activity types.
var optionSetRequest = new RetrieveOptionSetRequest()
{
Name = "activitypointer_activitytypecode"
};
var optionSetResponse = (RetrieveOptionSetResponse)service.Execute(optionSetRequest);
var optionSetMetadata = (OptionSetMetadata)optionSetResponse.OptionSetMetadata;
var optionValues = new Dictionary<string, int?>(StringComparer.OrdinalIgnoreCase);
foreach (var option in optionSetMetadata.Options)
{
foreach (var optionLabel in option.Label.LocalizedLabels)
{
optionValues[optionLabel.Label] = option.Value;
}
}
// Get the display name for the activity.
var retrieveEntityRequest = new RetrieveEntityRequest
{
EntityFilters = EntityFilters.Entity,
LogicalName = activityTypeCode
};
var retrieveEntityResponse = (RetrieveEntityResponse)service.Execute(retrieveEntityRequest);
LocalizedLabelCollection entityLabels = retrieveEntityResponse.EntityMetadata.DisplayName.LocalizedLabels;
// Look up the display name in the option set values.
foreach (var entityLabel in entityLabels)
{
if (optionValues.TryGetValue(entityLabel.Label, out int? value))
{
return (Schema.GlobalOptionSet.ActivityType?)value;
}
}
// If we get here then we've failed.
return null;
}
That is making two API calls, so best avoided in any situations where performance might be an issue. I'm not saying the code is perfect, but it hasn't let me down yet. Even so, I would recommend making do with the logical names provided by activitytypecode if you can.

sys_id arrays to is one of not displaying records on my report

I am not able to display records on my report.
Report Source: Group Approval(sysapproval_group) table
Condition:Sys Id - is one of - javascript: new GetMyGroupApprovals().getSysIds();
Script Include : MyGroupApproval
Note : Active is checked, Accesible is all application score & Client callable unchecked
var GetMyGroupApprovals = Class.create();
GetMyGroupApprovals.prototype = {
initialize: function() {
},
getSysIds : function getMyGroupMembers(){
var ga = new GlideRecord('sysapproval_group');
ga.addQuery('parent.sys_class_name', '=', 'change_request');
ga.query();
gs.log("TotalRecords1 Before:: " + ga.getRowCount());
var sysIdArray = [];
while(ga.next()){
sysIdArray.push(ga.sys_id);
}
return sysIdArray;
},
type: 'GetMyGroupApprovals'
};
Kindly note that I have to achieve with script approach. I am not able to get records on my report.
This line is probably causing unexpected behavior:
sysIdArray.push(ga.sys_id);
ga.sys_id returns a GlideElement object, which changes for each of the iterations in the GlideRecord, so the contents of sysIdArray will just be an instance of the same object for each row in the result set, but the value will just be the last row in the set.
You need to make sure you push a string to the array by using one of the following methods:
sysIdArray.push(ga.sys_id+''); // implicitly call toString
sysIdArray.push(ga.getValue('sys_id')); // return string value
Quick suggestion, you can use the following to get sys_ids as well:
sysIdArray.push(ga.getUniqueValue());

How to execute Linq queries in Serenity

I have created an application using Serenity framework. I have completed basic functionality for CRUD using serenity. Based on my tables I need to have graphical representations, any charts like high charts, D3 charts or any .
1. How can I get data from the tables using Linq in serenity
Finally I have found the answer for this. We can use sql queries as well as stored procedure to fetch data from DB. I have used stored procedure and linq to get the data from db.
In repository page you can add Linq,
public ListResponse<MyRow> GetUsers(IDbConnection connection)
{
// This must be te Repository
var myRepos = new UserRepository();
// This must be a type of Request (in this case ListRequest)
var request = new ListRequest();
request.Take = 100;
// This must be a type of Response (in this case ListResponse)
var response = new ListResponse<MyRow>();
// This must be called on Repository
var result = myRepos.List(connection, request);
// Data
var data = result.Entities.Where(r => r.Name != "").ToList();
response.Entities = data;
return response;
}
I have already defined MyRow as UserRow like this using MyRow = Entities.UserRow;.
Hope this will help you.

Is it possible to retrieve data of all classes in Parse using single REST URL request?

just a scenario :
I have 4 classes created in Parse cloud database for a particular Application - ClassA, ClassB, ClassC, ClassD.
I can retrieve data related to ClassA using REST URL like - https://api.parse.com/1/classes/ClassA
Is it possible to retrieve data of all 4 classes using single REST URL ?
No, it's not possible to do this. You can query from a single class at a time, and a maximum of 1,000 objects.
A cloud function can make multiple queries and merge the results, meaning that a single REST call (to call the function) could return results from multiple classes (but a maximum of 1,000 objects per query). Something like this:
Parse.Cloud.define("GetSomeData", function(request, response) {
var query1 = new Parse.Query("ClassA");
var query2 = new Parse.Query("ClassB");
query1.limit(1000);
query2.limit(1000);
var output = {};
query1.find().then(function(results) {
output['ClassA'] = results;
return query2.find();
}).then(function(results) {
output['ClassB'] = results;
response.success(output);
}, function(error) {
response.error(error);
});
});

Resources