Apigility: How to render embedded objects? - rest

How do I render embedded objects in Apigility? For example, if I have a 'user' object and it composes a 'country' object, should I be rendering the 'country' object as an embedded object? And how should I do this?
I am using the Zend\Stdlib\Hydrator\ArraySerializable. My getArrayCopy() method simply returns an array of properties that I want exposed. The array keys are the property names. The array values are the property values. In the case of user->country, the value is an object, not a scalar.
When I return the user object from UserResource->fetch(), here's how it is rendered:
{
"id": "1",
"firstName": "Joe",
"lastName": "Bloggs",
"status": "Active",
"email": "test#example.com",
"country": {
"code": "AU",
"name": "Australia"
},
"settings": "0",
"_links": {
"self": {
"href": "http://api.mydomain.local/users/1"
}
}
}
Note that 'country' is not in an _embedded field. If it is supposed to be in _embedded, I would have thought that Apigility would automatically do that (since it automatically adds the _links object).
As a related issue, how do I go about returning other rel links, such as back, forward, etc?

The easiest way to get Apigility to render embedded resources is when there is an API/resource associated to the embedded object. What I mean for your example is that you'd have an API resource that has a country entity. In that case, if your getArrayCopy returned the the CountryEntity, Apigility would render it automatically as an embedded resource.
If your getArrayCopy is returning country as an array with code and name, you'll end up with what you saw.
For the other part, the rel links for first, last, prev and next will come from the fetchAll method when you return a Paginator. Your collection extends from this already, but it needs an adapter. The code could look something like this:
public function fetchAll($params)
{
// Return a \Zend\Db\Select object that will retrieve the
// stuff you want from the database
$select = $this->service->fetchAll($params);
$entityClass = $this->getEntityClass();
$entity = new $entityClass();
$hydrator = new \Zend\Stdlib\ArraySerializable();
$prototype = new \Zend\Db\ResultSet\HydratingResultSet($hydrator, $entity);
$paginator = new \Zend\Paginator\Adapter\DbSelect($select, $this->sql, $prototype);
$collectionClass = $this->getCollectionClass();
return new $collectionClass($paginator);
}
There are other paginator adapters as well - an ArrayAdapter which will take in an array of however big and then paginate it so you only get the desired number of results. The downside to this if you use it with database results, you'll potentially be retrieving and discarding a lot of results. The DbSelect paginator will modify the $select object to add the limit and order clause automatically so you only retrieve the bits you need. There are also adapters if you're using DbTableGateway, Iterators or even callbacks. You can also implement your own of course.
Hope this helps. If you have more specific needs or clarification, please comment and I'll do my best.

I posted this example on github.
https://github.com/martins-smb/apigility-renderCollection-example
Hope this helps.

Related

Arbitrary HTTP API Call to Enter Cell Value into a MUTL_PICKLIST Column

I am quite new new to Smartsheets and to programming.
I am using Integromat to update various stuff in Smartsheets - 99% operations are done via a nice interface for dummies.
But I have an issue with one column which is MULTI_PICKLIST and which cannot be processed with native dummy-friendly UI.
Basically, I'm adding a new row and one of the columns on the way is the MULTI_PICKLIST one. In order to enter value into this cell, I need to make an arbitrary HTTP API call.
I know row ID, I know column ID. I just need to construct the body of the HTTP request.
The possible picklist value are: John or Maya or Paul. Assume I need to enter "John" into the column.
Attached, you will find my "progress". I obviously, I'm stuck with the BODY part. Can someone give me a little push, please? I think it's gotta be like 5 lines of code.
This is what I have:
DZ
A few things...
First, the value that you're using for URL doesn't look quite right. It should be in the following format, where {sheetId} is replaced with the ID of the sheet you're updating:
sheets/{sheetId}/rows
Second, I don't think you need the key/value that you've specified for Query String -- I'd suggest that you delete this info.
Next, I'm not sure what the other possible values are for Type (based on your screenshot, it looks like a picklist) -- but if JSON is an option, I'd suggest choosing that option instead of Text.
Finally, here's any example of the correct structure/contents for Body to update a MULTI_PICKLIST cell with the value John -- replace the value of the id property (5225480965908356) with your Row ID and replace the value of the columnId property (8436269809198980) with your Column ID:
[
{
"id": "5225480965908356",
"cells": [
{
"columnId": "8436269809198980",
"objectValue": {
"objectType": "MULTI_PICKLIST",
"values": ["John"]
}
}
]
}
]
If you want to select multiple values for a MULTI_PICKLIST cell, here's an example that specifies two values for the cell (John and Maya):
[
{
"id": "5225480965908356",
"cells": [
{
"columnId": "8436269809198980",
"objectValue": {
"objectType": "MULTI_PICKLIST",
"values": ["John", "Maya"]
}
}
]
}
]
** UPDATE **
My initial answer answer above assumed you wanted to update a cell value in a MULTI-PICKLIST column (b/c you've selected PUT for the Method value in your screenshot -- which is the verb used to update a row). Having re-read your question just now though, it sounds like maybe you want to add a new row...is that correct? If so, then the value for Method should be POST (not PUT), and Body will need to include additional objects within the cells array to specify values of other cells in the new row. The following example request (when used with the verb POST) adds a new row and populates 3 cells in that row, the first of which is a MULTI_PICKLIST cell:
[
{
"cells": [
{
"columnId": "8436269809198980",
"objectValue": {
"objectType": "MULTI_PICKLIST",
"values": ["John"]
}
},
{
"columnId": 6101753539127172,
"value": "test value"
},
{
"columnId": 4055216160040836,
"value": 10
}
]
}
]
More info about the Add Rows request can be found in the Smartsheet API docs: Add Rows.

Retrieve UserName from ServiceNow

I am able to retrieve records for a particular Incident ID using Invoke-RestMethod. However, while retrieving the data, values like Resolved To, Updated By, etc. get populated by a sysid.
Resolved By comes in this format:
https<!>://devinstance.servicenow.com/api/sysid, value= sysid
I would like to view the username instead of the sysid.
The 'User ID' (user_name) isn't on the Incident, it's on the sys_user table, so you'll have to dot-walk to it.
If you're using the table API, you'll need to specify a dot-walked field to return, using the sysparm_fields query parameter.
This is no problem, just specify your endpoint like this:
$uri = "https://YOUR_INSTANCE.service-now.com/api/now/table/incident?sysparm_query=number%3DINC0000001&sysparm_fields=resolved_by.user_name"
I've specified a query for a specific incident number is requested, but you can replace that with whatever your query is.The important part is sysparm_fields=resolved_by.user_name. You'll want to specify any other fields you need here, as well.
The JSON I get as a result of running this API call, is the following:
{
"result": [
{
"resolved_by.user_name": "admin"
}
]
}
Note the element name: "resolved_by.user_name".
Another option for doing this, would be to tell the API to return both display, and actual values by specifying the sysparm_display_value parameter and setting it to all to return both sys_id and display value, or just true to return only display values.
Your URI would then look like this:
https://dev12567.service-now.com/api/now/table/incident?sysparm_query=resolved_byISNOTEMPTY%5Enumber%3DINC0000001&sysparm_display_value=all
And your JSON would contain the following:
"number": {
"display_value": "INC0000001",
"value": "INC0000001"
},
"resolved_by": {
"display_value": "System Administrator",
"link": "https://YOUR_INSTANCE.service-now.com/api/now/table/sys_user/6816f79cc0a8016401c5a33be04be441",
"value": "6816f79cc0a8016401c5a33be04be441"
},
"sys_updated_by": {
"display_value": "admin",
"value": "admin"
},
This would be accessed by:
answer.result[n].resolved_by.display_value

Need to show how much data (In precentage) filled in mongoDb collection

I am using MongoDB for storing data in my application. I need to show the user how much data they filled using percentage.
For example: I have USER collection. In this collection i have several fields. Once the user enter data in their profile. I need to show how much percentage of data they filled in their profile.
In my application. I am using MongoDb and loopback frame work. I didn't defined any property in models.
How to solve this problem.Please help!!
You can add properties in you user model and define percentage against that or you can define one object property like the below mentioned
Suppose you have a User model and in it is settings property, of type object
Settings object will gave 10 keys. 1 keys is 10% then 10 keys filled is
100%. So just check whether user have filled the desired setting and mark a percentage.
so your model will be like
{
"name": "Member",
"base": "User",
"properties": {
"email": {
"type": "string"
},
"settings": {
"type": "object"
}
}
}
and inside your code retrieve the settings object and loop through it to make your percentage
Member.findById(your_id, function(err, usr){
if(!err){
var percentage_completed = 0;
Object.keys(usr.settings).map(function(pro){
if( usr.settings[pro] != "" ){
percentage_completed+=10;
}
})
}
})
I hope this will serve your purpose.

Is it possible to extract a set of database rows with RestTemplate?

I am having difficulties getting multiple datasets out of my database with RestTemplate. I have many routines that extract a single row, with a format like:
IndicatorModel indicatorModel = restTemplate.getForObject(URL + id,
IndicatorModel.class);
and they work fine. However, if I try to extract a set of data, such as:
Map<String, List<S_ServiceCoreTypeModel>> coreTypesMap =
restTemplate.getForObject(URL + id, Map.class);
this returns values in a
Map<String, LinkedHashMap<>>
format. Is there an easy way to return a List<> or Set<> in the desired format?
Fundamentally the issue is that your Java object model does not match the structure of your json document. You are attempting to deserialize a single json element into a java List. Your JSON document looks like:
{
"serviceCoreTypes":[
{
"serviceCoreType":{
"name":"ALL",
"description":"All",
"dateCreated":"2016-06-23 14:46:32.09",
"dateModified":"2016-06-23 14:46:32.09",
"deleted":false,
"id":1
}
},
{
"serviceCoreType":{
"name":"HSI",
"description":"High-speed Internet",
"dateCreated":"2016-06-23 14:47:31.317",
"dateModified":"2016-06-23 14:47:31.317",
"deleted":false,
"id":2
}
}
]
}
But you cannot turn a serviceCoreTypes into a List, you can only turn a Json Array into a List. For instance if you removed the unnecessary wrapper elements from your json and your input document looked like:
[
{
"name": "ALL",
"description": "All",
"dateCreated": "2016-06-23 14:46:32.09",
"dateModified": "2016-06-23 14:46:32.09",
"deleted": false,
"id": 1
},
{
"name": "HSI",
"description": "High-speed Internet",
"dateCreated": "2016-06-23 14:47:31.317",
"dateModified": "2016-06-23 14:47:31.317",
"deleted": false,
"id": 2
}
]
You should be able to then deserialize THAT into a List< S_ServiceCoreTypeModel>. Alternately if you cannot change the json structure, you could create a Java object model that models the json document by creating some wrapper classes. Something like:
class ServiceCoreTypes {
List<ServiceCoreType> serviceCoreTypes;
...
}
class ServiceCoreTypeWrapper {
ServiceCoreType serviceCoreType;
...
}
class ServiceCoreType {
String name;
String description;
...
}
I'm assuming you don't actually mean database, but instead a restful service as you're using RestTemplate
The problem you're facing is that you want to get a Collection back, but the getForObject method can only take in a single type parameter and cannot figure out what the type of the returned collection is.
I'd encourage you to consider using RestTemplate.exchange(...)
which should allow you request for and receive back a collection type.
I have a solution that works, for now at least. I would prefer a solution such as the one proposed by Ben, where I can get the HTTP response body as a list of items in the format I chose, but at least here I can extract each individual item from the JSON node. The code:
S_ServiceCoreTypeModel endModel;
RestTemplate restTemplate = new RestTemplate();
JsonNode node = restTemplate.getForObject(URL, JsonNode.class);
JsonNode allNodes = node.get("serviceCoreTypes");
JsonNode oneNode = allNodes.get(1);
ObjectMapper objectMapper = new ObjectMapper();
endModel = objectMapper.readValue(oneNode.toString(), S_ServiceCoreTypeModel.class);
If anyone has thoughts on how to make Ben's solution work, I would love to hear it.

How to document response fields for an object as Map(HashMap)

I am doing documentation for a REST service returning an object like this:
Map<String, HashMap<Long, String>>
and i find no way to describe response fields for such object.
Let's have a look at my code.
The service:
#RequestMapping(value = "/data", method = RequestMethod.GET)
public Map<String, HashMap<Long, String>> getData()
{
Map<String, HashMap<Long, String>> list = dao.getData();
return list;
}
My unit-test-based documentation:
#Test
public void testData() throws Exception
{
TestUtils.beginTestLog(log, "testData");
RestDocumentationResultHandler document = document(SNIPPET_NAME_PATTERN ,preprocessResponse(prettyPrint()));
document.snippets(
// ,responseFields(
// fieldWithPath("key").description("key description").type("String"),
// fieldWithPath("value").description("value as a Hashmap").type("String"),
// fieldWithPath("value.key").description("value.key description").type("String"),
// fieldWithPath("value.value").description("value.value description").type("String"),
// )
String token = TestUtils.performLogin(mockMvc, "user", "password");
mockMvc
.perform(get(APP_BUILD_NAME + "/svc/data").contextPath(APP_BUILD_NAME)
.header("TOKEN", token)
)
.andExpect(status().is(200))
.andExpect(content().contentType("application/json;charset=UTF-8"))
.andExpect(jsonPath("$").isMap())
.andDo(document);
TestUtils.endTestLog(log, "testData");
}
As you can see the code for response fields is commented out since I haven't had any solution for it yet. I am working on that but i really appreciate your help. Thank you in advance.
Your JSON contains a huge number of different fields. There looks to be over 1000 different entries in the map. Each of those entries is itself a map with a single key-value pair. Those keys all appear to vary as well. Potentially, that gives you over 2000 fields to document:
cancel
cancel.56284
year
year.41685
segment_de_clientele
segment_de_clientele.120705
…
This structure is making it hard to document and is also a strong indicator that it will be hard to consume by clients. Ideally, you would restructure the JSON so that each entry has the same keys and it's only the values the vary from entry to entry. Something like this, for example:
{
"translations": [ {
"name": "cancel",
"id": 56284,
"text": "Exit"
}, {
"name": "year",
"id": 41685,
"text": "Year"
}, {
"name": "segment_de_clientele",
"id": 120705,
"text": "Client segment"
}]
}
This would mean that you only have a handful of fields to document:
translations[]
translations[].name
translations[].id
translations[].text
If that's not possible, then I'd stop trying to use the response fields snippet to document the structure of the response. Instead, you should describe its structure manually in your main Asciidoctor source file.
There are two options:
1> Changing MAP to LIST of objects so that the response fields can be described easily.
2> Putting description manually to index.adoc file.
In my case, I go for option 2 because I have to stick to MAP.