How to query the knowledgebase using the QnaId - botframework

When there is a multiprompt present, instead of adding buttons to go further I would like to add them directly into my card.
I have the qnaid's of the questions, can I get the answer just with the id?

I think the proper way to do this is to build the qnaState from the answer, grab the text from the prompt, and then make your new query with those parameters.
So first for reference, here is the QnA maker call I'm making.
if (qnAcontext == null) {
qnAcontext = {
PreviousQnaId: 0,
PreviousUserQuery: null
}
}
const qnaResult = await request({
url: url,
method: 'POST',
headers: headers,
json: {
question: query,
top: 3,
context: qnAcontext
}
});
I have this code in a helper function that I call from my qna dialog but I think the location isn't important. You can see I've set a "default" qnAcontext. This is what you will be updating to make your prompt-based call.
From the result, you can get the prompt buttons via
var prompts = null;
if(qnaResult[0].context != null){
prompts = qnaResult[0].context.prompts;
}
I've not done this before so I'm not exactly sure where you can get the text attribute from, but I'm assuming it's at qnaResults[0].context.prompts[0].text. You'll need to take a look at the prompts object to confirm. You also need to create the new state, and grab the first part of the answer. These things can be done via
var qnAcontext = {
PreviousQnaId: qnaResult[0].id,
PreviousUserQuery: activity.text
}
answerText = qnaResult[0].answer;
Now just make another call using the new qnAContext and the prompt text as the query. Technically, if you don't have the follow up question set as context only, you don't have to mess with the qnAcontext. You should just be able to use the prompt text as the next query and the answer should come up. If the answer is context only, you'll need to pass the qnAcontext object, though.
Edit: For a direct answer to your question about querying with qnaId directly, see the link added below by Kyle Delaney.

Related

How to get query sys_id of current.sys_id Service Portal (ServiceNow)

I have a question regarding a small issue that I'm having. I've created a widget that will live on the Service Portal to allow an admin to Accept or Reject requests.
The data for the widget is pulling from the Approvals (approval_approver) table. Under my GlideRecord, I have a query that checks for the state as requested. (Ex. addQuery('state', 'requested'))
To narrow down the search, I tried entering addQuery('sys_id', current.sys_id). When I use this query, my script breaks and I get an error on the Service Portal end.
Here's a sample of the GlideRecord script I've written to Accept.
[//Accept Request
if(input && input.action=="acceptApproval") {
var inRec1 = new GlideRecord('sysapproval_approver');
inRec1.addQuery('state', 'requested');
//inRec1.get('sys_id', current.sys_id);
inRec1.query();
if(inRec1.next()) {
inRec1.setValue('state', 'Approved');
inRec1.setValue('approver', gs.getUserID());
gs.addInfoMessage("Accept Approval Processed");
inRec1.update();
}
}][1]
I've research the web, tried using $sp.getParameter() as a work-around and no change.
I would really appreciate any help or insight on what I can do different to get script to work and filter the right records.
If I understand your question correctly, you are asking how to get the sysId of the sysapproval_approver record from the client-side in a widget.
Unless you have defined current elsewhere in your server script, current is undefined. Secondly, $sp.getParameter() is used to retrieve URL parameters. So unless you've included the sysId as a URL parameter, that will not get you what you are looking for.
One pattern that I've used is to pass an object to the client after the initial query that gets the list of requests.
When you're ready to send input to the server from the client, you can add relevant information to the input object. See the simplified example below. For the sake of brevity, the code below does not include error handling.
// Client-side function
approveRequest = function(sysId) {
$scope.server.get({
action: "requestApproval",
sysId: sysId
})
.then(function(response) {
console.log("Request approved");
});
};
// Server-side
var requestGr = new GlideRecord();
requestGr.addQuery("SOME_QUERY");
requestGr.query(); // Retrieve initial list of requests to display in the template
data.requests = []; // Add array of requests to data object to be passed to the client via the controller
while(requestsGr.next()) {
data.requests.push({
"number": requestsGr.getValue("number");
"state" : requestsGr.getValue("state");
"sysId" : requestsGr.getValue("sys_id");
});
}
if(input && input.action=="acceptApproval") {
var sysapprovalGr = new GlideRecord('sysapproval_approver');
if(sysapprovalGr.get(input.sysId)) {
sysapprovalGr.setValue('state', 'Approved');
sysapprovalGr.setValue('approver', gs.getUserID());
sysapprovalGr.update();
gs.addInfoMessage("Accept Approval Processed");
}
...

Getting Parse Objects via pointers

I am trying to get a Reservation object which contains a pointer to Restaurant.
In Parse Cloud code, i am able to get the restaurants objects associated with Reservations via query.include('Restaurant') in log just before response.success. However, the Restaurants reverted back to pointer when i receive the response on client app.
I tried reverted JSSDK version to 1.4.2 & 1.6.7 as suggested in some answers, but it doesn't work for me.
Parse.Cloud.define('getreservationsforuser', function(request, response) {
var user = request.user
console.log(user)
var query = new Parse.Query('Reservations')
query.equalTo('User', user)
query.include('Restaurant')
query.find({
success : function(results) {
console.log(JSON.stringify(results))
response.success(results)
},
error : function (error) {
response.error(error)
}
})
})
response :
..."restaurant":{"__type":"Pointer",
"className":"Restaurants",
"objectId":"kIIYe7Z0tD"},...
You can't directly send the pointer objects back from cloud code even though you have included it. You need to manually copy the content of that pointer object to a javascript object. Like below:
var restaurant = {}
restaurant["id"] = YOUR_POINTER_OBJECT.id;
restaurant["createdAt"] = YOUR_POINTER_OBJECT.createdAt;
restaurant["custom_field"] = YOUR_POINTER_OBJECT.get("custom_field");
ps: in your code you seem do nothing else other than directly send the response back. I think parse REST api might be a better choice in that case.
It turned out that my code implementation was correct.

How to save a record and immediately use its GUID

I'm executing some javascript from a ribbon button and what I want to do is save the record that I am creating and then immediately use its GUID for some code a bit further on. Each time I try it the GUID is coming back null even though I'm requesting it after the record has been saved. If I try the button again after I've saved it then it works, but not as I'm saving it.
Is there a way to do this?
function RibbonButton_AddProduct()
{
//Save the Record
Xrm.Page.data.entity.save();
LoadProductCreate();
}
function LoadProductCreate()
{
var serverUrl;
var errorMessage = "Context to retrieve the Server URL is not available.";
if (typeof GetGlobalContext != "undefined"){
serverUrl = GetGlobalContext().getServerUrl();
} else {
if (typeof Xrm != "undefined"){
serverUrl = Xrm.Page.context.getServerUrl();
} else {
alert(errorMessage);
return;
}
}
if (serverUrl.match(/\/$/)){
serverUrl = serverUrl.substring(0, serverUrl.length - 1);
}
var recordId = Xrm.Page.data.entity.getId();
alert(recordId);
var url = serverUrl + "/main.aspx?etc=10030&extraqs=%3f_CreateFromId%3d%"+recordId
+"%257d%26_CreateFromType%3d10029%26etc%3d10030%26"
+"pagemode%3diframe%26preloadcache%3d1345465354543&pagetype=entityrecord";
window.open(url);
}
Here’s a different approach to solving this problem.
What you are trying to do is ‘working against the system’ - you are effectively making two save buttons. In the rest of Crm when the Id is required for a ribbon button the record must first be saved. E.g. you can’t use the dialog or workflow buttons on an unsaved record, you also can’t 'add new/existing' to an unsaved record.
So my solution would be to disable the button on unsaved forms, force the user to save the record manually and then allow them to use your button - this is the way Crm is meant to be used, and is the way the rest of Crm will work.
You should not work against the system, you should work with it, you have a product to customise and extend – not change.
If this doesn’t meet your requirement I would suggest uses Greg’s suggestion (1) of having flags, though it sounds a bit messy - but then this is a requirement that inherently is.
You could try one of two things:
Add a hidden boolean attribute to your form(e.g. "new_launchProductCreate"), set it in code prior to save and then read it onLoad.
Instead of setting the value prior to create (and therefore potentially commiting it to the database), you could create a plugin registered against the "Create" step of your record that injects a boolean value into the Entity.Attributes collection as the record is returned to the user. This would prevent the value persisting into the database and running every time your form loads.
You can instead use AJAX to reset the value as you launch your onLoad code so that it doesn't trigger on every form load
Assign the record guid manually, use AJAX to save your record, pop your new window using th enew guid and then reload your original form (so that the form is no longer in an "unsaved" state).
At the risk of being proven wrong as I cannot verify this right away... you will need to save and then reload the page.
The value stored in Xrm.Page.data.entity.getId() is set when the page is loaded/initialised and hence won't be updated when you access it after you have called Save().
It is also why it does work when you reload the page.
Perhaps you could call save and then reload the window adding a querystring variable of your own, to indicate that this event has just occurred?
e.g.
function DoSomething() {
//do your stuff
Xrm.Page.data.entity.save();
//something like - sure someone can do better!
window.location = window.location.href + '&foo=bar';
}
and then register something like this onFormLoad
function OnLoad() {
var queryStringParms = Xrm.Page.context.getQueryStringParameters();
//test to see if your query string param exists here
for (var i in queryStringParams) {
//if you find query string, do extra processing here
}
}

FindItems() and BindToItems() give inconsistent results for EmailMessage.Sender.Address

After quite a lot of debugging, I've refined a complicated Managed EWS problem down to the following two simple-ish test cases. The first one works, the second one fails:
var view = new ItemView(100) { PropertySet = new PropertySet { EmailMessageSchema.Id } };
var findResults = ews.FindItems(WellKnownFolderName.Inbox, view)
var bindResults = ews.BindToItems(findResults.Select(r => r.Id), new PropertySet { EmailMessageSchema.Sender });
// Sanity check
Assert.AreEqual(1, bindResults.Count());
// The results I care about
Assert.AreEqual("David Seiler", bindResults[0].Sender.Name);
Assert.AreEqual("david.seiler#yahoo.com", bindResults[0].Sender.Address);
One might try to cut out the BindToItems() call, and use FindItems() directly:
var view = new ItemView(100) { PropertySet = new PropertySet { EmailMessageSchema.Sender } };
var findResults = ews.FindItems(WellKnownFolderName.Inbox, view)
// This part still works fine
Assert.AreEqual(1, findResults.Count());
// So does this
Assert.AreEqual("David Seiler", findResults[0].Sender.Name);
// ...but this fails! Sender.Address is null
Assert.AreEqual("david.seiler#yahoo.com", findResults[0].Sender.Address);
Can anyone tell me where I've gone wrong? It really seems, from the documentation, as though this should work. Not all properties can be read through FindItems(), it's true, but those properties usually throw when I try to access them, and anyway there's a list of those properties on MSDN and Sender isn't on it. What's going on?
Actually I don't know why, but in the second option, it only load basic information of the sender like the name, but not the Address.
If you want to load all the sender properties but do not want to bind the full message you can add the following line before the first assert
service.LoadPropertiesForItems(findResults.Items, new PropertySet(EmailMessageSchema.Sender));

Should i use threads when executing action method through AJAX?

I am building a questionnarie. When a user clicks on an answer possibility for a multiple choice question (this is a radio button), i call an action method to save this answer.
The code:
<script language="javascript">
$(document).ready(function () {
$('.MCQRadio').click(function () {
var question_id = $(this).attr('question-id');
var mcq_id = $(this).attr('mcq-id');
$.ajax({
url: '/SaveSurveyAnswers/SaveMCQAnswer',
data: { "mcq_id": mcq_id, "question_id": question_id },
success: function (data) {
}
});
});
});
The code to save the answer:
public EmptyResult SaveMCQAnswer(int mcq_id, int question_id)
{
MCQ_Answers mcqa = null;
try
{
mcqa = db.MCQ_Answers.Single(x => x.question_ID == question_id);
}
catch (InvalidOperationException e)
{
}
if (mcqa != null)
{
mcqa.mcq_id = mcq_id;
}
else
{
MCQ_Answers mcq_answer = new MCQ_Answers()
{
question_ID = question_id,
respondent_id = 1
};
db.MCQ_Answers.AddObject(mcq_answer);
}
db.SaveChanges();
return new EmptyResult();
}
If a question has 5 answer possibilities, and i click on them randomly and fast, and then go back to the previous page, ie, when i return the correct answer wont be saved. Should i use threading to make sure the correct answer is saved? And how?
Thanks
rather than saving your answer by post all the time, you can just create a JSOn object and save the answers within json. you can then at the end post all completed answers in one go.
take a look at this: http://msdn.microsoft.com/en-us/scriptjunkie/ff962533
basically this will allow you to store session data - json on the remote machine you then just need an add, delete function and away you go....
i use this to huge extent in an application that would require the server to be updated with the location of objects on a canvas, however with sessvars i just keep all the X and Y locations within there and do a final push of JSON when i am done.
if you change pages, you can then get your values from the JSON object without a server call.
as a note you may also be better off with tabs or hiden sections of form, and therfor reduce the need to re-populate say page1, page2 etc as they will already be there, just hidden!

Resources