Rather than covering every possible use-case in this module (of additional information), we should allow other modules to be able to add arbitrary data to the accesslog.

The API should allow modules to declare field definitions and views definitions in a single place and perform all of the necessary schema alterations at appropriate times. I'm envisioning something like the following:

hook_better_statistics_fields() {
  $fields = array();

  // Which would become {accesslog}.my_module_foo
  $fields['my_module_foo'] = array(
    // Schema definition for this field
    'schema' => array(
      'type' => 'int',
      'size' => 'normal',
      'default' => 0,
      'description' => 'A description about foo.',
    ),
    // Called with the field name as a single argument.
    // E.g. my_module_get_stats_values('my_module_foo');
    // In PHP 5.3, this could also be a closure.
    'callback' => 'my_module_get_stats_values',
    // Optionally define views handlers here.
    'views_field' => array(
      'title' => t('Foo', array(), $en),
      'help' => t('Description of foo for site builders..', array(), $en),
    ),
  );

  return $fields;
}
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

iamEAP’s picture

Status: Active » Needs work

Posting progress so far. This does the following:

  • Re-implements better_statistics_exit() in the most generic way possible,
  • Introduces a hook_better_statistics_fields() allowing other modules to declare statistics fields and callbacks,
  • Attempts to do the above in as sane a way as possible (only allowing schema changes on cache flush, module enable, or disable).

Still to be done:

  • Get the views portion of the skeleton above working,
  • Clean out any cruft left in helpers.inc (may not need any of it anymore).
iamEAP’s picture

Whoops. Here's the patch.

mondrake’s picture

Very promisiing...

I got your patch above and tested it OK.

I'd suggest a couple of points -
a) include in every field an identification of the module where it is originating from
b) include an 'enabled' flag for each field

The idea could be to have each module to present its own 'full menu' of fields (with enabled flag preset by module developer), and an admin form in better_statistics where admins will cherry pick the fields they really need to make statistics upon.

What do you think? I may propose some patch here (when I have some time)...

iamEAP’s picture

Regarding a: this won't be necessary within the field array itself, since we can get that information by looping through the results of module_implements(). Definitely worth having, though.

Regarding b: I keep going back and forth on this. Ultimately, I think it's a good idea. When it's implemented, I also think it'd be a great way to get rid of those three extra hook implementations that call the single field alteration function. This way, instead of updating db schema on either flush caches/module enable/disable, it would just be on saving the admin form.

Some other problems that came up while I was thinking about it:

  • How do we handle if the module needs to update the schema of the field?
  • As the patch is, it won't easily write through changes to non-schema fields (like views/callback) that should change more or less automatically

Thanks for the thoughts, mondrake.

iamEAP’s picture

Status: Needs work » Needs review

I've pushed a huge change into the 7.x-1.x dev branch that implements the API I suggested. Mondrake, please have a look and let me know of any bugs or anything I may have overlooked. Since this could be extremely handy elsewhere, a few more eyes would be great too. If you can, please refer others.

I put a significant amount of work into documentation; for that, please see better_statistics.api.php.

Otherwise, I'll also update the issue summary above to reflect the new information.

mondrake’s picture

Hi and thanks for that. Brilliant.

Very first thoughts, will test more later in the week. Please take them as they are :)

a) if I understand right the callback ony takes the field name as the argument. However, we would need at least another argument to be taken. For instance, I was trying a micromodule for integrating Browscap with just the hook implementation + a data fetching function. In order to get the name of the browser I would need to pass the field name to be returned, plus the user-agent string which is used by browscap to fetch the info. In other words instead of

      $field_data[$field] = $data['callback']($field);

something like

      $field_data[$field] = $data['callback']($field, $args...);

Or maybe I am missing something...





b) how about introducing also a 'file' entry to the api to specifiy a file where the callback fucntion is coded? Like Forms API.

c)did not look in detail, but wouldn't it make sense to unify what _better_statistics_update_fields() and _better_statistics_settings_form_submit() do to the db and the schema, and call cache flush to trigger it from form submission? I was coming to it from the fact that after _better_statistics_settings_form_submit() there is no cache flush (and I have a module that caches the structure of the tables...)

d) for some reason I get a PDO exception when adding my hooked field -

Warning: 1265 Data truncated for column 'browser' at row 1: ALTER TABLE {accesslog} CHANGE `browser` `browser` VARCHAR(50) NOT NULL COMMENT 'Name of the browser.'; Array ( ) in db_add_field() (line 2809 of /home3/mondrak1/public_html/lab02/includes/database/database.inc).






In terms of spreading the news and getting reviews - I was thinking about preparing a couple of test modules for integrating Browscap (I started that, if you can give hints as to point sub (a) above it will get ready quickly) and IP Geolocation, and then posting in those modules' issue queues a proposal to integrate. What's your view?

mondrake’s picture

Oops - just realised I can get around point a) in #6 above... at least for Browscap and IP Geolocation. The $_SERVER variable would provide user agent and remote address...

Maybe a) could stay just as an option.

... and on (d)...

I was specifying 'not null' = TRUE but without specifying a default value for the field ... sorry for that :(

mondrake’s picture

FileSize
2.79 KB

Hi iamEAP,

I am leaving all the story above, just as a reference. Please let me know if this is too much and I will edit/remove anything unnecessary.

Here attached, an example of a micro-module to 'bridge' between Better Statistics and Browscap. It is already making me happy :)

Will test further, just two observations on top of above:
e) in the admin form, it may be worth having each module's exposed fields in a collapsible fieldset, as the list tends to grow
f) if you play around with adding/removing fields, then a cache flush is needed to have a proper list exposed in Views - if you don't, then the View keeps fields already removed in the list

Cheers

iamEAP’s picture

Wow, thanks for the very detailed review, mondrake!

a) In some of the older code I posted previously, I tried using a "callback_argument," but because of the way the API is engineered, it could only be a static value, even though the way it's declared makes it seem like it could be dynamic (i.e. setting $fields['my_field']['callback_argument'] = my_function(), which would work once and then permanently "cache" the value it returned). As a result, I just decided to get rid of the callback argument and force the declared callback function to take the field name as an argument. That way, in your callback function, you could set up a very simple switch/case; or, if that was still undesirable for whatever reason, you could specify different callback functions for each field and just ignore the field argument passed to them. Hopefully it's flexible enough how it is; it would require some non-trivial reengineering otherwise.

b) I like this idea, especially having seen your mini browscap integration module. Sounds like what might be best is to take cues from Views, Rules, et al. and introduce the concept of a my_module.statistics.inc file that gets autoloaded when needed. That way, the stats API code and callback won't have to clutter up the main module and those who don't want it or don't have Better Statistics installed won't have to care. I created an issue for that here: #1786896: Allow Stats API code to live in a module.statistics.inc file

c) It might be a good addition to call _better_statistics_update_fields() in the form submit, but the cases where that would need to happen would be pretty small. I don't think it would be necessary to flush the entire cache each time that form was saved. My only apprehension is having multiple, separate variable saves, but that issue can be worked out. Issue created here: #1786900: Better Stats field update code might also need running on configuration save

d) I ran into this a couple of times myself. I thought it was appropriate to fail gracefully in the case of a schema updates (since this would be happening, at least currently, when an admin was performing seemingly unrelated actions), but I think the schema add should fail hard like you saw so that developers can catch them before general release.

e) Good idea! Issue here: #1786906: Make each module's checkboxes a collapsible fieldset

f) This probably goes hand in hand with c above, though again, I don't think a full cache flush on saving that form is necessarily a good idea. I think it's best to just clear whatever caching mechanism Views uses for the field list and call it good. Issue here: #1786910: Views can list invalid fields after adding/removing Better Stats fields

Thanks again!

mondrake’s picture

Posted a request to review into Browscap issue queue

#1787488: Integration with Better Statistics [Discussion]

mondrake’s picture

just posted a comment to #1786906: Make each module's checkboxes a collapsible fieldset, I have an issue there

iamEAP’s picture

Just saw, and taken care of with a recent commit.

I'm signing off for the evening now, but everything in dev should be a little more stable. I'd checkout the repo directly for the latest, or if you'd prefer, wait for the Sept 19th dev package.

All of your major points above should be addressed now, so consider what's up in the repo (or the Sept 19th dev package) to be a release candidate.

I used your browscap work above to test the new hook_statistics_api() functionality, so you might try that out. See the documentation at #1786896: Allow Stats API code to live in a module.statistics.inc file

Thanks again!

mondrake’s picture

Thanks!

I suppose you developed a patch for Browscap then, with a browscap.statistics.inc file including more or less the contents of the mini-module, and a change to the browscap.module to implement browscap_statistics_api().
Can you please share if so? When we'll be happy and tested, and the new release of Better Statistics on air, we could then post it to Browscap's #1787488: Integration with Better Statistics [Discussion] issue I opened asking for it to be included there.

iamEAP’s picture

All I did was take your module, stick it in browscap.statistics.inc, and add the following code to browscap.module:

/**
  * Implements hook_statistics_api().
  */
function browscap_statistics_api() {
  return array(
    'version' => 1,
  );
}

I'll let you do the honors (once Better Statistics 7.x-1.1 is released) since you put all of the effort into making the thing.

mondrake’s picture

Got it, thanks.

Just a question - ctools dependency is only for Better Statistcs, right?

In other words, I understand modules do not have to put a ctools dependency in their .info file; just add the hook in .module and add the MODULE.statistics.inc file, then if the module is installed in a non-ctools (and therefore non-Better Statistics) environment, simply the add'l code will stay there idle.

mondrake’s picture

FileSize
1.74 KB

Hi,

I've made for IP Geoloc what I already did for Browscap. I am attaching ip_geoloc.statistics.inc here for you to review if you happen to use this module.

A couple of questions, although I believe these would be off topic. Maybe you can help anyway.

1) IP Geoloc runs the geolocation routine asychronously - that means that when a user hits a page, a ajax call is launched to get the geolocation; only when this is available, the lat/long and address are associated to the IP address and stored in a table. This requires some time, so most likely at the time when Better Statistics is getting the data for storing in the acceslog, this would not be available yet. For the time being I was thinking about trying to hook in the routines that update the info associated to the IP address when the ajax call is completed, and at that time fetch all the accesslog entries for that IP address that were created 'blank', and rewriting them back with the freshly received information. Definitely to be raised to the IP Geoloc maintainer when posting a request to integrate, but was wondering if you have any idea here.

2) Hopefully more trivial :) connected to my limited knowledge of Views - I can see now all the accesslog fields to be selected but for the user id and name... am I doing anything wrong? If I had those I could have a neat replacement to the core Recent hits report, inclusive of Browscap and IP Geoloc fields...

Thanks for your great work

iamEAP’s picture

0) Correct. You will only need ctools for the Better Statistics module. If a module (like Browscap or IP Geoloc) specifies a hook_statistics_api() and has a module.statistics.inc file, but they don't have Better Statistics (or ctools), the module.statistics.inc file will never be loaded.

1) Hmm. Very cool that they're pulling the data asynchronously that way, but definitely more difficult for integration. I think that what you described may be the only good option; I don't know the specifics of how the module works, but it might be worth running the data update on cron instead of what you described? Hard to say without diving in. I don't know how the module handles it now, but the most performant way to do the statistics side is to keep whatever geo data it returns in a cookie (once it's loaded) and in the statistics callback, just pull it from the cookie. Another, very useful integration point here would be a way for Views to take that data from accesslog and place points on a map. That level of Views API work is beyond me, though.

2) This looks to be a bug. I could've sworn this field was available by default without Better Stats installed, but I guess not. It's not a bug, you just have to go to the "Advanced" fieldset in the right of the Views UI and add a user relationship.

iamEAP’s picture

Edit: wrong tab...

mondrake’s picture

FileSize
34.58 KB

Hi

two days with Better Statistics + Browscap + IP Geoloc integrations running on my test env; quite smooth.

Thanks for your replies in #17. Here's my Views' based replacement of core's Recent hits report :)

Just an example for anyone reviewing these posts.

better accesslog

mondrake’s picture

... and the bad news: I got

PDOException: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'rendering_engine_description' at row 1: INSERT INTO {accesslog} (aid, sid, title, path, url, hostname, uid, timer, timestamp, user_agent, device_name, device_maker, is_mobile_device, ...

when the string returned from the callback is longer than what the db field can take. This is worse than @ #6.d because it happens at page hit time, not at config time. I fixed by changing the field definition in the statistics.inc file from varchar to text - but believe we cannot assume that the callbacks will always behave. How about wrapping the db_insert in the better_statistics_exit() function in a try/catch block?

Cheers

iamEAP’s picture

Probably a good idea. Since the schema is available at that point anyway, it might be worth doing a runtime check for strlen of the callback return vs. length of the declared schema (if one exists). Opened #1790836: Fail gracefully if db insert in hook_exit() fails for some reason

iamEAP’s picture

One thing I haven't tested, but I think should be tested, is what happens in the event that someone has 7.x-1.0 installed, somehow doesn't have ctools installed, and then updates to 7.x-1.1. I'm going to guess Drupal will error out in a very bad way.

mondrake’s picture

Posted a request to review into IP Geolocation issue queue

#1791526: Integration with Better Statistics

iamEAP’s picture

Believe most of the kinks have been ironed out at this point. If you don't have any objections, mondrake, I'd like to release 7.x-1.1 with the API. Then we can focus on those integrations; if there are any other issues, we can handle them in the usual way.

mondrake’s picture

Thanks. Let's roll :)

Please have a look at #1791068: Provide an enhanced 'Recent hit details' page when you can.

iamEAP’s picture

Status: Needs review » Fixed

Tag pushed up; release to come shortly. Thanks again for all your help, mondrake! Let's see about getting the browscap and IP Gelocation integrations underway.

http://drupal.org/node/1792876

mondrake’s picture

Hi

I started to propose patches to Browscap and IP Geoloc. Issues

#1787488: Integration with Better Statistics [Discussion]

and

#1791526: Integration with Better Statistics

I really trust you'll be following and contributing :))

Thanks for the credits in your release notes!

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

Anonymous’s picture

Issue summary: View changes

Updating how the API is used.