Pagination Links in Yii2 Rest API Custom Action Response - rest

I have created my own object/index action in a yii rest controller which does some simple things:
public function actionIndex($name){
$query = Object::find();
$count = $query->count();
$pagination = new Pagination(['totalCount'=>$count]);
$objectList = $query->offset($pagination->offset)->limit($pagination->limit)->all();
return $objectList;
}
When I make a request to: http://localhost:8443/v0/objects?name=warehouse&page=1&per-page=1 I receive the following response:
[
{
"id": 2,
"data": {
"city": "Test",
"name": "ABC Warehouse",
"postal_code": "M1F 4F2",
"street_address": "1234 Street",
"owner": 76,
"created_at": "2016-09-23 15:10:20",
"updated_at": "2017-07-27 11:56:15",
"created_by": 9,
"updated_by": 13
},
"displayData": []
}
]
I would like to include the pagination link information as shown here but am not sure how to go about this
http://www.yiiframework.com/doc-2.0/guide-rest-quick-start.html
HTTP/1.1 200 OK
...
X-Pagination-Total-Count: 1000
X-Pagination-Page-Count: 50
X-Pagination-Current-Page: 1
X-Pagination-Per-Page: 20
Link: <http://localhost/users?page=1>; rel=self,
<http://localhost/users?page=2>; rel=next,
<http://localhost/users?page=50>; rel=last

I assume you're using the yii\rest\ActiveController
A quick look into the yii2 source suggests (to me) that whatever is returned need to implement the yii\data\DataProviderInterface. Right now your code does not return the Pagination object at all to be handled.
Assuming you're Object extends ActiveRecord, try this in your action...
public function actionIndex($name){
$query = Object::find(); // Assumes Object extends ActiveRecord
$countQuery = clone $query; // Good idea to clone query
$count = $countQuery->count();
$pagination = new Pagination(['totalCount'=>$count]);
$query->offset($pagination->offset)->limit($pagination->limit);
return new ActiveDataProvider([
'query' => $query,
'pagination' => $pagination,
]);
}
Note, this code is NOT tested. Hopefully it will help anyway.
--- Edit ---
Also in the yii\rest\ActiveController set this property (as suggested by Scott)
public $serializer = [
'class' => 'yii\rest\Serializer',
'collectionEnvelope' => 'items',
];

Related

How to handle different languages with Google Actions and DiaglogflowApp with Firebase functions

I have configured multiple languages in my Dialogflow agent. I cannot figure out how to detect the language of the request in my firebase function in order to answer with the right language. Is there a standard approach to handle this? I don't see any function to detect the language in https://github.com/actions-on-google/actions-on-google-nodejs
I would expect to be able to do something like this:
const app = new DialogflowApp({request: request, response: response});
if (app.getLang == 'en') {
\\ Do something in english
}
else if (app.getLang == 'es') {
\\ Do something in spanish
}
There is a public sample on the AoG GitHub for Number Genie, which is in both French and English.
In this sample they define JSON objects for English and French locales:
{
"images": {
"cold": {
"url": "COLD.gif",
"altText": "cold genie",
"cardText": [
"Freezing like an ice cave in Antarctica?",
"I can't feel my face anymore",
"Hurry, before I turn into an icicle"
]
},
...
{
"images": {
"cold": {
"url": "COLD.gif",
"altText": "Génie froid",
"cardText": [
"Je me gèle comme un glaçon en Antartique",
"Je ne sens plus mon visage",
"Dépêchez-vous avant que je ne me transforme en glaçon"
]
},
...
Then there is a central strings.js file which will pull the correct string for that locale.
const i18n = require("i18n");
i18n.configure({
"directory": __dirname + "/locales",
"objectNotation": true,
"fallbacks": {
"fr-FR": "fr",
"fr-CA": "fr"
}
});
const prompts = () => ({
"welcome": {
"visual": {
"elements": [
[i18n.__("variants.greeting"), i18n.__("variants.invocation")],
i18n.__("variants.invocationGuess"),
i18n.__("images.intro")
],
"suggestions": onlyNumberSuggestions
}
},
...
Which is then used to map to each intent:
[Actions.GENERATE_ANSWER] () {
this.data.answer = strings.getRandomNumber(strings.numbers.min,
strings.numbers.max);
this.data.guessCount = 0;
this.data.fallbackCount = 0;
this.data.steamSoundCount = 0;
this.ask(strings.prompts.welcome, strings.numbers.min, strings.numbers.max);
}
The locale is set by getting that from the app.getUserLocale() method:
/**
* Get the Dialogflow intent and handle it using the appropriate method
*/
run () {
strings.setLocale(this.app.getUserLocale());
/** #type {*} */
const map = this;
const action = this.app.getIntent();
console.log(action);
if (!action) {
return this.app.ask(`I didn't hear a number. What's your guess?`);
}
map[action]();
}
There's definitely a lot here, and you don't need to do this exactly the same way. app.getUserLocale() should return the current locale, which you can then use in any way that you want to return the response.

Adding a key/value pair to an object in VTL (for API Gateway)

I am writing a mapping template for an AWS API Gateway integration response. I would like to add a key/value pair to the JSON object returned my Lambda function.
My function returns some JSON like this:
{
"id": "1234",
"name": "Foo Barstein"
}
I would like the template to output something like this:
{
"id": "1234",
"name": "Foo Barstein",
"href": "https://example.tld/thingy/1234"
}
And my mapping template looks like this:
#set($thingy = $input.json('$'))
#set($thingy.href = "https://example.tld/thingy/$thingy.id")
$thingy
However, my template outputs the unmodified $thingy, without the href I have tried to add.
I've read the VTL user guide, but to no avail.
Something like this has worked for me:
#set($body = $input.path('$'))
#set($body.href = "https://example.tld/thingy/$body.id")
$input.json('$')
There is no easy way to achieve this but you can workaround it:
## Mapping template
#set($body = $input.body)
#set($id = $input.json('$.id'))
{
"custom": {
"href" : "https://example.tld/thingy/$id"
},
"body": $body
}
And then merge all the keys in AWS.Lambda (if you use Lambda):
## Lambda handler
exports.handler = function(event, context) {
const requestParams = Object.assign({}, event.body, event.custom);
// ... function code
}
And requestParams will be what you want.
Following could do the trick. Beware, untested!
{
#set($payload = $util.parseJson($input.json('$')))
#set($body = "{
#foreach ($mapEntry in $payload.entrySet())
""$mapEntry.key"": ""$mapEntry.value"",
#end
""href"": ""$payload.id""
}")
$body
}

remote autocomplete by typeahead works only on unique queries

I am having problem setting up typeahead with bloodhound on two fields - symbol and name. You can try live version on my DGI portfolio manager and autocomplete remote source here.
Typeahead sometimes works and sometimes it does not.
If I type symbols like "jnj", "mcd", "aapl" it works.
However, when I type string from name like "corporation" and "inc" that have around 3000 objects with this name, it does not work. I doubt it is because it is loading, since json file is served quickly(under 250ms on localhost).
Firstly, I thought symbols work correctly and names are ignored. But I do get proper typeahead for some names: "apple" and "homestreet" for instance.
I believe it only works if there are 1 or 2 results. But I don't understand, json file serves always max 5 results.
Here are my codes:
views.py for autocomplete url:
from haystack.query import SearchQuerySet
import json
def autocomplete(request):
if request.GET.get('q', '') == '':
array = []
else:
sqs = SearchQuerySet().models(Stock)
sqs_symbol = sqs.filter(symbol_auto=request.GET.get('q', ''))
sqs_name = sqs.filter(name_auto=request.GET.get('q', ''))
sqs_result = sqs_symbol | sqs_name
array = []
print sqs_result.count()
for result in sqs_result[:5]:
data = {"symbol": str(result.symbol),
"name": str(result.name),
"tokens": str(result.name).split()
}
array.insert(0, data)
print array
return HttpResponse(json.dumps(array), content_type='application/json')
I added print so I know when it does not work.
search_indexes.py file:
from haystack import indexes
from stocks.models import Stock
class StockIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
symbol = indexes.CharField(model_attr='symbol')
name = indexes.CharField(model_attr='name')
# We add this for autocomplete.
symbol_auto = indexes.EdgeNgramField(model_attr='symbol')
name_auto = indexes.EdgeNgramField(model_attr='name')
def get_model(self):
return Stock
def index_queryset(self, using=None):
"""Used when the entire index for model is updated."""
return self.get_model().objects.all()
And in my template html file:
<script type="text/javascript">
$(function(){
var stocks = new Bloodhound({
datumTokenizer: function (datum) {
return Bloodhound.tokenizers.whitespace(datum.tokens);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
limit: 5,
remote: {
url: "/search/autocomplete/",
replace: function(url, query) {
return url + "?q=" + query;
},
filter: function(stocks) {
return $.map(stocks, function(data) {
return {
tokens: data.tokens,
symbol: data.symbol,
name: data.name
}
});
}
}
});
stocks.initialize();
$('.typeahead').typeahead(null, {
name: 'stocks',
displayKey: 'name',
minLength: 1, // send AJAX request only after user type in at least X characters
source: stocks.ttAdapter(),
templates: {
suggestion: function(data){
return '<p>' + data.name + ' (<strong>' + data.symbol + '</strong>)</p>';
}
}
}).on('typeahead:selected', function (obj, stock) {
window.location.href = "/stocks/detail/" + stock.symbol;
});
});
</script>
EDIT: Some Examples
Json response:
[{"tokens": ["Johnson", "&", "Johnson"], "symbol": "JNJ", "name": "Johnson & Johnson"}]
Not working for "sto":
json response:
[{"tokens": ["QKL", "Stores,", "Inc."], "symbol": "QKLS", "name": "QKL Stores, Inc."}, {"tokens": ["SPDR", "DJ", "STOXX", "50"], "symbol": "FEU", "name": "SPDR DJ STOXX 50 "}, {"tokens": ["Statoil", "ASA"], "symbol": "STO", "name": "Statoil ASA"}, {"tokens": ["STORE", "Capital", "Corporation"], "symbol": "STOR", "name": "STORE Capital Corporation"}, {"tokens": ["StoneMor", "Partners", "L.P."], "symbol": "STON", "name": "StoneMor Partners L.P."}]
It is typeahead.js's bug. It should be fixed in version 0.11.2.

Unable to send data to Sitecatalyst with function CQ_Analytics.record

I am working on a POC involving AEM and site catalyst integration .
I am using AEM’s out of box Geomatrixx outdoors website which already implements site catalyst features.
Data is being populated to report suite via
• Data tracking (on page load)
data-tracking="{'event': ['eventName'], 'values': {'key': 'value', 'nextKey': 'nextValue'}, componentPath: 'myapp/component/mycomponent'}"
• CQ_Analytics.record(after page load, activates on a page).
CQ_Analytics.record({event: 'eventName', values: { valueName: 'VALUE' }, collect: false, options: { obj: this, defaultLinkType: 'X' }, componentPath: '<%=resource.getResourceType()%>'})
UseCase: When, I am adding a product to cart below function gets executed CQ_Analytics.record But unable to send cart addition data to site catalyst .
I have verified same using adobe digital debugger.
Code snippet from /libs/commerce/components/product/product.jsp
function trackCartAdd(form) {
if (CQ_Analytics.Sitecatalyst) {
var productQuantity = Number($("input[name='product-quantity']", form).val() || '1');
var productPrice = Number($("input[name='product-size']:checked", form).data('price').replace(/[^0-9\\.]/g, ''));
var productChildSku = $("input[name='product-size']:checked", form).data('sku')
CQ_Analytics.record({
"event": ["cartAdd"<%= (session.getCartEntryCount() == 0) ? ", 'cartOpen'" : "" %>],
"values": {
"product": [{
"category": "",
"sku": "<%= xssAPI.encodeForJSString(baseProduct.getSKU()) %>",
"price": productPrice * productQuantity,
"quantity": productQuantity,
"evars": {
"childSku": CQ.shared.Util.htmlEncode(productChildSku)
}
}]
},
"componentPath": "<%= xssAPI.encodeForJSString(resource.getResourceType()) %>"
});
}
return true;
}
Note: I have done the product variable mapping for report suite in AEM.
Please guide me .

Facebook API: How to get count of group members

Im working on fb app using PHP client and FQL.Looks like that fb api doesnt support this. You can get max random 500 members with FQL. But when group has more than 500 members there is no way how to get total count of members.
I need only number dont care about member details.
Anyone can help me please?
I have actually found that you cannot, it is by design apparently. I wanted to know this myself about a month ago, and found that no matter what parameters you pass in to the graph api, you can not get past the 500th member. Even if you tell it to start at number 450 and give you 200, it will give you only 450-500.
Here's me asking, and the unfortunate answer:
http://forum.developers.facebook.net/viewtopic.php?id=82134
you can use graph api 2.0 and sdk 4.0
you can use below code
$url1= "https://graph.facebook.com/".$gid."/members?limit=10000&access_token=".$_SESSION['fb_token'];
if(!extension_loaded('curl') && !#dl('curl_php5.so'))
{
return "";
}
$parsedUrl = parse_url($url1);
$ch = curl_init();
$options = array(
CURLOPT_URL => $url1,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => array("Host: " . $parsedUrl['host']),
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_SSL_VERIFYPEER => false
);
curl_setopt_array($ch, $options);
$response_count = #curl_exec($ch);
$json_count = json_decode($response_count);
foreach ($json_count as $item_count)
{
$cnt=count($item_count);
break;
}
You can use the summary parameter which will return a total_count value.
/v2.11/{GROUP_ID}?fields=members.limit(0).summary(true)
It also works when searching for groups:
/v2.11/search?q=stackoverflow&type=group&fields=id,name,members.limit(0).summary(true)
{
"data": [
{
"id": "667082816766625",
"name": "Web Development Insiders",
"members": {
"data": [
],
"summary": {
"total_count": 2087
}
}
},
{
"id": "319262381492750",
"name": "WEB Developers India",
"members": {
"data": [
],
"summary": {
"total_count": 10240
}
}
}...
]
}
Make a query to:
https://graph.facebook.com/v2.11/{group-id}/members?limit=1500&access_token={token}
(replace {group-id} by the numeric group identification and {token} by your access token).
You will get a response like this:
{
"data": [
{
"name": "Foo bar",
"id": "123456",
"administrator": false
},
...
],
"paging": {
"cursors": {
"before": "QVFIUkwzT3FKY0JSS2hENU1NUlZAocm9GelJMcUE3S1cxWWZAPYWx1cUQxcXFHQUNGLUJnWnZAHa0tIdmpMT0U5ZAjRBLUY5Q2ZAGbmwwVTNoSHhLc1BCc2dvVTF3",
"after": "QVFIUkFoU3lYR2tXc09adkg5OGhlbHRWRk1GYkZAzQU1DalRSY05zOVl5aE1tcjRMS3lXLURaVWNMOGZArWTVxS2hPQUVGVWxhbXZAyZA0p3azVKM2hBSEp3YlpR"
}
},
"next": "https://graph.facebook.com/v2.11/123928391023981/members?access_token=EAACEdEose0cBALBDrdgLyVOjzW4mz6G3d3Yj1fTGYqygVgYq0JCDZAi0zYsY90pSSQ9hQZCn3TdwfXIAiyoXH5oUYcA4hOcCI9jztkkUhbBv9tEQ3ZBEEuHpmkm3kmgvk1HNq5mo6BM0hz8XkOLVh3twIdz83KhB9SkqxuxHeFD9GWsQqjys6XTuL2315QZD&pretty=0&limit= 1500&after=QVFIUkFoU3lYR2tXc08adkg5OGhlbHRWYk1GYkZAzQU1DalRSY05zOVl5aQ1tcjRMS3lXLURaVWNMOGZArWTVxS2hPQUVGVWxhbXZAyZA0p3azVKM1hBSEp3YlpR"
}
Then follow this algorithm:
Make a count variable with a 0 value.
Count the objects in the data array of the latest response and add the count to the count variable.
If the latest response has the next property, make a request to the URL which is the next property value and return to the step 2. Otherwise you have finished, the count of members is the count variable value.
This way is not very good because the more members there are in the group the more queries are required. I would be better to parse the number of members from the group page HTML but I can't find reliable way to do it.
Update 2017.10.19: If the Facebook API response size is more then about 345KB, Facebook returns an error: Please reduce the amount of data you're asking for, then retry your request. It is about 1997 members. So you need to set the limit request parameter to 1500 not to face the error.
Update 2018.01.26: There is a way to get a count of members using a single request: https://stackoverflow.com/a/47783306/1118709
Update 2018.01.31: After 90 days from releasing Graph API v2.12 the request will require an access token of an admin of the group. Source: Graph API docs / v2.12 changelog / 90-day breaking changes
You should do it with an FQL query on the group_member table like:
SELECT uid FROM group_member WHERE gid = <group_id> limit 500 offset 500
Example here: Is it possible / how to get number of a particular Facebook Group members (even if number of them is 500+)?