How to get query sys_id of current.sys_id Service Portal (ServiceNow) - 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");
}
...

Related

How to correctly get the results from MgetResponse object?

In our app, we are synchronizing some of the data to elasticsearch, and some of this data is users' records. The app is grails 5.1 and we are using Elasticsearch Java API Client for elasticsearch integration.
The indexing is working perfectly fine, and an example of user data looks like this:
Now, we have this following function that suppose to get the list of users by their ids:
PublicUser[] getAllByIds(Long[] ids) {
MgetRequest request = new MgetRequest.Builder()
.ids(ids.collect { it.toString() }.toList())
.index("users")
.build()
MgetResponse<PublicUser> response = elasticSearchClientProviderService.getClient().mget(
request,
PublicUser.class
)
response.docs().collect {
it.result().source()
}
}
And when the response holds at least one user record, we are getting a list of PulicUser objects -> as expected.
However, if the search result is empty, the eventual return from this function is a list with one null element.
Some investigation
response.docs() holds a single non-existing document (looks like this one is filled with the request data).
And, as a result, the return from this function is (as I mentioned above) list of one null element.
Another observation:
I expected that response object will have .hits(), for the actual results are accessible through: response.hits().hits(). But now of that exist.
The only season I started looking into docs() directly is because if this documentation: https://www.elastic.co/guide/en/elasticsearch/reference/master/docs-multi-get.html
There is a lack of Elasticsearch Java API Client docs. They mostly refer to REST API docs.
What is the correct way to get the list of results from mget request?
For now, I am solving this the following way. Will be glad to see if there is a better way, though.
PublicUser[] getAllByIds(Long[] ids) {
MgetRequest request = new MgetRequest.Builder()
.ids(ids.collect { it.toString() }.toList())
.index("users")
.build()
MgetResponse<PublicUser> response = elasticSearchClientProviderService.getClient().mget(
request,
PublicUser.class
)
List<PublicUser> users = []
response.docs().each {
if (it.result().found()) {
users.add(it.result().source())
}
}
users
}

How to query the knowledgebase using the QnaId

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.

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.

Compression response filter fails on breeze.js Metadata call

I have an http module where I'm adding a response filter below for compression. This works for all API calls except for 1, the call to MetaData. If I remove the [BreezeController] decoration it works fine. I think it has to do with action filter attribute that converts the string return type into an HttpResponse return type with string content.
The error I'm getting is " Exception message: The stream state of the underlying compression routine is inconsistent."
I've done some testing where a method thats defined to return an HttpResponse works fine. So I think its the scenario where the method is defined to return string, and then the action filter changes it to HttpResponse at runtime.
Any ideas how I can get this to work?
Here's the response filter being added in BeginRequest:
HttpApplication app = (HttpApplication)sender;
// Check the header to see if it can accept compressed output
string encodings = app.Request.Headers.Get("Accept-Encoding");
if (encodings == null)
return;
Stream s = app.Response.Filter;
encodings = encodings.ToLower();
if (encodings.Contains("gzip"))
{
app.Response.Filter = new GZipStream(s, CompressionMode.Compress);
app.Response.AppendHeader("Content-Encoding", "gzip");
}
Don't know the specifics of what you're doing but I know that the [BreezeController] attribute strips out filters and adds back just the ones that breeze wants.
One approach might be to define a separate controller (ModelMetadataController) that only serves the metadata. This controller doesn't have the [BreezeController] attribute; it's a plain old Web API controller.
Then you create a "Breeze controller" (ModelController) with all of the usual methods except the Metadata method.
You call the metadata controller from the client during app launch via MetadataStore.fetchMetadata just to get metadata.
Once you have populated a metadataStore in this fashion, you use it in your EntityManager which sends query and save requests to the "real" Web API data controller.
The client code might look something like this:
var ds = new breeze.DataService({
serviceName: 'breeze/Model' // the breeze query & save controller
});
var ms = new MetadataStore({
namingConvention: breeze.NamingConvention.camelCase, // assuming that's what you want
});
ms.addDataService(ds); // associate the metadata-to-come with the "real" dataService
var manager = new breeze.EntityManager({
dataService: ds,
metadataStore: ms
});
// the fun bit: fetch the metadata from a different controller
var promise = ms.fetchMetadata('breeze/ModelMetadata') // the metadata-only controller!
return promise; // wait on it appropriately

servicestack - caching a service response using redis

I have a servicestack service which when called via the browser (restful) Url ex:http://localhost:1616/myproducts, it works fine.
The service method has RedisCaching enabled. So first time it hits the data repository and caches it for subsequent use.
My problem is when I try calling it from a c# client via Soap12ServiceClient. It returns the below error:
Error in line 1 position 183. Expecting element '<target response>'
from namespace 'http://schemas.datacontract.org/2004/07/<target namespace>'..
Encountered 'Element' with name 'base64Binary',
namespace 'http://schemas.microsoft.com/2003/10/Serialization/'.
Below is my Client code:
var endpointURI = "http://mydevelopmentapi.serverhostingservices.com:1616/";
using (IServiceClient client = new Soap12ServiceClient(endpointURI))
{
var request = new ProductRequest { Param1 = "xy23432"};
client.Send<ProductResponse>(request);
}
It seems that the soapwsdl used is giving the problem, but I appear to have used the defaults as generated by servicestack..
Any help will be much appreciated.
Update
I was able over come this error by changing the cache code at the service end:
Code that returned error at client end:
return RequestContext.ToOptimizedResultUsingCache(this.CacheClient, cacheKey,
() =>
new ProductResponse(){CreateDate = DateTime.UtcNow,
products = new productRepository().Getproducts(request)
});
Code that works now:
var result = this.CacheClient.Get<ProductResponse>(cacheKey);
if (result == null)
{
this.CacheClient.Set<ProductResponse>(cacheKey, productResult);
result = productResult;
}
return result;
But I am still curious to know why the first method (RequestContext.ToOptimizedResultUsingCache) returned error at c# client?
But I am still curious to know why the first method (RequestContext.ToOptimizedResultUsingCache) returned error at c# client?
From what I can tell, the ToOptimizedResultUsingCache is trying to pull a specific format (xml, html, json, etc) out of the cache based on the RequestContext's ResponseContentType (see code here and here). When using the Soap12ServiceClient the ResponseContentType is text/html (not sure if this is correct/intentional within ServiceStack). So what ToOptimizedResultUsingCache is pulling out of the cache is a string of html. The html string is being returned to the Soap12ServiceClient and causing an exception.
By pulling directly out of the cache you are bypassing ToOptimizedResultUsingCache's 'format check' and returning something the Soap12ServiceClient can handle.
** If you are using Redis and creating your key with UrnId.Create method you should see a key like urn:ProductResponse:{yourkey}.html
Thanks for your response paaschpa.
I revisited the code and I was able to fix it. Since your response gave me the direction, I have accepted your answer. Below is my fix.
I moved the return statement from RequestContext to the response DTO.
Code which throws error when used via c# client (code was returning entire requestcontext):
return RequestContext.ToOptimizedResultUsingCache(this.CacheClient, cacheKey,
() =>
new ProductResponse(){CreateDate = DateTime.UtcNow,
products = new productRepository().Getproducts(request)
});
Fixed Code (return moved to response DTO):
RequestContext.ToOptimizedResultUsingCache(this.CacheClient, cacheKey,
() => {
return new ProductResponse(){CreateDate = DateTime.UtcNow,
products = new productRepository().Getproducts(request)
}
});

Resources