User activity tracking: schema and IDL
authorBill Erickson <berick@esilibrary.com>
Mon, 16 Jan 2012 21:18:49 +0000 (16:18 -0500)
committerThomas Berezansky <tsbere@mvlc.org>
Thu, 8 Mar 2012 18:50:05 +0000 (13:50 -0500)
* Adds 2 new tables and IDL classes.  The first is a configuration table
  used for defining activity types (config.usr_activity_type).  The
  second is for tracking activity events.  A user activity event is
  defined as a combination of user, action (e.g. login), the interface
  or 3rd-party responsible for the action (e.g. opac, staffclient,
  libraryelf), and the OpenSRF ingress (i.e. the mechanism through which
  the action was delivered: e.g. gateway, translator, xmlrpc).

* Includes a front-facing stored procedure (actor.insert_usr_activity),
  used for creating new activity entries.

* Adds seed data for some default activity types and reserves the first
  1000 IDs for system use.

Current default values for "ewho":

opac
staffclient
selfcheck
authproxy
ums
libraryelf
ezproxy

Current default values for "ehow" (ingress, some inherited from
opensrf):

opensrf (default)
gateway-v1
translator-v1
srfsh
--
sip2
xmlrpc
remoteauth
apache (default mod_perl/apache mod entry point)

Signed-off-by: Bill Erickson <berick@esilibrary.com>
Signed-off-by: Thomas Berezansky <tsbere@mvlc.org>
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/sql/Pg/002.schema.config.sql
Open-ILS/src/sql/Pg/005.schema.actors.sql
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/999.functions.global.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.user-activity.sql [new file with mode: 0644]

index 5260a76..237c8c2 100644 (file)
@@ -2736,7 +2736,45 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <link field="reservations" reltype="has_many" key="usr" map="" class="bresv"/>
                </links>
        </class>
-
+       <class id="cuat" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::usr_activity_type" oils_persist:tablename="config.usr_activity_type" reporter:label="User Activity Type">
+               <fields oils_persist:primary="id" oils_persist:sequence="config.usr_activity_type_id_seq">
+                       <field name="id" reporter:label="ID" reporter:datatype="id" />
+                       <field name="ewho" reporter:label="Event Caller" reporter:datatype="text"/>
+                       <field name="ewhat" reporter:label="Event Type" reporter:datatype="text"/>
+                       <field name="ehow" reporter:label="Event Mechanism" reporter:datatype="text"/>
+                       <field name="label" reporter:label="Label" reporter:datatype="text"/>
+                       <field name="egroup" reporter:label="Activity Group" reporter:datatype="text"/>
+                       <field name="enabled" reporter:label="Enabled" reporter:datatype="bool"/>
+                       <field name="transient" reporter:label="Transient" reporter:datatype="bool"/>
+        </fields>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+                       <actions>
+                               <create permission="ADMIN_USER_ACTIVITY_TYPE" global_required="true"/>
+                               <retrieve/>
+                               <update permission="ADMIN_USER_ACTIVITY_TYPE" global_required="true"/>
+                               <delete permission="ADMIN_USER_ACTIVITY_TYPE" global_required="true"/>
+                       </actions>
+               </permacrud>
+       </class>
+       <class id="auact" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="actor::usr_activity" oils_persist:tablename="actor.usr_activity" reporter:label="User Activity">
+               <fields oils_persist:primary="id" oils_persist:sequence="actor.usr_activity_id_seq">
+                       <field name="id" reporter:label="ID" reporter:datatype="id" />
+                       <field name="usr" reporter:label="User" reporter:datatype="link" />
+                       <field name="etype" reporter:label="Activity Type" reporter:datatype="link" />
+                       <field name="event_time" reporter:label="Event Time" reporter:datatype="timestamp" />
+        </fields>
+        <links>
+                       <link field="usr" reltype="has_a" key="id" map="" class="au"/>
+                       <link field="etype" reltype="has_a" key="id" map="" class="cuat"/>
+               </links>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+                       <actions>
+                               <retrieve permission="VIEW_USER">
+                                       <context link="usr" field="home_ou" />
+                               </retrieve>
+                       </actions>
+               </permacrud>
+       </class>
        <class id="csg" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::settings_group" oils_persist:tablename="config.settings_group" reporter:label="Settings Group">
                <fields oils_persist:primary="name">
                        <field name="name" reporter:datatype="text"/>
index 112a50b..5793062 100644 (file)
@@ -951,4 +951,22 @@ CREATE TABLE config.sms_carrier (
     active          BOOLEAN DEFAULT TRUE
 );
 
+CREATE TYPE config.usr_activity_group AS ENUM ('authen','authz','circ','hold','search');
+
+CREATE TABLE config.usr_activity_type (
+    id          SERIAL                      PRIMARY KEY, 
+    ewho        TEXT,
+    ewhat       TEXT,
+    ehow        TEXT,
+    label       TEXT                        NOT NULL, -- i18n
+    egroup      config.usr_activity_group   NOT NULL,
+    enabled     BOOL                        NOT NULL DEFAULT TRUE,
+    transient   BOOL                        NOT NULL DEFAULT FALSE,
+    CONSTRAINT  one_of_wwh CHECK (COALESCE(ewho,ewhat,ehow) IS NOT NULL)
+);
+
+CREATE UNIQUE INDEX unique_wwh ON config.usr_activity_type 
+    (COALESCE(ewho,''), COALESCE (ewhat,''), COALESCE(ehow,''));
+
+
 COMMIT;
index fbe0504..9c219e0 100644 (file)
@@ -623,4 +623,11 @@ CREATE TABLE actor.address_alert (
     billing_address BOOL    NOT NULL DEFAULT FALSE
 );
 
+CREATE TABLE actor.usr_activity (
+    id          BIGSERIAL   PRIMARY KEY,
+    usr         INT         REFERENCES actor.usr (id) ON DELETE SET NULL,
+    etype       INT         NOT NULL REFERENCES config.usr_activity_type (id),
+    event_time  TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
 COMMIT;
index 4896475..2ca7c7b 100644 (file)
@@ -11312,4 +11312,43 @@ INSERT INTO vandelay.merge_profile (owner, name, replace_spec)
 INSERT INTO vandelay.merge_profile (owner, name, preserve_spec) 
     VALUES (1, 'Full Overlay', '901c');
 
+-- user activity seed data --
+
+INSERT INTO config.usr_activity_type (id, ewho, ewhat, ehow, egroup, label) VALUES
+
+     -- authen/authz actions
+     -- note: "opensrf" is the default ingress/ehow
+     (1,  NULL, 'login',  'opensrf',      'authen', oils_i18n_gettext(1 , 'Login via opensrf', 'cuat', 'label'))
+    ,(2,  NULL, 'login',  'srfsh',        'authen', oils_i18n_gettext(2 , 'Login via srfsh', 'cuat', 'label'))
+    ,(3,  NULL, 'login',  'gateway-v1',   'authen', oils_i18n_gettext(3 , 'Login via gateway-v1', 'cuat', 'label'))
+    ,(4,  NULL, 'login',  'translator-v1','authen', oils_i18n_gettext(4 , 'Login via translator-v1', 'cuat', 'label'))
+    ,(5,  NULL, 'login',  'xmlrpc',       'authen', oils_i18n_gettext(5 , 'Login via xmlrpc', 'cuat', 'label'))
+    ,(6,  NULL, 'login',  'remoteauth',   'authen', oils_i18n_gettext(6 , 'Login via remoteauth', 'cuat', 'label'))
+    ,(7,  NULL, 'login',  'sip2',         'authen', oils_i18n_gettext(7 , 'SIP2 Proxy Login', 'cuat', 'label'))
+    ,(8,  NULL, 'login',  'apache',       'authen', oils_i18n_gettext(8 , 'Login via Apache module', 'cuat', 'label'))
+
+    ,(9,  NULL, 'verify', 'opensrf',      'authz',  oils_i18n_gettext(9 , 'Verification via opensrf', 'cuat', 'label'))
+    ,(10, NULL, 'verify', 'srfsh',        'authz',  oils_i18n_gettext(10, 'Verification via srfsh', 'cuat', 'label'))
+    ,(11, NULL, 'verify', 'gateway-v1',   'authz',  oils_i18n_gettext(11, 'Verification via gateway-v1', 'cuat', 'label'))
+    ,(12, NULL, 'verify', 'translator-v1','authz',  oils_i18n_gettext(12, 'Verification via translator-v1', 'cuat', 'label'))
+    ,(13, NULL, 'verify', 'xmlrpc',       'authz',  oils_i18n_gettext(13, 'Verification via xmlrpc', 'cuat', 'label'))
+    ,(14, NULL, 'verify', 'remoteauth',   'authz',  oils_i18n_gettext(14, 'Verification via remoteauth', 'cuat', 'label'))
+    ,(15, NULL, 'verify', 'sip2',         'authz',  oils_i18n_gettext(15, 'SIP2 User Verification', 'cuat', 'label'))
+
+     -- authen/authz actions w/ known uses of "who"
+    ,(16, 'opac',        'login',  'gateway-v1',   'authen', oils_i18n_gettext(16, 'OPAC Login (jspac)', 'cuat', 'label'))
+    ,(17, 'opac',        'login',  'apache',       'authen', oils_i18n_gettext(17, 'OPAC Login (tpac)', 'cuat', 'label'))
+    ,(18, 'staffclient', 'login',  'gateway-v1',   'authen', oils_i18n_gettext(18, 'Staff Client Login', 'cuat', 'label'))
+    ,(19, 'selfcheck',   'login',  'translator-v1','authen', oils_i18n_gettext(19, 'Self-Check Proxy Login', 'cuat', 'label'))
+    ,(20, 'ums',         'login',  'xmlrpc',       'authen', oils_i18n_gettext(20, 'Unique Mgt Login', 'cuat', 'label'))
+    ,(21, 'authproxy',   'login',  'apache',       'authen', oils_i18n_gettext(21, 'Apache Auth Proxy Login', 'cuat', 'label'))
+    ,(22, 'libraryelf',  'login',  'xmlrpc',       'authz',  oils_i18n_gettext(22, 'LibraryElf Login', 'cuat', 'label'))
+
+    ,(23, 'selfcheck',   'verify', 'translator-v1','authz',  oils_i18n_gettext(23, 'Self-Check User Verification', 'cuat', 'label'))
+    ,(24, 'ezproxy',     'verify', 'remoteauth',   'authz',  oils_i18n_gettext(24, 'EZProxy Verification', 'cuat', 'label'))
+    -- ...
+    ;
+
+-- reserve the first 1000 slots
+SELECT SETVAL('config.usr_activity_type_id_seq'::TEXT, 1000);
 
index 1acd06a..69d9cbe 100644 (file)
@@ -2092,4 +2092,61 @@ CREATE OR REPLACE FUNCTION evergreen.coded_value_map_normalizer( input TEXT, cty
             WHERE ctype = $2 AND code = $1;
 $F$ LANGUAGE SQL;
 
+-- user activity functions --
 
+-- remove transient activity entries on insert of new entries
+CREATE OR REPLACE FUNCTION actor.usr_activity_transient_trg () RETURNS TRIGGER AS $$
+BEGIN
+    DELETE FROM actor.usr_activity USING config.usr_activity_type atype
+        WHERE atype.transient AND NEW.etype = atype.id;
+    RETURN NEW;
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE TRIGGER remove_transient_usr_activity
+    BEFORE INSERT ON actor.usr_activity
+    FOR EACH ROW EXECUTE PROCEDURE actor.usr_activity_transient_trg();
+
+-- given a set of activity criteria, find the most approprate activity type
+CREATE OR REPLACE FUNCTION actor.usr_activity_get_type (
+        ewho TEXT, 
+        ewhat TEXT, 
+        ehow TEXT
+    ) RETURNS SETOF config.usr_activity_type AS $$
+SELECT * FROM config.usr_activity_type 
+    WHERE 
+        enabled AND 
+        (ewho  IS NULL OR ewho  = $1) AND
+        (ewhat IS NULL OR ewhat = $2) AND
+        (ehow  IS NULL OR ehow  = $3) 
+    ORDER BY 
+        -- BOOL comparisons sort false to true
+        COALESCE(ewho, '')  != COALESCE($1, ''),
+        COALESCE(ewhat,'')  != COALESCE($2, ''),
+        COALESCE(ehow, '')  != COALESCE($3, '') 
+    LIMIT 1;
+$$ LANGUAGE SQL;
+
+-- given a set of activity criteria, finds the best
+-- activity type and inserts the activity entry
+CREATE OR REPLACE FUNCTION actor.insert_usr_activity (
+        usr INT,
+        ewho TEXT, 
+        ewhat TEXT, 
+        ehow TEXT
+    ) RETURNS SETOF actor.usr_activity AS $$
+DECLARE
+    new_row actor.usr_activity%ROWTYPE;
+BEGIN
+    SELECT id INTO new_row.etype FROM actor.usr_activity_get_type(ewho, ewhat, ehow);
+    IF FOUND THEN
+        new_row.usr := usr;
+        INSERT INTO actor.usr_activity (usr, etype) 
+            VALUES (usr, new_row.etype)
+            RETURNING * INTO new_row;
+        RETURN NEXT new_row;
+    END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+-- user activity functions --
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.user-activity.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.user-activity.sql
new file mode 100644 (file)
index 0000000..30b3396
--- /dev/null
@@ -0,0 +1,144 @@
+-- Evergreen DB patch XXXX.schema.user-activity.sql
+--
+BEGIN;
+
+-- check whether patch can be applied
+-- SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+-- SCHEMA --
+
+CREATE TYPE config.usr_activity_group AS ENUM ('authen','authz','circ','hold','search');
+
+CREATE TABLE config.usr_activity_type (
+    id          SERIAL                      PRIMARY KEY, 
+    ewho        TEXT,
+    ewhat       TEXT,
+    ehow        TEXT,
+    label       TEXT                        NOT NULL, -- i18n
+    egroup      config.usr_activity_group   NOT NULL,
+    enabled     BOOL                        NOT NULL DEFAULT TRUE,
+    transient   BOOL                        NOT NULL DEFAULT FALSE,
+    CONSTRAINT  one_of_wwh CHECK (COALESCE(ewho,ewhat,ehow) IS NOT NULL)
+);
+
+CREATE UNIQUE INDEX unique_wwh ON config.usr_activity_type 
+    (COALESCE(ewho,''), COALESCE (ewhat,''), COALESCE(ehow,''));
+
+CREATE TABLE actor.usr_activity (
+    id          BIGSERIAL   PRIMARY KEY,
+    usr         INT         REFERENCES actor.usr (id) ON DELETE SET NULL,
+    etype       INT         NOT NULL REFERENCES config.usr_activity_type (id),
+    event_time  TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+-- remove transient activity entries on insert of new entries
+CREATE OR REPLACE FUNCTION actor.usr_activity_transient_trg () RETURNS TRIGGER AS $$
+BEGIN
+    DELETE FROM actor.usr_activity USING config.usr_activity_type atype
+        WHERE atype.transient AND NEW.etype = atype.id;
+    RETURN NEW;
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE TRIGGER remove_transient_usr_activity
+    BEFORE INSERT ON actor.usr_activity
+    FOR EACH ROW EXECUTE PROCEDURE actor.usr_activity_transient_trg();
+
+-- given a set of activity criteria, find the most approprate activity type
+CREATE OR REPLACE FUNCTION actor.usr_activity_get_type (
+        ewho TEXT, 
+        ewhat TEXT, 
+        ehow TEXT
+    ) RETURNS SETOF config.usr_activity_type AS $$
+SELECT * FROM config.usr_activity_type 
+    WHERE 
+        enabled AND 
+        (ewho  IS NULL OR ewho  = $1) AND
+        (ewhat IS NULL OR ewhat = $2) AND
+        (ehow  IS NULL OR ehow  = $3) 
+    ORDER BY 
+        -- BOOL comparisons sort false to true
+        COALESCE(ewho, '')  != COALESCE($1, ''),
+        COALESCE(ewhat,'')  != COALESCE($2, ''),
+        COALESCE(ehow, '')  != COALESCE($3, '') 
+    LIMIT 1;
+$$ LANGUAGE SQL;
+
+-- given a set of activity criteria, finds the best
+-- activity type and inserts the activity entry
+CREATE OR REPLACE FUNCTION actor.insert_usr_activity (
+        usr INT,
+        ewho TEXT, 
+        ewhat TEXT, 
+        ehow TEXT
+    ) RETURNS SETOF actor.usr_activity AS $$
+DECLARE
+    new_row actor.usr_activity%ROWTYPE;
+BEGIN
+    SELECT id INTO new_row.etype FROM actor.usr_activity_get_type(ewho, ewhat, ehow);
+    IF FOUND THEN
+        new_row.usr := usr;
+        INSERT INTO actor.usr_activity (usr, etype) 
+            VALUES (usr, new_row.etype)
+            RETURNING * INTO new_row;
+        RETURN NEXT new_row;
+    END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+-- SEED DATA --
+
+INSERT INTO config.usr_activity_type (id, ewho, ewhat, ehow, egroup, label) VALUES
+
+     -- authen/authz actions
+     -- note: "opensrf" is the default ingress/ehow
+     (1,  NULL, 'login',  'opensrf',      'authen', oils_i18n_gettext(1 , 'Login via opensrf', 'cuat', 'label'))
+    ,(2,  NULL, 'login',  'srfsh',        'authen', oils_i18n_gettext(2 , 'Login via srfsh', 'cuat', 'label'))
+    ,(3,  NULL, 'login',  'gateway-v1',   'authen', oils_i18n_gettext(3 , 'Login via gateway-v1', 'cuat', 'label'))
+    ,(4,  NULL, 'login',  'translator-v1','authen', oils_i18n_gettext(4 , 'Login via translator-v1', 'cuat', 'label'))
+    ,(5,  NULL, 'login',  'xmlrpc',       'authen', oils_i18n_gettext(5 , 'Login via xmlrpc', 'cuat', 'label'))
+    ,(6,  NULL, 'login',  'remoteauth',   'authen', oils_i18n_gettext(6 , 'Login via remoteauth', 'cuat', 'label'))
+    ,(7,  NULL, 'login',  'sip2',         'authen', oils_i18n_gettext(7 , 'SIP2 Proxy Login', 'cuat', 'label'))
+    ,(8,  NULL, 'login',  'apache',       'authen', oils_i18n_gettext(8 , 'Login via Apache module', 'cuat', 'label'))
+
+    ,(9,  NULL, 'verify', 'opensrf',      'authz',  oils_i18n_gettext(9 , 'Verification via opensrf', 'cuat', 'label'))
+    ,(10, NULL, 'verify', 'srfsh',        'authz',  oils_i18n_gettext(10, 'Verification via srfsh', 'cuat', 'label'))
+    ,(11, NULL, 'verify', 'gateway-v1',   'authz',  oils_i18n_gettext(11, 'Verification via gateway-v1', 'cuat', 'label'))
+    ,(12, NULL, 'verify', 'translator-v1','authz',  oils_i18n_gettext(12, 'Verification via translator-v1', 'cuat', 'label'))
+    ,(13, NULL, 'verify', 'xmlrpc',       'authz',  oils_i18n_gettext(13, 'Verification via xmlrpc', 'cuat', 'label'))
+    ,(14, NULL, 'verify', 'remoteauth',   'authz',  oils_i18n_gettext(14, 'Verification via remoteauth', 'cuat', 'label'))
+    ,(15, NULL, 'verify', 'sip2',         'authz',  oils_i18n_gettext(15, 'SIP2 User Verification', 'cuat', 'label'))
+
+     -- authen/authz actions w/ known uses of "who"
+    ,(16, 'opac',        'login',  'gateway-v1',   'authen', oils_i18n_gettext(16, 'OPAC Login (jspac)', 'cuat', 'label'))
+    ,(17, 'opac',        'login',  'apache',       'authen', oils_i18n_gettext(17, 'OPAC Login (tpac)', 'cuat', 'label'))
+    ,(18, 'staffclient', 'login',  'gateway-v1',   'authen', oils_i18n_gettext(18, 'Staff Client Login', 'cuat', 'label'))
+    ,(19, 'selfcheck',   'login',  'translator-v1','authen', oils_i18n_gettext(19, 'Self-Check Proxy Login', 'cuat', 'label'))
+    ,(20, 'ums',         'login',  'xmlrpc',       'authen', oils_i18n_gettext(20, 'Unique Mgt Login', 'cuat', 'label'))
+    ,(21, 'authproxy',   'login',  'apache',       'authen', oils_i18n_gettext(21, 'Apache Auth Proxy Login', 'cuat', 'label'))
+    ,(22, 'libraryelf',  'login',  'xmlrpc',       'authz',  oils_i18n_gettext(22, 'LibraryElf Login', 'cuat', 'label'))
+
+    ,(23, 'selfcheck',   'verify', 'translator-v1','authz',  oils_i18n_gettext(23, 'Self-Check User Verification', 'cuat', 'label'))
+    ,(24, 'ezproxy',     'verify', 'remoteauth',   'authz',  oils_i18n_gettext(24, 'EZProxy Verification', 'cuat', 'label'))
+    -- ...
+    ;
+
+-- reserve the first 1000 slots
+SELECT SETVAL('config.usr_activity_type_id_seq'::TEXT, 1000);
+
+COMMIT;
+
+/* 
+-- UNDO SQL --
+BEGIN;
+DELETE FROM actor.usr_activity;
+DELETE FROM config.usr_activity_type;
+DROP TRIGGER remove_transient_usr_activity ON actor.usr_activity;
+DROP FUNCTION actor.usr_activity_transient_trg();
+DROP FUNCTION actor.insert_usr_activity(INT, TEXT, TEXT, TEXT);
+DROP FUNCTION actor.usr_activity_get_type(TEXT, TEXT, TEXT);
+DROP TABLE actor.usr_activity;
+DROP TABLE config.usr_activity_type;
+DROP TYPE config.usr_activity_group;
+COMMIT;
+*/