Correct themes visualisation
authorymh <ymh.work@gmail.com>
Fri, 16 Dec 2016 17:43:07 +0100
changeset 474 245b4df137d3
parent 473 1b8b29c0e95c
child 475 dbb941b9fb66
Correct themes visualisation
cms/app-client/app/components/.gitkeep
cms/app-client/app/components/sorting-component.js
cms/app-client/app/routes/tabs/thematiques.js
cms/app-client/app/serializers/theme.js
cms/app-client/app/styles/components/sorting-component.scss
cms/app-client/app/templates/components/sorting-component.hbs
cms/app-client/app/templates/tabs/thematiques.hbs
cms/app-client/mirage/config.js
cms/app-client/mirage/serializers/discourse.js
cms/app-client/mirage/serializers/language.js
cms/app-client/mirage/serializers/theme.js
common/corpus-common-addon/addon/components/doc-subject.js
common/corpus-common-addon/addon/services/resolver-service.js
common/corpus-common-addon/package.json
server/src/app/Http/Controllers/Api/ThemeController.php
server/src/tests/Controllers/ThemeControllerTest.php
--- a/cms/app-client/app/components/sorting-component.js	Tue Dec 13 23:30:54 2016 +0100
+++ b/cms/app-client/app/components/sorting-component.js	Fri Dec 16 17:43:07 2016 +0100
@@ -13,21 +13,41 @@
     maximum: Ember.computed('themes.[]', function() { return Math.max(...this.get('themes').mapBy('count')); }),
 
     more: true,
-
-    themes: [],
-    themesObserver: Ember.observer('themes.@each', function() {
-        this.$('ul').on('scroll', Ember.run.bind(this, this.scrolling));
+    hasMore: Ember.computed('themes.[]', 'total', function() {
+      let themes = this.get('themes');
+      let total = this.get('total');
+      return !themes || themes.get('length') < total;
     }),
 
-    scrolling: function(event) {
-        if(Ember.$(event.target).scrollTop() !== 0 ) {
-            if(Ember.$(event.target).scrollTop() + Ember.$(event.target).height() > Ember.$(event.target).get(0).scrollHeight - 50) {
+    themes: null,
+    total: Ember.computed('themes.[]', function() {
+      let themes = this.get('themes');
+      return !!(themes)?themes.get('meta').total:0;
+    }),
+
+    scrolling: function(target) {
+        if(Ember.$(target).scrollTop() !== 0 ) {
+            if(Ember.$(target).scrollTop() + Ember.$(target).height() > Ember.$(target).get(0).scrollHeight - 50) {
                 this.triggerAction({ action:'loadMore', target: this });
-                Ember.$(event.target).off('scroll');
             }
         }
     },
 
+    moreObserver: Ember.observer('more', function() {
+      let more = this.get('more');
+      let self = this;
+      if(!more) {
+        this.set('scrollHandler', this.$('ul').on('scroll', function(event) { Ember.run.throttle(self, function() { this.scrolling(event.target); }, 150); }));
+      } else {
+        this.$('ul').off('scroll', this.get('scrollHandler'));
+      }
+    }),
+
+    filterObserver: Ember.observer('filter.date.[]', 'filter.discourse.[]', 'filter.language', 'filter.location', 'filter.theme.[]', function() {
+      this.set('more', true);
+      this.sendAction('resetIndex');
+    }),
+
     actions: {
 
         setQueryParameters: function(id) {
--- a/cms/app-client/app/routes/tabs/thematiques.js	Tue Dec 13 23:30:54 2016 +0100
+++ b/cms/app-client/app/routes/tabs/thematiques.js	Fri Dec 16 17:43:07 2016 +0100
@@ -3,64 +3,68 @@
 
 export default Ember.Route.extend({
 
-    player: Ember.inject.service(),
-    filter: Ember.inject.service(),
+  player: Ember.inject.service(),
+  filter: Ember.inject.service(),
 
-    index: 0,
-    limit: 40,
-    sort: 'alphabetical',
+  index: 0,
+  limit: 40,
+  sort: 'alphabetical',
+  isFetching: false,
 
-    themes: [],
+  themes: null,
+  total: 0,
 
-    model: Ember.observer('index', function() {
-        var self = this;
-        var filterQueryParams = _.clone(this.get('filter').get('queryParamsValues'));
-        var promise = this.store.query('theme', _.merge(filterQueryParams, {
-            'limit': this.get('limit'),
-            'index': this.get('index'),
-            'sort': this.get('sort')
-        }));
-        promise.then(function(value) {
-            if (self.get('themes').length) {
-                value = self.get('themes').pushObjects(value.get('content'));
-            }
-            self.set('themes', value);
-        });
-        return promise;
-    }),
+  model() {
+    var filterQueryParams = _.clone(this.get('filter').get('queryParamsValues'));
+    var promise = this.get('store').query('theme', _.merge(filterQueryParams, {
+      'limit': this.get('limit'),
+      'index': this.get('index'),
+      'sort': this.get('sort')
+    }));
+    promise.then(response => {
+      console.log("MODEL", response, response.get('content'), response.get('meta').total);
+      this.set('themes', response.get('content'));
+      this.set('total', response.get('meta').total);
+    });
+    return promise;
+  },
+
+  activate: function () {
+    this.get('player').set('window', false);
+  },
 
-    setupController: function(controller) {
-        this._super(...arguments);
-        controller.set('themes', this.get('themes'));
+  actions: {
+
+    loadMore: function() {
+      if(this.get('isFetching') || (this.get('themes').length === this.get('total'))) {
+        return;
+      }
+      this.set('isFetching', true);
+      this.set('index', this.get('index') + 1);
+      var filterQueryParams = _.clone(this.get('filter').get('queryParamsValues'));
+      this.get('store').query('theme', _.merge(filterQueryParams, {
+        'limit': this.get('limit'),
+        'index': this.get('index'),
+        'sort': this.get('sort')
+      })).then(response => { // success
+        this.get('themes').pushObjects(response.get('content'));
+        this.set('isFetching', false);
+      }, () => { // fail
+        this.set('isFetching', false);
+      });
     },
 
-    deactivate: function () {
-        this.set('themes', []);
-    },
-
-    activate: function() {
-        this.get('player').set('window', false);
+    resetIndexQueryParams: function () {
+      this.set('index', 0);
+      this.refresh();
     },
 
-    actions: {
-
-        setIndexQueryparams: function() {
-            this.set('index', this.get('index') + 1);
-        },
-
-        setSortQueryparams: function(sort) {
-            this.set('sort', sort);
-            this.get('themes').get('content').clear();
-            if(this.get('index') === 0) {
-                // Force property reset to trigger request
-                this.propertyWillChange('index');
-                this.set('index', 0);
-                this.propertyDidChange('index');
-            } else {
-                this.set('index', 0);
-            }
-        }
-
+    setSortQueryparams: function (sort) {
+      this.set('sort', sort);
+      this.set('index', 0);
+      this.refresh();
     }
 
+  }
+
 });
--- a/cms/app-client/app/serializers/theme.js	Tue Dec 13 23:30:54 2016 +0100
+++ b/cms/app-client/app/serializers/theme.js	Fri Dec 16 17:43:07 2016 +0100
@@ -16,7 +16,8 @@
             });
         });
         return {
-            'data': data
+            'data': data,
+            'meta': payload['meta']
         };
     }
 
--- a/cms/app-client/app/styles/components/sorting-component.scss	Tue Dec 13 23:30:54 2016 +0100
+++ b/cms/app-client/app/styles/components/sorting-component.scss	Fri Dec 16 17:43:07 2016 +0100
@@ -94,8 +94,7 @@
     background-color: $corpus-black;
     color: $corpus-white;
     cursor: pointer;
-    position: absolute;
-    bottom: 35px;
-    left: 50%;
+    margin-left: 50%;
+    margin-top: 1em;
     transform: translateX(-50%);
 }
--- a/cms/app-client/app/templates/components/sorting-component.hbs	Tue Dec 13 23:30:54 2016 +0100
+++ b/cms/app-client/app/templates/components/sorting-component.hbs	Fri Dec 16 17:43:07 2016 +0100
@@ -9,8 +9,8 @@
 <div class="tags{{if more " hasMore"}}">
     <ul>
     {{#each themes as |theme|}}
-        <li {{ action 'setQueryParameters' theme.id }} class="{{ popularity theme.count minimum maximum }}{{if (contains filter.theme theme.id) ' selected'}}">{{ theme.label }} ({{ theme.count }})</li>
+        <li {{ action 'setQueryParameters' theme.id }} class="{{ popularity theme.count minimum maximum }}{{if (contains filter.theme theme.id) ' selected'}}">{{ theme.label }}&nbsp;({{ theme.count }})</li>
     {{/each }}
+      {{#if (and more hasMore)}}<li><button class="more" {{ action 'loadMore' }}>Voir plus</button></li>{{/if}}
     </ul>
-    {{#if more}}<button class="more" {{ action 'loadMore' }}>Voir plus</button>{{/if}}
 </div>
\ No newline at end of file
--- a/cms/app-client/app/templates/tabs/thematiques.hbs	Tue Dec 13 23:30:54 2016 +0100
+++ b/cms/app-client/app/templates/tabs/thematiques.hbs	Fri Dec 16 17:43:07 2016 +0100
@@ -1,3 +1,8 @@
 <div id="tabs-thematiques">
-    {{ sorting-component themes=model load='setIndexQueryparams' sort='setSortQueryparams' }}
+    {{ sorting-component
+        themes=model
+        load='loadMore'
+        sort='setSortQueryparams'
+        resetIndex='resetIndexQueryParams'
+    }}
 </div>
\ No newline at end of file
--- a/cms/app-client/mirage/config.js	Tue Dec 13 23:30:54 2016 +0100
+++ b/cms/app-client/mirage/config.js	Fri Dec 16 17:43:07 2016 +0100
@@ -1,7 +1,6 @@
 import ENV from 'app-client/config/environment';
 import _ from 'lodash/lodash';
 import * as constants from 'corpus-common-addon/utils/constants';
-import Collection from 'ember-cli-mirage/orm/collection';
 
 export default function() {
 
@@ -31,49 +30,13 @@
         return transcripts.find(id).transcript;
     });
 
-    this.get('/stats/languages', ({languages}, request) => {
-      let qParams = request.queryParams['discourse'];
-      if(qParams) {
-        var res = [];
-        let allLanguages = languages.all().models;
-        let i=0;
-        while(i<allLanguages.length && res.length < (allLanguages.length/Math.pow(2,qParams.length))) {
-          let d = allLanguages[i++];
-          d.count = Math.max(Math.floor(d.count / 2), 1);
-          res.push(d);
-        }
-
-        return new Collection('language', res);
-      } else {
-        return languages.all();
-      }
-
-    });
+    this.get('/stats/languages', 'languages');
 
     this.get('/stats/geostats', 'geostats');
 
     this.get('/stats/themes', 'themes');
 
-    this.get('/stats/discourses', ({discourses}, request) => {
-
-      let qParams = request.queryParams['discourse'];
-      if(qParams) {
-        var res = [];
-        let allDiscourses = discourses.all().models;
-        let i=0;
-        while(i<allDiscourses.length && res.length < (allDiscourses.length/Math.pow(2,qParams.length))) {
-          let d = allDiscourses[i++];
-          if(!_.contains(request.queryParams.discourse, d.id)) {
-            d.count = Math.max(Math.floor(d.count / 2), 1);
-            res.push(d);
-          }
-        }
-
-        return new Collection('discourse', res);
-      } else {
-        return discourses.all();
-      }
-    });
+    this.get('/stats/discourses', 'discourses');
 
     this.get('/stats/datestats', 'datestats');
 
--- a/cms/app-client/mirage/serializers/discourse.js	Tue Dec 13 23:30:54 2016 +0100
+++ b/cms/app-client/mirage/serializers/discourse.js	Fri Dec 16 17:43:07 2016 +0100
@@ -3,7 +3,24 @@
 import _ from 'lodash/lodash';
 
 export default JSONAPISerializer.extend({
-    serialize(response) {
-        return { 'discourses' : _(response.models).map((discourse) => { return [discourse.id, {count: discourse.count, label: discourse.label}];}).object().value()};
+    serialize(response, request) {
+
+      let json = JSONAPISerializer.prototype.serialize.apply(this, arguments);
+
+      let qParams = request.queryParams['discourse'];
+      if(qParams) {
+        json =  { 'data': _.map(json.data.slice(0, json.data.length/Math.pow(2,qParams.length)), (d) => {
+          let res = _.clone(d);
+          res.attributes.count = Math.max(Math.floor(d.attributes.count / 2), 1);
+          return res;
+        })};
+      }
+
+      return { 'discourses' : _(json.data).map((discourse) => {
+        return [
+          discourse.id,
+          { count: discourse.attributes.count, label: discourse.attributes.label}
+        ];
+      }).object().value()};
     }
 });
--- a/cms/app-client/mirage/serializers/language.js	Tue Dec 13 23:30:54 2016 +0100
+++ b/cms/app-client/mirage/serializers/language.js	Fri Dec 16 17:43:07 2016 +0100
@@ -3,7 +3,19 @@
 import _ from 'lodash/lodash';
 
 export default JSONAPISerializer.extend({
-    serialize(response) {
-        return {'languages' : _(response.models).map((lang) => { return [lang.id, lang.count];}).object().value()};
+    serialize(response, request) {
+
+      let json = JSONAPISerializer.prototype.serialize.apply(this, arguments);
+
+      let qParams = request.queryParams['discourse'];
+      if(qParams) {
+        json =  { 'data': _.map(json.data.slice(0, json.data.length/Math.pow(2,qParams.length)), (lang) => {
+          let resLang = _.clone(lang);
+          resLang.attributes.count = Math.max(Math.floor(resLang.attributes.count / 2), 1);
+          return resLang;
+        })};
+      }
+
+      return {'languages' : _(json.data).map((lang) => { return [lang.id, lang.attributes.count];}).object().value()};
     }
 });
--- a/cms/app-client/mirage/serializers/theme.js	Tue Dec 13 23:30:54 2016 +0100
+++ b/cms/app-client/mirage/serializers/theme.js	Fri Dec 16 17:43:07 2016 +0100
@@ -4,33 +4,52 @@
 
 export default JSONAPISerializer.extend({
 
-    serialize: function(response, request) {
-        // Remove models with no name
-        response.models = response.models.filter(element => element.label);
-        // Alphabetical order
-        if(request.queryParams.sort === 'alphabetical') {
-            response.models.sort(function(a, b) {
-                var aLabel = a.label.toUpperCase();
-                var bLabel = b.label.toUpperCase();
-                if (aLabel < bLabel) { return -1; }
-                if (aLabel > bLabel) { return 1; }
-                return 0;
-            });
-        // Descending order
-        } else if(request.queryParams.sort === 'descending') {
-            response.models.sort(function(a, b) {
-                return b.count - a.count;
-            });
-        }
-        var array = [];
-        if(typeof request.queryParams.index !== 'undefined' && typeof request.queryParams.limit !== 'undefined') {
-            var begin = parseInt(request.queryParams.index * request.queryParams.limit) || 0;
-            var end = parseInt(begin + (request.queryParams.limit - 1));
-            array = response.models.slice(begin, end);
-        } else {
-            array = response.models;
-        }
-        return { 'themes': _(array).map((theme) => { return [theme.id, {count: theme.count, label: theme.label}];}).object().value() };
+  serialize: function (response, request) {
+
+    // Remove models with no name
+    response.models = response.models.filter(element => element.label);
+
+    let qParams = request.queryParams['theme'];
+    if (qParams) {
+      response.models = response.models
+        .slice(0, response.models.length / Math.pow(2, qParams.length))
+        .map(t => {
+          t.count = Math.max(Math.floor(t.count / 2), 1);
+          return t;
+        });
     }
 
+    let total = response.models.length;
+
+    // Alphabetical order
+    if (request.queryParams.sort === 'alphabetical') {
+      response.models.sort(function (a, b) {
+        var aLabel = a.label.toUpperCase();
+        var bLabel = b.label.toUpperCase();
+        if (aLabel < bLabel) { return -1; }
+        if (aLabel > bLabel) { return 1; }
+        return 0;
+      });
+      // Descending order
+    } else if (request.queryParams.sort === 'descending') {
+      response.models.sort(function (a, b) {
+        return b.count - a.count;
+      });
+    }
+    var array = [];
+    if (typeof request.queryParams.index !== 'undefined' && typeof request.queryParams.limit !== 'undefined') {
+      var begin = parseInt(request.queryParams.index) * parseInt(request.queryParams.limit) || 0;
+      var end = begin + parseInt(request.queryParams.limit);
+      array = response.models.slice(begin, end);
+    } else {
+      array = response.models;
+    }
+    return {
+      themes: _(array).map((theme) => { return [theme.id, { count: theme.count, label: theme.label }]; }).object().value(),
+      meta: {
+        total: total
+      }
+    };
+  }
+
 });
--- a/common/corpus-common-addon/addon/components/doc-subject.js	Tue Dec 13 23:30:54 2016 +0100
+++ b/common/corpus-common-addon/addon/components/doc-subject.js	Fri Dec 16 17:43:07 2016 +0100
@@ -3,39 +3,44 @@
 import layout from '../templates/components/doc-subject';
 
 export default Ember.Component.extend({
-    layout: layout,
-    tagName: "span",
-    displayLabel: Ember.computed('label', 'resolvedLabel', function() {
-      return this.get('label') || this.get('resolvedLabel');
-    }),
-    bnfResolver: Ember.inject.service(),
+  layout: layout,
+  tagName: 'span',
+  displayLabel: Ember.computed('label', 'resolvedLabel', function () {
+    return this.get('label') || this.get('resolvedLabel');
+  }),
+  bnfResolver: Ember.inject.service(),
 
-    _resolveBnfIds: Ember.on('init', Ember.observer('url', 'label', function() {
-      if(this.get('label')) {
-        if (!(this.get('isDestroyed') || this.get('isDestroying'))) {
-          this.set('resolvedLabel', this.get('label'));
-        }
-        return;
+  _resolveBnfIds: Ember.on('init', Ember.observer('url', 'label', function () {
+    if (this.get('label')) {
+      if (!(this.get('isDestroyed') || this.get('isDestroying'))) {
+        this.set('resolvedLabel', this.get('label'));
       }
-      this.get('bnfResolver').getLabel(this.get('url')).then(function(str) {
-        if (!(this.get('isDestroyed') || this.get('isDestroying'))) {
-          this.set('resolvedLabel', str);
-        }
-      }.bind(this));
-    })),
+
+      return;
+    }
+    if (this.get('bnfResolver')) {
+      this.get('bnfResolver').getLabel(this.get('url'))
+        .then(function (str) {
+          if (!(this.get('isDestroyed') || this.get('isDestroying'))) {
+            this.set('resolvedLabel', str);
+          }
+        }.bind(this));
+    }
+  })),
 
-    code: Ember.computed('url', function() {
-      var rawSubject = this.get('url');
-      if(rawSubject) {
-        if(rawSubject.startsWith(constants.BNF_BASE_URL)) {
-          return rawSubject.substr(constants.BNF_BASE_URL.length);
-        }
-        else if (rawSubject.startsWith(constants.BNF_ARK_BASE_URL)) {
-          return rawSubject.substr(constants.BNF_ARK_BASE_URL.length);
-        }
+  code: Ember.computed('url', function () {
+    const rawSubject = this.get('url');
+
+    if (rawSubject) {
+      if (rawSubject.startsWith(constants.BNF_BASE_URL)) {
+        return rawSubject.substr(constants.BNF_BASE_URL.length);
+      } else if (rawSubject.startsWith(constants.BNF_ARK_BASE_URL)) {
+        return rawSubject.substr(constants.BNF_ARK_BASE_URL.length);
       }
-      return rawSubject;
-    }),
-    isSubjectLink: Ember.computed.match('url', /^http\:\/\//)
+    }
+
+    return rawSubject;
+  }),
+  isSubjectLink: Ember.computed.match('url', /^http\:\/\//)
 
 });
--- a/common/corpus-common-addon/addon/services/resolver-service.js	Tue Dec 13 23:30:54 2016 +0100
+++ b/common/corpus-common-addon/addon/services/resolver-service.js	Fri Dec 16 17:43:07 2016 +0100
@@ -105,7 +105,11 @@
         .then(function (names) {
           delete this.get('promises').storeKey; /* eslint prefer-reflect: "off" */
 
-          return store.set(storeKey, names[this.getReturnDictKey(objectId)]);
+          if (names) {
+            return store.set(storeKey, names[this.getReturnDictKey(objectId)]);
+          }
+
+          return null;
         }.bind(this));
       this.get('promises')[storeKey] = namePromise;
     }
--- a/common/corpus-common-addon/package.json	Tue Dec 13 23:30:54 2016 +0100
+++ b/common/corpus-common-addon/package.json	Fri Dec 16 17:43:07 2016 +0100
@@ -21,7 +21,9 @@
   },
   "dependencies": {
     "chalk": "^1.1.3",
-    "ember-cli-babel": "^5.1.7"
+    "ember-cli-babel": "^5.1.7",
+    "ember-cli-htmlbars": "^1.0.10",
+    "ember-cli-htmlbars-inline-precompile": "^0.3.3"
   },
   "devDependencies": {
     "bower": "^1.8.0",
@@ -30,8 +32,6 @@
     "ember-cli": "2.10.0",
     "ember-cli-app-version": "^2.0.0",
     "ember-cli-dependency-checker": "^1.3.0",
-    "ember-cli-htmlbars": "^1.0.10",
-    "ember-cli-htmlbars-inline-precompile": "^0.3.3",
     "ember-cli-inject-live-reload": "^1.4.1",
     "ember-cli-jshint": "^2.0.1",
     "ember-cli-qunit": "^3.0.1",
--- a/server/src/app/Http/Controllers/Api/ThemeController.php	Tue Dec 13 23:30:54 2016 +0100
+++ b/server/src/app/Http/Controllers/Api/ThemeController.php	Fri Dec 16 17:43:07 2016 +0100
@@ -93,6 +93,11 @@
                                     "size" => ($termSize==0)?2147483647:$termSize,
                                     "order" => [ $order_field => $order_dir ]
                                 ]
+                            ],
+                            "subjects_count" => [
+                                "cardinality" => [
+                                    "field" => "subject.label_code"
+                                ]
                             ]
                         ]
                     ]
@@ -128,7 +133,7 @@
             ];
         }
 
-        return response()->json(['themes' => $themes ]);
+        return response()->json(['themes' => $themes, 'meta' => [ 'total' => $esRes['aggregations']['subjects']['subjects_count']['value']] ]);
 
     }
 
--- a/server/src/tests/Controllers/ThemeControllerTest.php	Tue Dec 13 23:30:54 2016 +0100
+++ b/server/src/tests/Controllers/ThemeControllerTest.php	Fri Dec 16 17:43:07 2016 +0100
@@ -41,6 +41,11 @@
                                     "order" => [ '_count' => 'desc' ],
                                     "include" => ".*\|bnf\|.*"
                                 ]
+                            ],
+                            "subjects_count" => [
+                                "cardinality" => [
+                                    "field" => "subject.label_code"
+                                ]
                             ]
                         ]
                     ]
@@ -95,6 +100,9 @@
                                 "key" => "arbres|bnf|ark:/12148/cb11934786x",
                                 "doc_count" => 989
                             ] ]
+                        ],
+                        "subjects_count" => [
+                            "value" => 999
                         ]
                     ]
                 ]
@@ -109,7 +117,7 @@
             "http://ark.bnf.fr/ark:/12148/cb11933145f" => ['label' => 'plantes', 'count' => 995  ],
             "http://ark.bnf.fr/ark:/12148/cb11930908q" => ['label' => 'animaux sauvages', 'count' => 989 ],
             "http://ark.bnf.fr/ark:/12148/cb11934786x" => ['label' => 'arbres', 'count' => 989 ]
-        ]]);
+        ], "meta" => ['total' => 999]]);
 
     }
 
@@ -132,6 +140,11 @@
                                     "size" => config('corpusparole.theme_default_limit'),
                                     "order" => [ '_count' => 'desc' ]
                                 ]
+                            ],
+                            "subjects_count" => [
+                                "cardinality" => [
+                                    "field" => "subject.label_code"
+                                ]
                             ]
                         ]
                     ]
@@ -192,6 +205,9 @@
                                 "key" => "arbres|bnf|ark:/12148/cb11934786x",
                                 "doc_count" => 989
                             ] ]
+                        ],
+                        "subjects_count" => [
+                            "value" => 999
                         ]
                     ]
                 ]
@@ -208,7 +224,7 @@
             "http://lexvo.org/id/iso639-3/bzh" => ['label' => 'Breton', 'count' => 992 ],
             "http://ark.bnf.fr/ark:/12148/cb11930908q" => ['label' => 'animaux sauvages', 'count' => 989 ],
             "http://ark.bnf.fr/ark:/12148/cb11934786x" => ['label' => 'arbres', 'count' => 989 ]
-        ]]);
+        ], "meta" => ['total' => 999]]);
 
     }
 
@@ -232,6 +248,11 @@
                                     "order" => [ '_count' => 'desc' ],
                                     "include" => ".*\|lxv\|.*"
                                 ]
+                            ],
+                            "subjects_count" => [
+                                "cardinality" => [
+                                    "field" => "subject.label_code"
+                                ]
                             ]
                         ]
                     ]
@@ -268,6 +289,9 @@
                                 "key" => "Breton|lxv|bzh",
                                 "doc_count" => 992
                             ] ]
+                        ],
+                        "subjects_count" => [
+                            "value" => 999
                         ]
                     ]
                 ]
@@ -276,7 +300,7 @@
         $this->seeJsonEquals(["themes" => [
             "http://lexvo.org/id/iso639-3/fra" => ['label' => 'Français', 'count' => 1002 ],
             "http://lexvo.org/id/iso639-3/bzh" => ['label' => 'Breton', 'count' => 992 ]
-        ]]);
+        ], "meta" => ['total' => 999]]);
 
     }
 
@@ -299,6 +323,11 @@
                                     "order" => $order,
                                     "include" => ".*\|bnf\|.*"
                                 ]
+                            ],
+                            "subjects_count" => [
+                                "cardinality" => [
+                                    "field" => "subject.label_code"
+                                ]
                             ]
                         ]
                     ]
@@ -329,6 +358,9 @@
                         "doc_count_error_upper_bound" => 0,
                         "sum_other_doc_count" => 13678,
                         "buckets" => [  ]
+                    ],
+                    "subjects_count" => [
+                        "value" => 999
                     ]
                 ]
             ]
@@ -398,93 +430,10 @@
                                     "order" => [ '_count' => 'desc' ],
                                     "include" => ".*\|bnf\|.*"
                                 ]
-                            ]
-                        ]
-                    ]
-                ]
-            ]
-        ];
-
-        // This is not normally what should be received, but we test that the limit is respected
-        Es::shouldReceive('search')
-            ->once()
-            ->with($query)
-            ->andReturn([
-                "took" => 27,
-                "timed_out" => false,
-                "_shards" => [
-                    "total" => 1,
-                    "successful" => 1,
-                    "failed" => 0
-                ],
-                "hits" => [
-                    "total" => 3011,
-                    "max_score" => 0.0,
-                    "hits" => [ ]
-                ],
-                "aggregations" => [
-                    "subjects" => [
-                        "doc_count" => 41524,
-                        "subjects" => [
-                            "doc_count_error_upper_bound" => 0,
-                            "sum_other_doc_count" => 13678,
-                            "buckets" => [ [
-                                "key" => "professions|bnf|ark:/12148/cb13318415c",
-                                "doc_count" => 1412
-                            ], [
-                                "key" => "travail non rémunéré|bnf|ark:/12148/cb16604691s",
-                                "doc_count" => 1092
-                            ], [
-                                "key" => "famille|bnf|ark:/12148/cb119339867",
-                                "doc_count" => 1050
-                            ], [
-                                "key" => "oiseaux|bnf|ark:/12148/cb11932889r",
-                                "doc_count" => 1003
-                            ], [
-                                "key" => "météorologie|bnf|ark:/12148/cb11932496x",
-                                "doc_count" => 1001
-                            ], [
-                                "key" => "plantes|bnf|ark:/12148/cb11933145f",
-                                "doc_count" => 995
-                            ], [
-                                "key" => "animaux sauvages|bnf|ark:/12148/cb11930908q",
-                                "doc_count" => 989
-                            ], [
-                                "key" => "arbres|bnf|ark:/12148/cb11934786x",
-                                "doc_count" => 989
-                            ] ]
-                        ]
-                    ]
-                ]
-            ]);
-        $this->get('/api/v1/stats/themes/?limit=3')->assertTrue($this->response->isOk(), $this->response->content());
-        $this->seeJsonEquals(["themes" => [
-            "http://ark.bnf.fr/ark:/12148/cb13318415c" => ['label' => 'professions', 'count' => 1412 ],
-            "http://ark.bnf.fr/ark:/12148/cb16604691s" => ['label' => 'travail non rémunéré', 'count' => 1092 ],
-            "http://ark.bnf.fr/ark:/12148/cb119339867" => ['label' => 'famille', 'count' => 1050 ]
-        ]]);
-
-    }
-
-    public function testIndexIndex() {
-
-        $query = [
-            'index' => env('ELASTICSEARCH_INDEX'),
-            'body' => [
-                'size' => 0,
-                'query' =>[
-                    'match_all' => (object) null
-                ],
-                'aggs' => [
-                    "subjects" => [
-                        "nested" => [ "path" => "subject" ],
-                        "aggs" => [
-                            "subjects" => [
-                                "terms" => [
-                                    "field" => "subject.label_code",
-                                    "size" => 6,
-                                    "order" => [ '_count' => 'desc' ],
-                                    "include" => ".*\|bnf\|.*"
+                            ],
+                            "subjects_count" => [
+                                "cardinality" => [
+                                    "field" => "subject.label_code"
                                 ]
                             ]
                         ]
@@ -541,20 +490,23 @@
                                 "key" => "arbres|bnf|ark:/12148/cb11934786x",
                                 "doc_count" => 989
                             ] ]
+                        ],
+                        "subjects_count" => [
+                            "value" => 999
                         ]
                     ]
                 ]
             ]);
-        $this->get('/api/v1/stats/themes/?limit=3&index=1')->assertTrue($this->response->isOk(), $this->response->content());
+        $this->get('/api/v1/stats/themes/?limit=3')->assertTrue($this->response->isOk(), $this->response->content());
         $this->seeJsonEquals(["themes" => [
-            "http://ark.bnf.fr/ark:/12148/cb11932889r" => ['label' => 'oiseaux', 'count' => 1003 ],
-            "http://ark.bnf.fr/ark:/12148/cb11932496x" => ['label' => 'météorologie', 'count' => 1001 ],
-            "http://ark.bnf.fr/ark:/12148/cb11933145f" => ['label' => 'plantes', 'count' => 995  ]
-        ]]);
+            "http://ark.bnf.fr/ark:/12148/cb13318415c" => ['label' => 'professions', 'count' => 1412 ],
+            "http://ark.bnf.fr/ark:/12148/cb16604691s" => ['label' => 'travail non rémunéré', 'count' => 1092 ],
+            "http://ark.bnf.fr/ark:/12148/cb119339867" => ['label' => 'famille', 'count' => 1050 ]
+        ], "meta" => ['total' => 999]]);
 
     }
 
-    public function testIndexLimitIndex2() {
+    public function testIndexIndex() {
 
         $query = [
             'index' => env('ELASTICSEARCH_INDEX'),
@@ -570,10 +522,15 @@
                             "subjects" => [
                                 "terms" => [
                                     "field" => "subject.label_code",
-                                    "size" => 9,
+                                    "size" => 6,
                                     "order" => [ '_count' => 'desc' ],
                                     "include" => ".*\|bnf\|.*"
                                 ]
+                            ],
+                            "subjects_count" => [
+                                "cardinality" => [
+                                    "field" => "subject.label_code"
+                                ]
                             ]
                         ]
                     ]
@@ -629,6 +586,105 @@
                                 "key" => "arbres|bnf|ark:/12148/cb11934786x",
                                 "doc_count" => 989
                             ] ]
+                        ],
+                        "subjects_count" => [
+                            "value" => 999
+                        ]
+                    ]
+                ]
+            ]);
+        $this->get('/api/v1/stats/themes/?limit=3&index=1')->assertTrue($this->response->isOk(), $this->response->content());
+        $this->seeJsonEquals(["themes" => [
+            "http://ark.bnf.fr/ark:/12148/cb11932889r" => ['label' => 'oiseaux', 'count' => 1003 ],
+            "http://ark.bnf.fr/ark:/12148/cb11932496x" => ['label' => 'météorologie', 'count' => 1001 ],
+            "http://ark.bnf.fr/ark:/12148/cb11933145f" => ['label' => 'plantes', 'count' => 995  ]
+        ], "meta" => ['total' => 999]]);
+
+    }
+
+    public function testIndexLimitIndex2() {
+
+        $query = [
+            'index' => env('ELASTICSEARCH_INDEX'),
+            'body' => [
+                'size' => 0,
+                'query' =>[
+                    'match_all' => (object) null
+                ],
+                'aggs' => [
+                    "subjects" => [
+                        "nested" => [ "path" => "subject" ],
+                        "aggs" => [
+                            "subjects" => [
+                                "terms" => [
+                                    "field" => "subject.label_code",
+                                    "size" => 9,
+                                    "order" => [ '_count' => 'desc' ],
+                                    "include" => ".*\|bnf\|.*"
+                                ]
+                            ],
+                            "subjects_count" => [
+                                "cardinality" => [
+                                    "field" => "subject.label_code"
+                                ]
+                            ]
+                        ]
+                    ]
+                ]
+            ]
+        ];
+
+        // This is not normally what should be received, but we test that the limit is respected
+        Es::shouldReceive('search')
+            ->once()
+            ->with($query)
+            ->andReturn([
+                "took" => 27,
+                "timed_out" => false,
+                "_shards" => [
+                    "total" => 1,
+                    "successful" => 1,
+                    "failed" => 0
+                ],
+                "hits" => [
+                    "total" => 3011,
+                    "max_score" => 0.0,
+                    "hits" => [ ]
+                ],
+                "aggregations" => [
+                    "subjects" => [
+                        "doc_count" => 41524,
+                        "subjects" => [
+                            "doc_count_error_upper_bound" => 0,
+                            "sum_other_doc_count" => 13678,
+                            "buckets" => [ [
+                                "key" => "professions|bnf|ark:/12148/cb13318415c",
+                                "doc_count" => 1412
+                            ], [
+                                "key" => "travail non rémunéré|bnf|ark:/12148/cb16604691s",
+                                "doc_count" => 1092
+                            ], [
+                                "key" => "famille|bnf|ark:/12148/cb119339867",
+                                "doc_count" => 1050
+                            ], [
+                                "key" => "oiseaux|bnf|ark:/12148/cb11932889r",
+                                "doc_count" => 1003
+                            ], [
+                                "key" => "météorologie|bnf|ark:/12148/cb11932496x",
+                                "doc_count" => 1001
+                            ], [
+                                "key" => "plantes|bnf|ark:/12148/cb11933145f",
+                                "doc_count" => 995
+                            ], [
+                                "key" => "animaux sauvages|bnf|ark:/12148/cb11930908q",
+                                "doc_count" => 989
+                            ], [
+                                "key" => "arbres|bnf|ark:/12148/cb11934786x",
+                                "doc_count" => 989
+                            ] ]
+                        ],
+                        "subjects_count" => [
+                            "value" => 999
                         ]
                     ]
                 ]
@@ -637,7 +693,7 @@
         $this->seeJsonEquals(["themes" => [
             "http://ark.bnf.fr/ark:/12148/cb11930908q" => ['label' => 'animaux sauvages', 'count' => 989 ],
             "http://ark.bnf.fr/ark:/12148/cb11934786x" => ['label' => 'arbres', 'count' => 989 ]
-        ]]);
+        ], "meta" => ['total' => 999]]);
     }
 
 }