Link checker: user interface and supporting fixes (part 2)
authorLebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Fri, 31 Aug 2012 21:31:43 +0000 (17:31 -0400)
committerMike Rylander <mrylander@gmail.com>
Thu, 14 Feb 2013 19:19:17 +0000 (14:19 -0500)
Started verification review UI, also SCHEMA CHANGES
    It just doesn't work for me to not have url_verify.url directly related
    to url_verify.session.  When dealing with the "root" URL in a redirect
    chain, you can get the related session through url_selector, but not when
    you have any later URL in the chain.  The only way for IDL perms to work
    would be to have a link to a view using a CTE to find the "root" URL.
    That's too complex, so instead of that I've just added a session fkey on
    url_verify.url.
Corrections to the preceding commit
Vertical scrolling UI glitches fixed
Fix broken display of verification attempt in progress
Implement the "process immediately" switch, hitherto unhooked up
Verify-all now means all-matching-my-search-terms, not necessarily all-in-uvs
let's do filter sets a little more generalized-like
Permission fixing
Filter set loading works.
Filter loading: gracefully skip unknown fields, remove inital empty row
Saving filter sets
Fix filter dialog for pkey fields, scrolliness issue, saved filters issue
Pretty start page for staff client menu to land on
Staff client menu entry
User settings for saved columns
Session cloning, working and rather tested
show name of session on url select page ...
    ... and link back to that on review attempt page
IN / NOT IN for filter somewhat working, but doesn't save/load yet
Saving/loading filter rows for IN, NOT IN operators
Printing

Signed-off-by: Lebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Signed-off-by: Mike Rylander <mrylander@gmail.com>
30 files changed:
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/perlmods/lib/OpenILS/Application/URLVerify.pm
Open-ILS/src/sql/Pg/002.schema.config.sql
Open-ILS/src/sql/Pg/075.schema.url_verify.sql
Open-ILS/src/sql/Pg/076.functions.url_verify.sql
Open-ILS/src/sql/Pg/800.fkeys.sql
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.url_verify.sql
Open-ILS/src/sql/Pg/upgrade/YYYY.functions.url_verify.sql
Open-ILS/src/sql/Pg/upgrade/ZZZZ.data.url_verify.sql
Open-ILS/src/templates/base.tt2
Open-ILS/src/templates/url_verify/create_session.tt2
Open-ILS/src/templates/url_verify/review_attempt.tt2 [new file with mode: 0644]
Open-ILS/src/templates/url_verify/select_urls.tt2
Open-ILS/src/templates/url_verify/sessions.tt2 [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/FlattenerStore.js
Open-ILS/web/js/dojo/openils/URLVerify/CreateSession.js
Open-ILS/web/js/dojo/openils/URLVerify/ReviewAttempt.js [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/URLVerify/SelectURLs.js
Open-ILS/web/js/dojo/openils/URLVerify/Sessions.js [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/URLVerify/Verify.js [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/URLVerify/nls/URLVerify.js
Open-ILS/web/js/dojo/openils/widget/FlattenerFilterDialog.js
Open-ILS/web/js/dojo/openils/widget/FlattenerGrid.js
Open-ILS/web/js/dojo/openils/widget/PCrudFilterPane.js
Open-ILS/web/js/dojo/openils/widget/nls/PCrudFilterPane.js
Open-ILS/web/opac/locale/en-US/lang.dtd
Open-ILS/xul/staff_client/chrome/content/main/menu.js
Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties

index 7726438..1510df1 100644 (file)
@@ -9345,7 +9345,7 @@ SELECT  usr,
         reporter:label="URL Verification Session"
     >
         <fields oils_persist:primary="id" oils_persist:sequence="url_verify.session_id_seq">
-            <field reporter:label="ID" name="id" reporter:datatype="id"/>
+            <field reporter:label="Session ID" name="id" reporter:datatype="id"/>
             <field reporter:label="Name" name="name" reporter:datatype="text" oils_obj:required="true"/>
                        <field reporter:label="Owning Library" name="owning_lib" reporter:datatype="org_unit" oils_obj:required="true"/>
             <field reporter:label="Creator" name="creator" reporter:datatype="link" oils_obj:required="true"/>
@@ -9411,7 +9411,7 @@ SELECT  usr,
         reporter:label="URL Verification URL Selector"
     >
         <fields oils_persist:primary="id" oils_persist:sequence="url_verify.url_selector_id_seq">
-            <field reporter:label="ID" name="id" reporter:datatype="id"/>
+            <field reporter:label="URL Selector ID" name="id" reporter:datatype="id"/>
             <field reporter:label="XPath" name="xpath" reporter:datatype="text" oils_obj:required="true"/>
                        <field reporter:label="Session" name="session" reporter:datatype="link" oils_obj:required="true"/>
             <field reporter:label="URLs" name="urls" reporter:datatype="link" oils_persist:virtual="true"/>
@@ -9449,9 +9449,10 @@ SELECT  usr,
         reporter:label="URL Verification URL"
     >
         <fields oils_persist:primary="id" oils_persist:sequence="url_verify.url_id_seq">
-            <field reporter:label="ID" name="id" reporter:datatype="id"/>
+            <field reporter:label="URL ID" name="id" reporter:datatype="id"/>
                        <field reporter:label="Redirected From" name="redirect_from" reporter:datatype="link"/>
                        <field reporter:label="Container Item" name="item" reporter:datatype="link" oils_obj:required="true"/>
+                       <field reporter:label="Session" name="session" reporter:datatype="link" oils_obj:required="true"/>
                        <field reporter:label="URL Selector" name="url_selector" reporter:datatype="link"/>
             <field reporter:label="Tag" name="tag" reporter:datatype="text"/>
             <field reporter:label="Subfield" name="subfield" reporter:datatype="text"/>
@@ -9465,27 +9466,30 @@ SELECT  usr,
             <field reporter:label="Page" name="page" reporter:datatype="text"/>
             <field reporter:label="Query" name="query" reporter:datatype="text"/>
             <field reporter:label="Fragment" name="fragment" reporter:datatype="text"/>
+            <field reporter:label="Verifications" name="verifications" reporter:datatype="link" oils_persist:virtual="true" />
         </fields>
 
         <links>
             <link field="redirect_from" reltype="has_a" key="id" map="" class="uvu"/>
-            <link field="item" reltype="has_a" key="id" map="" class="uvsbrem" /><!-- surprise! -->
+            <link field="item" reltype="has_a" key="id" map="" class="uvsbrem" />
+            <link field="session" reltype="has_a" key="id" map="" class="uvs"/>
             <link field="url_selector" reltype="has_a" key="id" map="" class="uvus"/>
+            <link field="verifications" reltype="has_many" key="url" map="" class="uvuv"/>
         </links>
 
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
             <actions>
                 <create permission="URL_VERIFY">
-                    <context link="url_selector" jump="session" field="owning_lib"/>
+                    <context link="session" field="owning_lib"/>
                 </create>
                 <retrieve permission="URL_VERIFY">
-                    <context link="url_selector" jump="session" field="owning_lib"/>
+                    <context link="session" field="owning_lib"/>
                 </retrieve>
                 <update permission="URL_VERIFY">
-                    <context link="url_selector" jump="session" field="owning_lib"/>
+                    <context link="session" field="owning_lib"/>
                 </update>
                 <delete permission="URL_VERIFY">
-                    <context link="url_selector" jump="session" field="owning_lib"/>
+                    <context link="session" field="owning_lib"/>
                 </delete>
             </actions>
         </permacrud>
@@ -9500,7 +9504,7 @@ SELECT  usr,
         reporter:label="URL Verification Attempt"
     >
         <fields oils_persist:primary="id" oils_persist:sequence="url_verify.verification_attempt_id_seq">
-            <field reporter:label="ID" name="id" reporter:datatype="id"/>
+            <field reporter:label="Attempt ID" name="id" reporter:datatype="id"/>
                        <field reporter:label="User" name="usr" reporter:datatype="link"/>
                        <field reporter:label="Session" name="session" reporter:datatype="link"/>
             <field reporter:label="Start Time" name="start_time" reporter:datatype="timestamp"/>
@@ -9539,7 +9543,7 @@ SELECT  usr,
         reporter:label="URL Verification"
     >
         <fields oils_persist:primary="id" oils_persist:sequence="url_verify.url_verification_id_seq">
-            <field reporter:label="ID" name="id" reporter:datatype="id"/>
+            <field reporter:label="Verification ID" name="id" reporter:datatype="id"/>
                        <field reporter:label="URL" name="url" reporter:datatype="link"/>
                        <field reporter:label="Attempt" name="attempt" reporter:datatype="link"/>
             <field reporter:label="Request Time" name="req_time" reporter:datatype="timestamp"/>
@@ -9574,37 +9578,56 @@ SELECT  usr,
 
     </class>
 
-    <class
-        id="uvfs"
-        controller="open-ils.cstore open-ils.pcrud"
-        oils_obj:fieldmapper="url_verify::filter_set"
-        oils_persist:tablename="url_verify.filter_set"
-        reporter:label="URL Verification Filter Set"
-    >
-        <fields oils_persist:primary="id" oils_persist:sequence="url_verify.url_verification_id_seq">
-            <field reporter:label="ID" name="id" reporter:datatype="id"/>
-            <field reporter:label="Name" name="name" reporter:datatype="text"/>
+       <class
+               id="cfdi"
+               controller="open-ils.cstore open-ils.pcrud"
+               oils_obj:fieldmapper="config::filter_dialog_interface"
+               oils_persist:tablename="config.filter_dialog_interface"
+               reporter:label="FilterDialog Interface">
+               <fields oils_persist:primary="key" oils_persist:sequence="config.filter_dialog_interface_pkey">
+                       <field reporter:label="Interface Key" name="key" reporter:datatype="text" />
+                       <field reporter:label="Description" name="description" reporter:datatype="text" />
+               </fields>
+               <links>
+                       <link field="filter_sets" reltype="has_many" key="interface" map="" class="cfdfs"/>
+               </links>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+                       <actions>
+                               <retrieve />
+                       </actions>
+               </permacrud>
+       </class>
+
+       <class
+               id="cfdfs"
+               controller="open-ils.cstore open-ils.pcrud"
+               oils_obj:fieldmapper="config::filter_dialog_filter_set"
+               oils_persist:tablename="config.filter_dialog_filter_set"
+               reporter:label="FilterDialog Filter Set">
+               <fields oils_persist:primary="id" oils_persist:sequence="config.filter_dialog_filter_set_id_seq">
+                       <field reporter:label="ID" name="id" reporter:datatype="id"/>
+                       <field reporter:label="Name" name="name" reporter:datatype="text"/>
                        <field reporter:label="Owning Library" name="owning_lib" reporter:datatype="org_unit"/>
                        <field reporter:label="Creator" name="creator" reporter:datatype="link"/>
-            <field reporter:label="Create Time" name="create_time" reporter:datatype="timestamp"/>
-                       <field reporter:label="Filter" name="filter" reporter:datatype="text"/>
-        </fields>
-
-        <links>
-            <link field="owning_lib" reltype="has_a" key="id" map="" class="aou"/>
-            <link field="creator" reltype="has_a" key="id" map="" class="au"/>
-        </links>
-
-        <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
-            <actions>
-                <create permission="URL_VERIFY" context_field="owning_lib"/>
-                <retrieve permission="URL_VERIFY" context_field="owning_lib"/>
-                <update permission="URL_VERIFY" context_field="owning_lib"/>
-                <delete permission="URL_VERIFY" context_field="owning_lib"/>
-            </actions>
-        </permacrud>
+                       <field reporter:label="Interface" name="interface" reporter:datatype="link"/>
+                       <field reporter:label="Create Time" name="create_time" reporter:datatype="timestamp"/>
+                       <field reporter:label="Filters" name="filters" reporter:datatype="text"/>
+               </fields>
+               <links>
+                       <link field="owning_lib" reltype="has_a" key="id" map="" class="aou"/>
+                       <link field="creator" reltype="has_a" key="id" map="" class="au"/>
+                       <link field="interface" reltype="has_a" key="key" map="" class="cfdi"/>
+               </links>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+                       <actions>
+                               <create permission="SAVED_FILTER_DIALOG_FILTERS" context_field="owning_lib"/>
+                               <retrieve permission="SAVED_FILTER_DIALOG_FILTERS" context_field="owning_lib"/>
+                               <update permission="SAVED_FILTER_DIALOG_FILTERS" context_field="owning_lib"/>
+                               <delete permission="SAVED_FILTER_DIALOG_FILTERS" context_field="owning_lib"/>
+                       </actions>
+               </permacrud>
 
-    </class>
+       </class>
 
        <class id="cmrtm" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::marc21_rec_type_map" oils_persist:tablename="config.marc21_rec_type_map" reporter:label="MARC21 Record Type Map" oils_persist:field_safe="true">
                <fields oils_persist:primary="code">
index 0e49a00..ea1e4f7 100644 (file)
@@ -83,12 +83,8 @@ sub verify_session {
             select => {uvu => ['id']},
             from => {
                 uvu => { # url
-                    cbrebi => { # bucket item
-                        join => { cbreb => { # bucket
-                            join => { uvs => { # session
-                                filter => {id => $session_id}
-                            }}
-                        }}
+                    uvs => { # session
+                        filter => {id => $session_id}
                     }
                 }
             }
@@ -589,6 +585,7 @@ sub verify_one_url {
 
         if (my $loc = $res->headers->{location}) {
             $redir_url = Fieldmapper::url_verify::url->new;
+            $redir_url->session($attempt->session);
             $redir_url->redirect_from($url->id);
             $redir_url->full_url($loc);
 
index 25fa292..8463598 100644 (file)
@@ -1002,4 +1002,21 @@ CREATE UNIQUE INDEX unique_wwh ON config.usr_activity_type
     (COALESCE(ewho,''), COALESCE (ewhat,''), COALESCE(ehow,''));
 
 
+CREATE TABLE config.filter_dialog_interface (
+    key         TEXT                        PRIMARY KEY,
+    description TEXT
+);  
+
+CREATE TABLE config.filter_dialog_filter_set (
+    id          SERIAL                      PRIMARY KEY,
+    name        TEXT                        NOT NULL,
+    owning_lib  INT                         NOT NULL, -- REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
+    creator     INT                         NOT NULL, -- REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
+    create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
+    interface   TEXT                        NOT NULL REFERENCES config.filter_dialog_interface (key) DEFERRABLE INITIALLY DEFERRED,
+    filters     TEXT                        NOT NULL, -- CHECK (evergreen.is_json(filters))
+    CONSTRAINT cfdfs_name_once_per_lib UNIQUE (name, owning_lib)
+);
+
+
 COMMIT;
index db42861..7e39eba 100644 (file)
@@ -42,6 +42,7 @@ CREATE TABLE url_verify.url (
     id              SERIAL  PRIMARY KEY,
     redirect_from   INT     REFERENCES url_verify.url(id) DEFERRABLE INITIALLY DEFERRED,
     item            INT     REFERENCES container.biblio_record_entry_bucket_item (id) DEFERRABLE INITIALLY DEFERRED,
+    session         INT     REFERENCES url_verify.session (id) DEFERRABLE INITIALLY DEFERRED,
     url_selector    INT     REFERENCES url_verify.url_selector (id) DEFERRABLE INITIALLY DEFERRED,
     tag             TEXT,    
     subfield        TEXT,    
@@ -88,15 +89,5 @@ CREATE TABLE url_verify.url_verification (
     redirect_to INT                         REFERENCES url_verify.url (id) DEFERRABLE INITIALLY DEFERRED -- if redirected
 );
 
-CREATE TABLE url_verify.filter_set (
-    id          SERIAL                      PRIMARY KEY,
-    name        TEXT                        NOT NULL,
-    owning_lib  INT                         NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
-    creator     INT                         NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
-    create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
-    filter      TEXT                        NOT NULL,
-    CONSTRAINT uvfs_name_once_per_lib UNIQUE (name, owning_lib)
-);
 COMMIT;
 
index a49e5fc..5443d51 100644 (file)
@@ -91,8 +91,8 @@ BEGIN
                     JOIN container.biblio_record_entry_bucket_item c ON (c.target_biblio_record_entry = b.id)
               WHERE c.id = item_id;
 
-            INSERT INTO url_verify.url (item, url_selector, tag, subfield, ord, full_url)
-              VALUES ( item_id, current_selector.id, current_tag, current_sf, current_ord, current_url);
+            INSERT INTO url_verify.url (session, item, url_selector, tag, subfield, ord, full_url)
+              VALUES ( session_id, item_id, current_selector.id, current_tag, current_sf, current_ord, current_url);
 
             current_url_pos := current_url_pos + 1;
             current_ord := current_ord + 1;
index 53be2e1..e24ca9f 100644 (file)
@@ -126,4 +126,17 @@ ALTER TABLE config.z3950_source ADD CONSTRAINT use_perm_fkey FOREIGN KEY (use_pe
 
 ALTER TABLE config.org_unit_setting_type_log ADD CONSTRAINT config_org_unit_setting_type_log_fkey FOREIGN KEY (org) REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
 
+ALTER TABLE config.filter_dialog_filter_set
+    ADD CONSTRAINT config_filter_dialog_filter_set_owning_lib_fkey
+    FOREIGN KEY (owning_lib) REFERENCES actor.org_unit (id)
+    ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
+
+ALTER TABLE config.filter_dialog_filter_set
+    ADD CONSTRAINT config_filter_dialog_filter_set_creator_fkey
+    FOREIGN KEY (creator) REFERENCES actor.usr (id)
+    ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
+
+ALTER TABLE config.filter_dialog_filter_set
+    ADD CONSTRAINT config_filter_dialog_filter_set_filters_check
+    CHECK (evergreen.is_json(filters))
 COMMIT;
index d6f0547..120120e 100644 (file)
@@ -1577,7 +1577,11 @@ INSERT INTO permission.perm_list ( id, code, description ) VALUES
  ( 543, 'URL_VERIFY', oils_i18n_gettext( 543, 
     'Allows a user to process and verify ULSs', 'ppl', 'description')),
  ( 544, 'URL_VERIFY_UPDATE_SETTINGS', oils_i18n_gettext( 544, 
-    'Allows a user to configure URL verification org unit settings', 'ppl', 'description'))
+    'Allows a user to configure URL verification org unit settings', 'ppl', 'description')),
+ ( 545, 'SAVED_FILTER_DIALOG_FILTERS', oils_i18n_gettext( 545,
+    'Allows users to save and load sets of filters for filter dialogs, available in certain staff interfaces', 'ppl', 'description'))
+
+
 ;
 
 
@@ -11937,9 +11941,7 @@ INSERT INTO actor.search_filter_group_entry (grp, query, pos)
         (SELECT id FROM actor.search_filter_group WHERE code = 'kpac_main'),
         (SELECT id FROM actor.search_query WHERE label = 'Children''s Materials'),
         0
-    );
-INSERT INTO actor.search_filter_group_entry (grp, query, pos) 
-    VALUES (
+    ); INSERT INTO actor.search_filter_group_entry (grp, query, pos) VALUES (
         (SELECT id FROM actor.search_filter_group WHERE code = 'kpac_main'),
         (SELECT id FROM actor.search_query WHERE label = 'Young Adult Materials'),
         1
@@ -12080,6 +12082,53 @@ INSERT INTO config.org_unit_setting_type
         544
     );
 
+INSERT INTO config.filter_dialog_interface (key, description) VALUES (
+    'url_verify',
+    oils_i18n_gettext(
+        'url_verify',
+        'All Link Checker filter dialogs',
+        'cfdi',
+        'description'
+    )
+);
+
+INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
+    'url_verify.select_urls',
+    'url_verify',
+    FALSE,
+    oils_i18n_gettext(
+        'url_verify.select_urls',
+        'Link Checker''s URL Selection interface''s saved columns',
+        'cust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'url_verify.select_urls',
+        'Link Checker''s URL Selection interface''s saved columns',
+        'cust',
+        'description'
+    ),
+    'string'
+);
+
+INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
+    'url_verify.review_attempt',
+    'url_verify',
+    FALSE,
+    oils_i18n_gettext(
+        'url_verify.review_attempt',
+        'Link Checker''s Review Attempt interface''s saved columns',
+        'cust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'url_verify.review_attempt',
+        'Link Checker''s Review Attempt interface''s saved columns',
+        'cust',
+        'description'
+    ),
+    'string'
+);
 
 INSERT INTO config.org_unit_setting_type
     (name, grp, label, description, datatype, update_perm)
index 82da8ee..ddddd41 100644 (file)
@@ -29,6 +29,7 @@ CREATE TABLE url_verify.url (
     redirect_from   INT     REFERENCES url_verify.url(id) DEFERRABLE INITIALLY DEFERRED,
     item            INT     REFERENCES container.biblio_record_entry_bucket_item (id) DEFERRABLE INITIALLY DEFERRED,
     url_selector    INT     REFERENCES url_verify.url_selector (id) DEFERRABLE INITIALLY DEFERRED,
+    session         INT     REFERENCES url_verify.session (id) DEFERRABLE INITIALLY DEFERRED,
     tag             TEXT,
     subfield        TEXT,
     ord             INT,
@@ -74,14 +75,20 @@ CREATE TABLE url_verify.url_verification (
     redirect_to INT                         REFERENCES url_verify.url (id) DEFERRABLE INITIALLY DEFERRED -- if redirected
 );
 
-CREATE TABLE url_verify.filter_set (
+CREATE TABLE config.filter_dialog_interface (
+    key         TEXT                        PRIMARY KEY,
+    description TEXT
+);
+
+CREATE TABLE config.filter_dialog_filter_set (
     id          SERIAL                      PRIMARY KEY,
     name        TEXT                        NOT NULL,
     owning_lib  INT                         NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
     creator     INT                         NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
-    filter      TEXT                        NOT NULL,
-    CONSTRAINT uvfs_name_once_per_lib UNIQUE (name, owning_lib)
+    interface   TEXT                        NOT NULL REFERENCES config.filter_dialog_interface (key) DEFERRABLE INITIALLY DEFERRED,
+    filters     TEXT                        NOT NULL CHECK (evergreen.is_json(filters)),
+    CONSTRAINT cfdfs_name_once_per_lib UNIQUE (name, owning_lib)
 );
  
 COMMIT;
index f8a5bad..bed0ae3 100644 (file)
@@ -75,8 +75,8 @@ BEGIN
                     JOIN container.biblio_record_entry_bucket_item c ON (c.target_biblio_record_entry = b.id)
               WHERE c.id = item_id;
 
-            INSERT INTO url_verify.url (item, url_selector, tag, subfield, ord, full_url)
-              VALUES ( item_id, current_selector.id, current_tag, current_sf, current_ord, current_url);
+            INSERT INTO url_verify.url (session, item, url_selector, tag, subfield, ord, full_url)
+              VALUES ( session_id, item_id, current_selector.id, current_tag, current_sf, current_ord, current_url);
 
             current_url_pos := current_url_pos + 1;
             current_ord := current_ord + 1;
index f0f9846..4e14754 100644 (file)
@@ -30,6 +30,19 @@ INSERT INTO permission.perm_list (id, code, description)
     );
 
 
+INSERT INTO permission.perm_list (id, code, description) 
+    VALUES ( 
+        545, 
+        'SAVED_FILTER_DIALOG_FILTERS',
+        oils_i18n_gettext(
+            545, 
+            'Allows users to save and load sets of filters for filter dialogs, available in certain staff interfaces',
+            'ppl', 
+            'description'
+        )
+    );
+
+
 INSERT INTO config.settings_group (name, label)
     VALUES (
         'url_verify',
@@ -127,5 +140,54 @@ INSERT INTO config.org_unit_setting_type
     );
 
 
+INSERT INTO config.filter_dialog_interface (key, description) VALUES (
+    'url_verify',
+    oils_i18n_gettext(
+        'url_verify',
+        'All Link Checker filter dialogs',
+        'cfdi',
+        'description'
+    )
+);
+
+
+INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
+    'url_verify.select_urls',
+    'url_verify',
+    FALSE,
+    oils_i18n_gettext(
+        'url_verify.select_urls',
+        'Link Checker''s URL Selection interface''s saved columns',
+        'cust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'url_verify.select_urls',
+        'Link Checker''s URL Selection interface''s saved columns',
+        'cust',
+        'description'
+    ),
+    'string'
+);
+
+INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
+    'url_verify.review_attempt',
+    'url_verify',
+    FALSE,
+    oils_i18n_gettext(
+        'url_verify.review_attempt',
+        'Link Checker''s Review Attempt interface''s saved columns',
+        'cust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'url_verify.review_attempt',
+        'Link Checker''s Review Attempt interface''s saved columns',
+        'cust',
+        'description'
+    ),
+    'string'
+);
+
 COMMIT;
 
index 0bd3e5e..efdee63 100644 (file)
                 </div>
             </div>
             <div id="oils-base-main-block" dojoType="dijit.layout.LayoutContainer" layoutAlign="client">
+                [% IF no_content_pane %]
+                [% content %]
+                [% ELSE %]
                 <div id="oils-base-content-block" dojoType="dijit.layout.ContentPane" layoutAlign="client">
                     [% content %]
                 </div>
+                [% END %]
             </div>
         </div>
     </body>
index d29ccd9..72983e9 100644 (file)
@@ -20,6 +20,7 @@
 <style type="text/css">
     #uv-search { width: 20em; }
     .note { font-style: italic; background-color: #eee; }
+    #saved-searches { width: 15em; }
     #uv-tags-and-subfields { background-color: #ddd; }
     table.create-session-form th { text-align: right; padding-right: 1em; }
     table.create-session-form {
@@ -55,7 +56,7 @@
                     <div id="org-selector"></div>
                 </td>
                 <td class="note">
-                    [% l("This will only be used if your search doesn't contain a hand-entered filter such as site(BR1)") %]
+                    [% l("This will only be used if your search doesn't contain an explicit filter such as site(BR1)") %]
                 </td>
             </tr>
 
                     constant load time regardless of dataset size. -->
                     <select id="saved-searches" multiple="true" size="6"></select>
                 </td>
-                <td class="note">[% l("Optionally select one or more to combine with 'Search' field above.") %]
+                <td class="note">
+                    <p>[% l("Optionally select one or more to combine with 'Search' field above.") %]</p>
+                    <p class="hidden" id="clone-saved-search-warning">
+                        [% l("NOTE: When cloning sessions, any saved searches used in the original session will already be mentioned in the 'Search' field above. You should not need to select them again here.") %]
+                    </p>
                 </td>
             </tr>
 
 
             <tr>
                 <th>
-                    [% l('Tags and subfields possibly containing URLs:') %]
+                    [% l('Tags and subfields to search for URLs:') %]
                 </th>
                 <td>
                     <div id="uv-tags-and-subfields">
                     </div>
                     <div class="tag-and-subfield-add-another">
                         [% l("Tag") %]
-                        <input type="text" size="4" maxlength="3" />
+                        <input type="text" size="3" maxlength="3" />
                         [% l("Subfield(s)") %]
-                        <input type="text" size="15" />
+                        <input type="text" size="8" />
                         <a href="javascript:module.tag_and_subfields.add();">[% l('Add') %]</a>
                     </div>
                 </td>
diff --git a/Open-ILS/src/templates/url_verify/review_attempt.tt2 b/Open-ILS/src/templates/url_verify/review_attempt.tt2
new file mode 100644 (file)
index 0000000..8252bfd
--- /dev/null
@@ -0,0 +1,78 @@
+[% WRAPPER base.tt2 %]
+[% ctx.page_title = "Link Checker - Review Verification Attempt" %]
+<script type="text/javascript">
+    dojo.require("dijit.form.Button");
+    dojo.require("openils.widget.FlattenerGrid");
+    dojo.require("openils.widget.ProgressDialog");
+    dojo.require("openils.Util");
+    dojo.require("openils.CGI");
+    dojo.require("openils.URLVerify.ReviewAttempt");
+
+    /* Minimize namespace pollution, but save us some typing later. */
+    var module = openils.URLVerify.ReviewAttempt;
+
+    openils.Util.addOnLoad(
+        function() {
+            module.setup(grid, progress_dialog);
+        }
+    );
+</script>
+<style type="text/css">
+    .url-verify-attempt-info { font-style: italic; }
+    #session-link-here { font-weight: normal; font-size: 90%; }
+</style>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+    <div dojoType="dijit.layout.ContentPane"
+         layoutAlign="top" class="oils-header-panel">
+        <div>[% ctx.page_title %] - <span id="session-link-here"></span></div>
+        <div>
+            <button dojoType="dijit.form.Button" onClick="grid.print();">
+                [% l("Print verification results") %]
+            </button>
+        </div>
+    </div>
+    <div class="oils-acq-basic-roomy">
+        <!-- any blurb to precede grid can go here -->
+    </div>
+    <table
+        jsid="grid"
+        dojoType="openils.widget.FlattenerGrid"
+        columnPersistKey='"url_verify.review_attempt"'
+        autoHeight="10"
+        editOnEnter="false"
+        autoCoreFields="true"
+        autoCoreFieldsFilter="true"
+        autoCoreFieldsUnsorted="true"
+        savedFiltersInterface="'url_verify'"
+        fetchLock="true"
+        mapExtras="{attempt_id:{path:'attempt.id',filter:true,_label:'[% l("Attempt ID") %]'}}"
+        showLoadFilter="true"
+        fmClass="'uvuv'">
+        <thead>
+            <tr>
+                <th field="redirect_from" fpath="url.redirect_from"></th><!-- From here through fragment, these are fields from uvu.  We could have just used autoFieldFields on the FlattenerGrid, but by naming them explicitly we use the same field names that we use in the URL selection interface, allowing the two interfaces to use common saved filter sets. -->
+                <th field="tag" fpath="url.tag" ffilter="true"></th>
+                <th field="subfield" fpath="url.subfield" ffilter="true"></th>
+                <th field="ord" fpath="url.ord" ffilter="true"></th>
+                <th field="full_url" fpath="url.full_url" ffilter="true"></th>
+                <th field="scheme" fpath="url.scheme" _visible="false" ffilter="true"></th>
+                <th field="host" fpath="url.host" _visible="false" ffilter="true"></th>
+                <th field="domain" fpath="url.domain" _visible="false" ffilter="true"></th>
+                <th field="tld" fpath="url.tld" _visible="false" ffilter="true"></th>
+                <th field="path" fpath="url.path" _visible="false" ffilter="true"></th>
+                <th field="page" fpath="url.page" _visible="false" ffilter="true"></th>
+                <th field="query" fpath="url.query" _visible="false" ffilter="true"></th>
+                <th field="fragment" fpath="url.fragment" _visible="false" ffilter="true"></th>
+                <th field="title" fpath="url.item.target_biblio_record_entry.simple_record.title"></th>
+                <th field="author" fpath="url.item.target_biblio_record_entry.simple_record.author"></th>
+                <th field="isbn" fpath="url.item.target_biblio_record_entry.simple_record.isbn" _visible="false"></th>
+                <th field="issn" fpath="url.item.target_biblio_record_entry.simple_record.issn" _visible="false"></th>
+                <th field="bib_id" fpath="url.item.target_biblio_record_entry.id" _visible="false"></th>
+            </tr>
+        </thead>
+    </table>
+</div>
+<div class="hidden">
+    <div dojoType="openils.widget.ProgressDialog" jsId="progress_dialog"></div>
+</div>
+[% END %]
index b3985d9..8ad683a 100644 (file)
@@ -1,4 +1,4 @@
-[% WRAPPER base.tt2 %]
+[% WRAPPER base.tt2 no_content_pane=1 %]
 [% ctx.page_title = "Link Checker - Select URLs" %]
 <script type="text/javascript">
     dojo.require("dijit.form.Button");
 </script>
 <style type="text/css">
     .url-verify-attempt-info { font-style: italic; }
+    #session-name-here { font-weight: normal; font-size: 90%; }
 </style>
-<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
-    <div dojoType="dijit.layout.ContentPane"
-         layoutAlign="top" class="oils-header-panel">
-        <div>[% ctx.page_title %]</div>
-        <div>
-            <button dojoType="dijit.form.Button"
-                onClick="module.verify_selected();">[%
-                l("Verify Selected URLs")
-            %]</button>
-        </div>
+<div class="oils-header-panel" dojoType="dijit.layout.ContentPane" layoutAlign="top">
+    <div>[% ctx.page_title %] - <span id="session-name-here"></span></div>
+    <div class="url-verify-button">
+        <button dojoType="dijit.form.Button"
+            onClick="grid.print();">[%
+            l("Print URLs")
+        %]</button>
+        <button dojoType="dijit.form.Button"
+            onClick="module.verify_selected();">[%
+            l("Verify Selected URLs")
+        %]</button>
     </div>
-    <div class="oils-acq-basic-roomy url-verify-attempt-info">
-        <div id="url-verify-attempt-id"></div>
-        <div id="url-verify-attempt-start"></div>
-        <div id="url-verify-attempt-finish"></div>
-    </div>
-    <table
-        jsid="grid"
+</div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="bottom" style="height: 85%;">
+    <table jsid="grid"
         dojoType="openils.widget.FlattenerGrid"
-        columnPersistKey='"url_verify.select_url"'
-        autoHeight="10"
+        columnPersistKey="'url_verify.select_urls'"
         editOnEnter="false"
-        autoFieldFields="null"
         autoCoreFields="true"
+        autoCoreFieldsFilter="true"
         autoCoreFieldsUnsorted="true"
+        savedFiltersInterface="'url_verify'"
         fetchLock="true"
-        mapExtras="{session_id: {path: 'item.session.id', filter: true}}"
+        mapExtras="{session_id: {path: 'session.id', filter: true}}"
         showLoadFilter="true"
         fmClass="'uvu'">
         <thead>
                 <th field="isbn" fpath="item.target_biblio_record_entry.simple_record.isbn" _visible="false"></th>
                 <th field="issn" fpath="item.target_biblio_record_entry.simple_record.issn" _visible="false"></th>
                 <th field="bib_id" fpath="item.target_biblio_record_entry.id" _visible="false"></th>
+                <!-- You do NOT want to add the "verifications" column to this
+                table with ffilter="true".  That introduces a left join to a
+                table linked to the core table in has-many relationship.  When
+                PCRUD tries a query like that, it can return fewer objects than
+                its LIMIT clause called for, even when there are more objects
+                left.  This may need to be a bug report against flattener or
+                cstore/pcrud(?) but I'm not sure I understand the expectations. -->
             </tr>
         </thead>
     </table>
diff --git a/Open-ILS/src/templates/url_verify/sessions.tt2 b/Open-ILS/src/templates/url_verify/sessions.tt2
new file mode 100644 (file)
index 0000000..6458705
--- /dev/null
@@ -0,0 +1,51 @@
+[% WRAPPER base.tt2 no_content_pane=1 %]
+[% ctx.page_title = "Link Checker" %]
+<script type="text/javascript">
+    dojo.require("dijit.form.Button");
+    dojo.require("openils.widget.FlattenerGrid");
+    dojo.require("openils.widget.OrgUnitFilteringSelect")
+    dojo.require("openils.URLVerify.Sessions");
+
+    /* Minimize namespace pollution, but save us some typing later. */
+    var module = openils.URLVerify.Sessions;
+
+    openils.Util.addOnLoad(
+        function() {
+            module.setup(grid, org_selector);
+        }
+    );
+</script>
+<div class="oils-header-panel" dojoType="dijit.layout.ContentPane" layoutAlign="top">
+    <div>[% ctx.page_title %]</div>
+    <div>
+        <strong><a href="create_session">[% l("New Link Checker Session") %]</a></strong>
+    </div>
+</div>
+<div class="oils-acq-basic-roomy" dojoType="dijit.layout.ContentPane" layoutAlign="top">
+    <label for="org_selector">[% l("View existing sessions started at:") %]</label>
+    <select id="org_selector" jsId="org_selector"
+        dojoType="openils.widget.OrgUnitFilteringSelect"
+        searchAttr="name" labelAttr="name">
+    </select>
+</div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="bottom" style="height: 85%;">
+    <table jsid="grid"
+        dojoType="openils.widget.FlattenerGrid"
+        editOnEnter="false"
+        hideSelector="true"
+        fetchLock="true"
+        fmClass="'uvs'">
+        <thead>
+            <tr>
+                <th field="id" fpath="id" formatter="module.format_id"></th>
+                <th field="name" fpath="name" width="auto"></th>
+                <th field="attempts" fpath="attempts.id" formatter="module.format_attempts" width="auto">[% l("Verification Attempts") %]</th>
+                <th field="creator" fpath="creator.usrname">[% l("Creator ") %]</th>
+                <th field="create_time" fpath="create_time" width="auto"></th>
+                <th field="search" fpath="search" width="auto"></th>
+                <th field="selectors" fpath="selectors.xpath" width="auto">[% l("URL Selectors") %]</th>
+            </tr>
+        </thead>
+    </table>
+</div>
+[% END %]
index 6f3f989..d36100c 100644 (file)
@@ -182,7 +182,9 @@ if (!dojo._hasResource["openils.FlattenerStore"]) {
             if (!this.mapKey)
                 this._get_map_key();
 
-            return this._build_flattener_params(req);
+            var p = this._build_flattener_params(req);
+            console.debug("_fetch_prepare() returning " + dojo.toJson(p));
+            return p;
         },
 
         "_fetch_execute": function(params,handle_as,mime_type,onload,onerror) {
@@ -390,9 +392,17 @@ if (!dojo._hasResource["openils.FlattenerStore"]) {
                     else
                         might_be_a_lie += obj.length;
 
+                    console.debug(
+                        "process_fetch() calling onBegin with " +
+                        might_be_a_lie + ", " + dojo.toJson(req)
+                    );
                     req.onBegin.call(callback_scope, might_be_a_lie, req);
                 }
 
+                console.debug(
+                    "about to call onItem for " + obj.length +
+                    " elements in the obj array"
+                );
                 dojo.forEach(
                     obj,
                     function(item) {
index cb79e82..c17681c 100644 (file)
@@ -3,8 +3,10 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) {
     dojo.require("dojox.jsonPath");
     dojo.require("fieldmapper.OrgUtils");
     dojo.require("openils.Util");
+    dojo.require("openils.CGI");
     dojo.require("openils.PermaCrud");
     dojo.require("openils.widget.FilteringTreeSelect");
+    dojo.require("openils.URLVerify.Verify");
 
     dojo.requireLocalization("openils.URLVerify", "URLVerify");
 
@@ -87,7 +89,7 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) {
         );
     };
 
-    /* 2) save the tag/subfield sets for URL extraction, */
+    /* 2a) save the tag/subfield sets for URL extraction, */
     module.save_tags = function() {
         module.progress_dialog.attr("title", localeStrings.SAVING_TAGS);
         module.progress_dialog.show(); /* sic */
@@ -95,7 +97,8 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) {
         uvus_progress = 0;
 
         /* Note we're not using openils.PermaCrud, which is inadequate
-         * when you want transactions. Thanks for figuring it out, Bill. */
+         * when you need one big transaction. Thanks for figuring it
+         * out Bill. */
         var pcrud_raw = new OpenSRF.ClientSession("open-ils.pcrud");
 
         pcrud_raw.connect();
@@ -106,9 +109,7 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) {
             "oncomplete": function(r) {
                 module._create_uvus_one_at_a_time(
                     pcrud_raw,
-                    module.tag_and_subfields.generate_uvus(
-                        module.session_id
-                    )
+                    module.tag_and_subfields.generate_uvus(module.session_id)
                 );
             }
         }).send();
@@ -125,7 +126,7 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) {
                     {"maximum": uvus_list.length, "progress": ++uvus_progress}
                 );
 
-                uvus_list.shift();  /* /now/ actually shorten the list */
+                uvus_list.shift();  /* /now/ actually shorten working list */
 
                 if (uvus_list.length < 1) {
                     pcrud_raw.request({
@@ -147,8 +148,9 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) {
     };
 
     /* 3) search and populate the container (API call). */
-    var search_result_count = 0;
     module.perform_search = function() {
+        var search_result_count = 0;
+
         module.progress_dialog.attr("title", localeStrings.PERFORMING_SEARCH);
         module.progress_dialog.show(true);
 
@@ -180,11 +182,13 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) {
                     module.progress_dialog.show(true);
 
                     if (no_url_selection.checked) {
-                        location.href = oilsBasePath +
-                            "/url_verify/validation_review?" +
-                            "session_id=" + module.session_id +
-                            "&validate=1";
+                        /* verify URLs and ultimately redirect to review page */
+                        openils.URLVerify.Verify.go(
+                            module.session_id, null, module.progress_dialog
+                        );
                     } else {
+                        /* go to the URL selection page, allowing users to
+                         * selectively verify URLs */
                         location.href = oilsBasePath +
                             "/url_verify/select_urls?session_id=" +
                             module.session_id;
@@ -200,14 +204,12 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) {
      * fewer moving parts that can go haywire anyway.
      */
     module._populate_saved_searches = function(node) {
-        var pcrud = new openils.PermaCrud();
-        var list = pcrud.retrieveAll(
+        var list = module.pcrud.retrieveAll(
             "asq", {"order_by": {"asq": "label"}}
         );
 
         dojo.forEach(
-            list,
-            function(o) {
+            list, function(o) {
                 dojo.create(
                     "option", {
                         "innerHTML": o.label(),
@@ -217,8 +219,6 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) {
                 );
             }
         );
-
-        pcrud.disconnect();
     };
 
     /* set up an all-org-units-in-the-tree selector */
@@ -234,7 +234,34 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) {
         module.org_selector = widget;
     };
 
+    /* Can only be called by setup() */
+    module._clone = function(id) {
+        module.progress_dialog.attr("title", localeStrings.CLONING);
+        var old_session = module.pcrud.retrieve(
+            "uvs", id, {"flesh": 1, "flesh_fields": {"uvs": ["selectors"]}}
+        );
+
+        /* Set name to "Copy of [old name]" */
+        uv_session_name.attr(
+            "value", dojo.string.substitute(
+                localeStrings.CLONE_SESSION_NAME, [old_session.name()]
+            )
+        );
+
+        /* Set search field. */
+        uv_search.attr("value", old_session.search());
+
+        /* Explain to user why we don't affect the saved searches picker. */
+        if (old_session.search().match(/saved_query/))
+            openils.Util.show("clone-saved-search-warning");
+
+        /* Add related xpaths (URL selectors) to TagAndSubfieldsMgr. */
+        module.tag_and_subfields.add_xpaths(old_session.selectors());
+    };
+
     module.setup = function(saved_search_id, org_selector_id, progress_dialog) {
+        module.pcrud = new openils.PermaCrud(); /* only used for setup */
+
         module.progress_dialog = progress_dialog;
 
         module.progress_dialog.attr("title", localeStrings.INTERFACE_SETUP);
@@ -243,6 +270,12 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) {
         module._populate_saved_searches(dojo.byId(saved_search_id));
         module._prepare_org_selector(dojo.byId(org_selector_id));
 
+        var cgi = new openils.CGI();
+        if (cgi.param("clone"))
+            module._clone(cgi.param("clone"));
+
+        module.pcrud.disconnect();
+
         module.progress_dialog.hide();
     };
 
@@ -286,8 +319,37 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) {
                     "innerHTML": "[X]" /* XXX i18n */
                 }, div, "last"
             );
+        };
 
-            this.counter++;
+        this.add_xpaths = function(xpaths) {
+            if (!dojo.isArray(xpaths)) {
+                console.info("No xpaths to add");
+                return;
+            }
+
+            dojo.forEach(
+                xpaths, dojo.hitch(this, function(xpath) {
+                    var newid = "t-and-s-row-" + String(this.counter++);
+                    var div = dojo.create(
+                        "div", {
+                            "id": newid,
+                            "innerHTML": localeStrings.XPATH +
+                                " <span class='t-and-s-xpath'>" +
+                                xpath.xpath() + "</span>"
+                        }, this.container_id, "last"
+                    );
+                    dojo.create(
+                        "a", {
+                            "href": "javascript:void(0);",
+                            "onclick": function() {
+                                var me = dojo.byId(newid);
+                                me.parentNode.removeChild(me);
+                            },
+                            "innerHTML": "[X]" /* XXX i18n */
+                        }, div, "last"
+                    );
+                })
+            );
         };
 
         /* return a boolean indicating whether or not we have any rows */
@@ -307,27 +369,52 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) {
                 '[id^="t-and-s-row-"]', dojo.byId(this.container_id)
             ).forEach(
                 function(row) {
-                    var tag = dojo.query(".t-and-s-tag", row)[0].innerHTML;
-                    var subfield = dojo.query(".t-and-s-subfields", row)[0].innerHTML;
-
-                    var existing;
-                    if ((existing = uniquely_grouped[tag])) { /* sic, assignment */
-                        existing = openils.Util.uniqueElements(
-                            (existing + subfield).split("")
-                        ).sort().join("");
+                    var holds_xpath = dojo.query(".t-and-s-xpath", row);
+                    if (holds_xpath.length) {
+                        uniquely_grouped.xpath = uniquely_grouped.xpath || [];
+                        uniquely_grouped.xpath.push(holds_xpath[0].innerHTML);
                     } else {
-                        uniquely_grouped[tag] = subfield;
+                        var tag = dojo.query(".t-and-s-tag", row)[0].innerHTML;
+                        var subfield =
+                            dojo.query(".t-and-s-subfields", row)[0].innerHTML;
+
+                        var existing;
+                        if ((existing = uniquely_grouped[tag])) { // assignment
+                            existing = openils.Util.uniqueElements(
+                                (existing + subfield).split("")
+                            ).sort().join("");
+                        } else {
+                            uniquely_grouped[tag] = subfield;
+                        }
                     }
                 }
             );
 
             var uvus_list = [];
+            /* Handle things that are already in XPath form first (these
+             * come from cloning link checker sessions. */
+            if (uniquely_grouped.xpath) {
+                dojo.forEach(
+                    uniquely_grouped.xpath,
+                    function(xpath) {
+                        var obj = new uvus();
+
+                        obj.session(session_id);
+                        obj.xpath(xpath);
+                        uvus_list.push(obj);
+                    }
+                );
+                delete uniquely_grouped.xpath;
+            }
+
+            /* Now handle anything entered by hand. */
             for (var tag in uniquely_grouped) {
                 var obj = new uvus();
 
                 obj.session(session_id);
 
-                /* XXX TODO handle control fields (no subfields) */
+                /* XXX TODO Handle control fields (No subfields. but would
+                 * control fields ever contain URLs? Don't know.) */
                 obj.xpath(
                     "//*[@tag='" + tag + "']/*[" +
                     uniquely_grouped[tag].split("").map(
diff --git a/Open-ILS/web/js/dojo/openils/URLVerify/ReviewAttempt.js b/Open-ILS/web/js/dojo/openils/URLVerify/ReviewAttempt.js
new file mode 100644 (file)
index 0000000..d9a1cf5
--- /dev/null
@@ -0,0 +1,67 @@
+if (!dojo._hasResource["openils.URLVerify.ReviewAttempt"]) {
+    dojo.require("dojo.string");
+    dojo.require("openils.CGI");
+    dojo.require("openils.PermaCrud");
+    dojo.require("dijit.Tooltip");
+
+    dojo.requireLocalization("openils.URLVerify", "URLVerify");
+
+    dojo._hasResource["openils.URLVerify.ReviewAttempt"] = true;
+    dojo.provide("openils.URLVerify.ReviewAttempt");
+
+    dojo.declare("openils.URLVerify.ReviewAttempt", null, {});
+
+    /* Take care that we add nothing to the global namespace.
+     * This is not an OO module so much as a container for
+     * functions needed by a specific interface. */
+
+(function() {
+    var module = openils.URLVerify.ReviewAttempt;
+    var localeStrings =
+        dojo.i18n.getLocalization("openils.URLVerify", "URLVerify");
+
+    module._display_session_name = function() {
+        var pcrud = new openils.PermaCrud();
+
+        var attempt = pcrud.retrieve(
+            "uvva", module.attempt_id, {
+                "flesh": 1, "flesh_fields": {"uvva": ["session"]}
+            }
+        );
+
+        dojo.byId("session-link-here").innerHTML =
+            "<a href='select_urls?session_id=" + attempt.session().id() + "'>" +
+            dojo.string.substitute(
+                localeStrings.SESSION_NAME, [attempt.session().name()]
+            ) + "</a>";
+
+        pcrud.disconnect();
+
+        new dijit.Tooltip({
+            "connectId": "session-link-here",
+            "label": localeStrings.SELECT_MORE
+        });
+    };
+
+    module.setup = function(grid, progress_dialog) {
+        module.progress_dialog = progress_dialog;
+        module.progress_dialog.attr("title", localeStrings.INTERFACE_SETUP);
+        module.progress_dialog.show(true);
+
+        var cgi = new openils.CGI();
+        module.attempt_id = cgi.param("attempt_id");
+
+        module.grid = grid;
+
+        module.grid.setBaseQuery({"attempt_id": module.attempt_id});
+
+        module.grid.refresh();
+
+        module._display_session_name();
+
+        module.progress_dialog.hide();
+    };
+
+}());
+
+}
index b8992ac..83e5fdf 100644 (file)
@@ -2,6 +2,8 @@ if (!dojo._hasResource["openils.URLVerify.SelectURLs"]) {
     dojo.require("dojo.string");
     dojo.require("openils.CGI");
     dojo.require("openils.Util");
+    dojo.require("openils.PermaCrud");
+    dojo.require("openils.URLVerify.Verify");
 
     dojo.requireLocalization("openils.URLVerify", "URLVerify");
 
@@ -20,84 +22,78 @@ if (!dojo._hasResource["openils.URLVerify.SelectURLs"]) {
         dojo.i18n.getLocalization("openils.URLVerify", "URLVerify");
 
     module.setup = function(grid, progress_dialog) {
+        module.progress_dialog = progress_dialog;
+        module.progress_dialog.attr("title", localeStrings.INTERFACE_SETUP);
+        module.progress_dialog.show(true);
+
         var cgi = new openils.CGI();
         module.session_id = cgi.param("session_id");
 
         module.grid = grid;
 
-        module.grid.attr("query", {"session_id": module.session_id});
+        module.grid.setBaseQuery({"session_id": module.session_id});
+
         module.grid.refresh();
         // Alternative to grid.refresh() once filter is set up
         //module.grid.fetchLock = false;
         //module.grid.filterUi.doApply();
+
+        module._display_session_name();
+
+        module.progress_dialog.hide();
     };
 
-    module.verify_selected = function() {
-        var really_everything = false;
-
-        if (module.grid.everythingSeemsSelected())
-            really_everything = confirm(localeStrings.VERIFY_ALL);
-
-        module.clear_attempt_display();
-        progress_dialog.attr("title", localeStrings.VERIFICATION_BEGIN);
-        progress_dialog.show();
-
-        fieldmapper.standardRequest(
-            ["open-ils.url_verify", "open-ils.url_verify.session.verify"], {
-                "params": [
-                    openils.User.authtoken,
-                    module.session_id,
-                    really_everything ? null : module.grid.getSelectedIDs()
-                ],
+    module._display_session_name = function() {
+        var pcrud = new openils.PermaCrud();
+
+        pcrud.retrieve(
+            "uvs", module.session_id, {
                 "async": true,
-                "onresponse": function(r) {
+                "oncomplete": function(r) {
                     if (r = openils.Util.readResponse(r)) {
-                        progress_dialog.attr(
-                            "title",
+                        dojo.byId("session-name-here").innerHTML =
                             dojo.string.substitute(
-                                localeStrings.VERIFICATION_PROGRESS,
-                                [r.total_processed]
-                            )
-                        );
-                        progress_dialog.update({
-                            "maximum": r.url_count,
-                            "progress": r.total_excluding_redirects
-                        });
-
-                        if (r.attempt)
-                            module.update_attempt_display(r.attempt);
+                                localeStrings.SESSION_NAME, [r.name()]
+                            );
+
+                        pcrud.disconnect();
                     }
                 }
             }
-        )
-
-        module.grid.getSelectedIDs();   
+        );
     };
 
-    module.clear_attempt_display = function() {
-        dojo.empty(dojo.byId("url-verify-attempt-id"));
-        dojo.empty(dojo.byId("url-verify-attempt-start"));
-        dojo.empty(dojo.byId("url-verify-attempt-finish"));
-    };
+    module.verify_selected = function() {
+        if (module.grid.getSelectedItems().length < 1) {
+            alert(localeStrings.NOTHING_SELECTED);
+            return;
+        }
 
-    module.update_attempt_display = function(attempt) {
-        dojo.byId("url-verify-attempt-id").innerHTML =
-            dojo.string.substitute(
-                localeStrings.VERIFICATION_ATTEMPT_ID,
-                [attempt.id()]
-            );
-        dojo.byId("url-verify-attempt-start").innerHTML =
-            dojo.string.substitute(
-                localeStrings.VERIFICATION_ATTEMPT_START,
-                [attempt.start_time()]
+        if (module.grid.everythingSeemsSelected() &&
+            confirm(localeStrings.VERIFY_ALL)) {
+            /* If we're here, the user wants to verify all URLs matching
+             * the grid's current filters. We need to reach down to the
+             * grid's store to do a special fetch to get all those IDs. */
+
+            module.grid.store.fetch({
+                "query": dojo.clone(module.grid.query),
+                "queryOptions": {"all": true},
+                "onComplete": function(rows) {
+                    openils.URLVerify.Verify.go(
+                        module.session_id,
+                        dojo.map(rows, function(row) { return row.id; }),
+                        module.progress_dialog
+                    );
+                }
+            });
+        } else {
+            /* If we're here, the user wants to verify just the rows he
+             * specifically selected with the checkboxes. */
+            openils.URLVerify.Verify.go(
+                module.session_id,
+                module.grid.getSelectedIDs(),
+                module.progress_dialog
             );
-
-        if (attempt.finish_time()) {
-            dojo.byId("url-verify-attempt-finish").innerHTML =
-                dojo.string.substitute(
-                    localeStrings.VERIFICATION_ATTEMPT_FINISH,
-                    [attempt.finish_time()]
-                );
         }
     };
 
diff --git a/Open-ILS/web/js/dojo/openils/URLVerify/Sessions.js b/Open-ILS/web/js/dojo/openils/URLVerify/Sessions.js
new file mode 100644 (file)
index 0000000..a2283e5
--- /dev/null
@@ -0,0 +1,74 @@
+if (!dojo._hasResource["openils.URLVerify.Sessions"]) {
+    dojo.require("dojo.string");
+    dojo.require("openils.Util");
+    dojo.require("openils.URLVerify.Verify");
+
+    dojo.requireLocalization("openils.URLVerify", "URLVerify");
+
+    dojo._hasResource["openils.URLVerify.Sessions"] = true;
+    dojo.provide("openils.URLVerify.Sessions");
+
+    dojo.declare("openils.URLVerify.Sessions", null, {});
+
+    /* Take care that we add nothing to the global namespace.
+     * This is not an OO module so much as a container for
+     * functions needed by a specific interface. */
+
+(function() {
+    var module = openils.URLVerify.Sessions;
+    var localeStrings =
+        dojo.i18n.getLocalization("openils.URLVerify", "URLVerify");
+
+    module.setup = function(grid, org_selector) {
+        module.grid = grid;
+
+        module.setup_org_selector_for_grid(org_selector);
+    };
+
+    module.setup_org_selector_for_grid = function(org_selector) {
+        function filter_grid_by_selected_org() {
+            module.grid.query = {
+                "owning_lib": org_selector.attr("value")
+            };
+            module.grid.refresh();
+        }
+
+        new openils.User().buildPermOrgSelector(
+            "URL_VERIFY", org_selector, null,
+            function() {
+                dojo.connect(
+                    org_selector, "onChange", filter_grid_by_selected_org
+                );
+                filter_grid_by_selected_org();
+            }
+        );
+    };
+
+    module.format_id = function(str) {
+        if (!str)
+            return "";
+
+        return "<a href='select_urls?session_id=" + str + "' title='" +
+            localeStrings.REREVIEW + "'>" + str +
+            "</a> <a href='create_session?clone=" + str + "' title='" +
+            localeStrings.CLONE_SESSION + "'>" +
+            localeStrings.CLONE_SESSION + "</a>";
+    };
+
+    module.format_attempts = function(list) {
+        if (!dojo.isArray(list)) return "";
+
+        return dojo.map(
+            list, function(id) {
+                if (isNaN(id))
+                    return "";
+                return "<a title='" + localeStrings.REVIEW_ATTEMPT +
+                    "' href='review_attempt?attempt_id=" + id + "'>" +
+                    id + "</a>";
+            }
+        ).join(", ");
+    };
+
+}());
+
+}
diff --git a/Open-ILS/web/js/dojo/openils/URLVerify/Verify.js b/Open-ILS/web/js/dojo/openils/URLVerify/Verify.js
new file mode 100644 (file)
index 0000000..1839af8
--- /dev/null
@@ -0,0 +1,59 @@
+if (!dojo._hasResource["openils.URLVerify.Verify"]) {
+
+    dojo.requireLocalization("openils.URLVerify", "URLVerify");
+
+    dojo._hasResource["openils.URLVerify.Verify"] = true;
+    dojo.provide("openils.URLVerify.Verify");
+
+    dojo.declare("openils.URLVerify.Verify", null, {});
+
+    /* Take care that we add nothing to the global namespace.
+     * This is not an OO module so much as a container for
+     * functions needed by specific interfaces. */
+
+(function() {
+    var module = openils.URLVerify.Verify;
+    var localeStrings =
+        dojo.i18n.getLocalization("openils.URLVerify", "URLVerify");
+
+    module.go = function(session_id, url_id_list, progress_dialog) {
+        progress_dialog.attr("title", localeStrings.VERIFICATION_DIALOG);
+        progress_dialog.show();
+
+        fieldmapper.standardRequest(
+            ["open-ils.url_verify", "open-ils.url_verify.session.verify"], {
+                "params": [openils.User.authtoken, session_id, url_id_list],
+                "async": true,
+                "onresponse": function(r) {
+                    if (r = openils.Util.readResponse(r)) {
+                        progress_dialog.update({
+                            "maximum": r.url_count,
+                            "progress": r.total_excluding_redirects
+                        });
+
+                        if (r.attempt) {
+                            module.attempt = r.attempt;
+                            progress_dialog.show(
+                                false,
+                                dojo.string.substitute(
+                                    localeStrings.VERIFICATION_ATTEMPT_ID,
+                                    [r.attempt.id()]
+                                )
+                            );
+                        }
+                    }
+                },
+                "oncomplete": function() {
+                    progress_dialog.show(true);
+                    progress_dialog.attr("title", localeStrings.REDIRECTING);
+                    location.href = oilsBasePath +
+                        "/url_verify/review_attempt?attempt_id=" +
+                        module.attempt.id();
+                }
+            }
+        );
+    };
+
+}());
+
+}
index d0b6f3d..d2128db 100644 (file)
@@ -3,13 +3,19 @@
     "SAVING_TAGS": "Saving tag/subfield selectors ...",
     "PERFORMING_SEARCH": "Performing search ...",
     "EXTRACTING_URLS": "Extracting URLs ...",
-    "NEED_UVUS": "You must add some tag and subfield combinations",
+    "NEED_UVUS": "You must add some tag and subfield combinations.",
     "REDIRECTING": "Loading next interface ...",
     "INTERFACE_SETUP": "Setting up interface ...",
-    "VERIFY_ALL": "Click 'OK' to verify ALL the URLs found in this session.  Click 'Cancel' if the selected, visible URLs are the only ones you want verified.",
-    "VERIFICATION_BEGIN": "Verifying URLs. This can take a moment ...",
-    "VERIFICATION_PROGRESS": "Verifying URLs: ${0} URLs processed ...",
-    "VERIFICATION_ATTEMPT_ID": "Last verification attempt ID: ${0}",
-    "VERIFICATION_ATTEMPT_START": "Attempt start time: ${0}",
-    "VERIFICATION_ATTEMPT_FINISH": "Attempt finish time: ${0}"
+    "VERIFY_ALL": "Click 'OK' to verify ALL the URLs that belong to this session and match all your search terms.  Click 'Cancel' if the selected, visible URLs are the only ones you want verified.",
+    "VERIFICATION_DIALOG": "Verifying URLs ...",
+    "VERIFICATION_ATTEMPT_ID": "Verification attempt ID: ${0}",
+    "NOTHING_SELECTED": "No rows are selected, so no action will be taken.",
+    "REVIEW_ATTEMPT": "Review this verification attempt",
+    "CLONE_SESSION": "Clone",
+    "REREVIEW": "Review / Verify",
+    "CLONING": "Cloning existing session ...",
+    "CLONE_SESSION_NAME": "Copy of ${0}",
+    "XPATH": "XPath",
+    "SESSION_NAME": "Session '${0}'",
+    "SELECT_MORE": "Click here to review all session URLs and/or select other URLs to verify"
 }
index 187b69f..bad2762 100644 (file)
@@ -6,6 +6,24 @@ if (!dojo._hasResource["openils.widget.FlattenerFilterDialog"]) {
     dojo.declare(
         "openils.widget.FlattenerFilterDialog", [
             dijit.Dialog, openils.widget.FlattenerFilterPane
-        ]
+        ], {
+            "constructor": function() {
+                dojo.connect(
+                    this, "postCreate", this,
+                    function() {
+                        /* Of course I don't *want* to hardcode 400px below,
+                         * but without some kind of maxHeight, this dialog
+                         * can just grow forever, until it's no longer
+                         * possible to access the close button on the top or
+                         * the buttons at the bottom. */
+                        dojo.style(
+                            this.domNode, {
+                                "maxHeight": "400px", "overflow": "auto"
+                            }
+                        );
+                    }
+                );
+            }
+        }
     );
 }
index e83fca9..d66ee66 100644 (file)
@@ -18,7 +18,9 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
             "columnPersistKey": null,
             "autoCoreFields": false,
             "autoCoreFieldsUnsorted": false,
+            "autoCoreFieldsFilter": false,
             "autoFieldFields": null,
+            "autoFieldFieldsUnsorted": null, /* array, subset of autoFieldFields */
             "showLoadFilter": false,    /* use FlattenerFilter(Dialog|Pane) */
             "filterAlwaysInDiv": null,  /* use FlattenerFilterPane and put its
                                            content in this HTML element */
@@ -31,6 +33,7 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                                    OU selectors so that it should get mixed
                                    correctly with the generated query from the
                                    filter dialog. */
+            "savedFiltersInterface": null,
 
             /* These potential constructor arguments may be useful to
              * FlattenerGrid in their own right, and are passed to
@@ -286,16 +289,23 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                 return {"labels": labels, "columns": columns};
             },
 
-            "_getAutoFieldFields": function(fmclass) {
-                return dojo.clone(
+            "_getAutoFieldFields": function(fmclass, path) {
+                var field_list = dojo.clone(
                     fieldmapper.IDL.fmclasses[fmclass].fields)
                 .filter(
-                    function(field) {
-                        return !field.virtual && field.datatype != "link";
-                    }
-                ).sort(
-                    function(a, b) { return a.label > b.label ? 1 : -1; }
+                    function(f) { return !f.virtual && f.datatype != "link"; }
                 );
+                
+                /* Sort fields unless the path is named in grid property
+                 * 'autoFieldFieldsUnsorted' (array). */
+                if (!dojo.isArray(this.autoFieldFieldsUnsorted) ||
+                        this.autoFieldFieldsUnsorted.indexOf(path) == -1) {
+                    field_list = field_list.sort(
+                        function(a, b) { return a.label > b.label ? 1 : -1; }
+                    );
+                }
+
+                return field_list;
             },
 
             /* Take our core class (this.fmClass) and add table columns for
@@ -314,7 +324,7 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                 }
 
                 dojo.forEach(
-                    fields, function(f) {
+                    fields, dojo.hitch(this, function(f) {
                         if (f.datatype == "link" || f.virtual)
                             return;
 
@@ -329,10 +339,10 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                         cell_list.push({
                             "field": f.name,
                             "name": f.label,
-                            "fsort": true /*,
-                            "_visible": false */
+                            "fsort": true,
+                            "ffilter": this.autoCoreFieldsFilter
                         });
-                    }
+                    })
                 );
             },
 
@@ -350,7 +360,9 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                             return;
                         } else {
                             dojo.forEach(
-                                self._getAutoFieldFields(beginning.fmClass),
+                                self._getAutoFieldFields(
+                                    beginning.fmClass, path
+                                ),
                                 function(field) {
                                     var would_be_path =
                                         path + "." + field.name;
@@ -506,10 +518,10 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                             "fmClass": this.fmClass,
                             "mapTerminii": this.mapTerminii,
                             "useDiv": this.filterAlwaysInDiv,
-                            "compact": true,
                             "initializers": this.filterInitializers,
                             "widgetBuilders": this.filterWidgetBuilders,
-                            "suppressFilterFields": this.suppressFilterFields
+                            "suppressFilterFields": this.suppressFilterFields,
+                            "savedFiltersInterface": this.savedFiltersInterface
                         });
 
                     this.filterUi.onApply = dojo.hitch(
@@ -922,6 +934,13 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                     this.getSelectedIDs();
 
                 this.print(null, null, id_blob);
+            },
+
+            "setBaseQuery": function(query) {   /* sets a persistent query
+                                                   that always gets mixed in
+                                                   with whatever you do in the
+                                                   filter dialog */
+                this._baseQuery = dojo.clone(this.query = query);
             }
         }
     );
index 205deb4..4c6af87 100644 (file)
@@ -35,6 +35,8 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
     dojo.require('openils.widget.AutoFieldWidget');
     dojo.require('dijit.form.FilteringSelect');
     dojo.require('dijit.form.Button');
+    dojo.require('dijit.form.DropDownButton');
+    dojo.require('dijit.TooltipDialog');
     dojo.require('dojo.data.ItemFileReadStore');
     dojo.require('openils.Util');
 
@@ -99,6 +101,18 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
                         "param_count": 1,
                         "strict": true
                     }, {
+                        "name": "in",
+                        "label": pcFilterLocaleStrings.OPERATOR_IN,
+                        "param_count": null,    /* arbitrary number, special */
+                        "strict": true,
+                        "minimal": true
+                    }, {
+                        "name": "not in",
+                        "label": pcFilterLocaleStrings.OPERATOR_NOT_IN,
+                        "param_count": null,    /* arbitrary number, special */
+                        "strict": true,
+                        "minimal": true
+                    }, {
                         "name": "between",
                         "label": pcFilterLocaleStrings.OPERATOR_BETWEEN,
                         "param_count": 2,
@@ -151,7 +165,7 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
 
         var ops = openils.Util.objectProperties(clause);
         var op = ops.pop();
-        var matches = op.match(/^not (\w+)$/);
+        var matches = op.match(/^not [lb].+$/); /* "not in" needs no change */
         if (matches) {
             clause[matches[1]] = clause[op];
             delete clause[op];
@@ -160,6 +174,30 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
         return false;
     }
 
+    /* Given a value, add it to selector options if it's not already there,
+     * and select it. */
+    function _add_or_at_least_select(value, selector) {
+        var found = false;
+
+        for (var i = 0; i < selector.options.length; i++) {
+            var option = selector.options[i];
+            if (option.value == value) {
+                found = true;
+                option.selected = true;
+            }
+        }
+
+        if (!found) {
+            dojo.create(
+                "option", {
+                    "innerHTML": value,
+                    "value": value,
+                    "selected": "selected"
+                }, selector
+            );
+        }
+    }
+
     /* This is not the dijit per se. Search further in this file for
      * "dojo.declare" for the beginning of the dijit.
      *
@@ -249,20 +287,58 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
             return result;
         };
 
-        this.add_row = function(initializer) {
+        this._validate_initializer = function(initializer, onsuccess) {
+            this.field_store.fetchItemByIdentity({
+                "identity": initializer.field,
+                "onItem": dojo.hitch(this, function(item) {
+                    if (item) {
+                        onsuccess();
+                    } else {
+                        console.debug(
+                            "skipping initializer for field " +
+                            initializer.field + " not present here"
+                        );
+                    }
+                })
+            });
+        };
+
+        this._proceed_add_row = function(initializer) {
+            var row_id_list = openils.Util.objectProperties(this.rows);
+
+            /* Kill initial empty row when adding pre-initialized rows. */
+            if (row_id_list.length == 1 && initializer) {
+                var existing_row_id = row_id_list.shift();
+                if (this.rows[existing_row_id].is_unset())
+                    this.remove_row(existing_row_id, true /* no_apply */);
+            }
+
             this.hide_empty_placeholder();
             var row_id = this.row_index++;
             this.rows[row_id] = new PCrudFilterRow(this, row_id, initializer);
         };
 
-        this.remove_row = function(row_id) {
+        this.add_row = function(initializer) {
+            if (initializer) {
+                this._validate_initializer(
+                    initializer,
+                    dojo.hitch(this, function() {
+                        this._proceed_add_row(initializer);
+                    })
+                );
+            } else {
+                this._proceed_add_row(initializer);
+            }
+        };
+
+        this.remove_row = function(row_id, no_apply) {
             this.rows[row_id].destroy();
             delete this.rows[row_id];
 
             if (openils.Util.objectProperties(this.rows).length < 1)
                 this.show_empty_placeholder();
 
-            if (this.compact)
+            if (this.compact && !no_apply)
                 this.do_apply();
         };
 
@@ -320,6 +396,20 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
             }
         };
 
+        /* This is for generating a data structure so that we can store
+         * a representation of the state of the filter rows.  Not for
+         * generating a query to be used in search.  You want compile() for
+         * that. */
+        this.serialize = function() {
+            var serialized = [];
+            for (var rowkey in this.rows) { /* row order doesn't matter */
+                var row_ser = this.rows[rowkey].serialize();
+                if (row_ser)
+                    serialized.push(row_ser);
+            }
+            return dojo.toJson(serialized);
+        };
+
         this._init.apply(this, arguments);
     }
 
@@ -383,6 +473,11 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
                     "scrollOnFocus": false,
                     "onChange": function(value) {
                         self.update_selected_field(value);
+                        if (this.and_then) {    /* ugh. also, self != this. */
+                            var once = this.and_then;
+                            delete this.and_then;
+                            once();
+                        }
                     },
                     "store": this.filter_row_manager.field_store
                 }, dojo.create("span", {}, td)
@@ -413,12 +508,12 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
         };
 
         this._create_value_slot = function(use_element) {
+            var how = {"innerHTML": "-"};
+
             if (use_element)
-                this.value_slot = dojo.create(
-                    "span", {"innerHTML": "-"}, use_element
-                );
+                this.value_slot = dojo.create("span", how, use_element);
             else
-                this.value_slot = dojo.create("td",{"innerHTML":"-"},this.tr);
+                this.value_slot = dojo.create("td", how, this.tr);
         };
 
         this._create_remover = function(use_element) {
@@ -438,7 +533,10 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
         this._clear_value_slot = function() {
             if (this.value_widgets) {
                 this.value_widgets.forEach(
-                    function(autowidg) { autowidg.widget.destroy(); }
+                    function(autowidg) {
+                        if (autowidg.widget)
+                            autowidg.widget.destroy();
+                    }
                 );
                 delete this.value_widgets;
             }
@@ -454,28 +552,112 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
 
             this.value_widgets = [];
 
-            var param_count = this.operator_selector.item.param_count;
-
-            /* This is where find and deploy custom widget builders. */
+            /* This is where find custom widget builders to deploy shortly. */
             var widget_builder_key = this.selected_field_fm_class + ":" +
                 this.selected_field_fm_field;
             var constr =
                 this.filter_row_manager.widget_builders[widget_builder_key] ||
                 openils.widget.AutoFieldWidget;
 
-            for (var i = 0; i < param_count; i++) {
-                var widg = new constr({
-                    "fmClass": this.selected_field_fm_class,
-                    "fmField": this.selected_field_fm_field,
-                    "parentNode": dojo.create("span", {}, this.value_slot),
-                    "dijitArgs": {"scrollOnFocus": false}
-                });
+            /* How many value widgets do we need for this operator? */
+            var param_count =
+                this.operator_selector.store.getValue(
+                    this.operator_selector.item, "param_count"
+                );
 
-                widg.build();
-                this.value_widgets.push(widg);
+            if (param_count === null) {
+                /* When param_count is null, we invoke the special case of
+                 * preparing widgets for building a dynamic set of values.
+                 * All other cases are handled by the else branch. */
+                this._build_set_value_widgets(constr);
+            } else {
+                for (var i = 0; i < param_count; i++) {
+                    this.value_widgets.push(
+                        this._build_one_value_widget(constr)
+                    );
+                }
             }
         };
 
+        this._build_set_value_widgets = function(constr) {
+            var value_widget = dojo.create(
+                "select", {
+                    "multiple": "multiple",
+                    "size": 4,
+                    "style": {
+                        "width": "6em",
+                        "verticalAlign": "middle",
+                        "margin": "0 0.75em"
+                    }
+                },
+                this.value_slot
+            );
+            var entry_widget = this._build_one_value_widget(constr);
+            var adder = dojo.create(
+                "a", {
+                    "href": "javascript:void(0);",
+                    "style": {"verticalAlign": "middle", "margin": "0 0.75em"},
+                    "innerHTML": "[+]", /* XXX i18n? */
+                    "onclick": dojo.hitch(this, function() {
+                        _add_or_at_least_select(
+                            this._value_for_compile(entry_widget),
+                            value_widget
+                        );
+                        entry_widget.widget.attr("value", ""); /* clear */
+                    })
+                }, this.value_slot
+            );
+            this.value_widgets.push(value_widget);
+        };
+
+
+        /* Create just one value widget (used by higher-level functions
+         * that worry about how many are needed). */
+        this._build_one_value_widget = function(constr) {
+            var widg = new constr({
+                "fmClass": this.selected_field_fm_class,
+                "fmField": this.selected_field_fm_field,
+                "noDisablePkey": true,
+                "parentNode": dojo.create(
+                    "span", {
+                        "style": {"verticalAlign": "middle"}
+                    }, this.value_slot
+                ),
+                "dijitArgs": {"scrollOnFocus": false}
+            });
+
+            widg.build();
+            return widg;
+        };
+
+        this._value_for_serialize = function(widg) {
+            if (!widg.widget)   /* widg is <select> */
+                return dojo.filter(
+                    widg.options,
+                    function(o) { return o.selected; }
+                ).map(
+                    function(o) { return o.value; }
+                );
+            else
+                return widg.widget.attr("value");
+        };
+
+        this._value_for_compile = function(widg) {
+            if (!widg.widget)   /* widg is <select> */
+                return dojo.filter(
+                    widg.options,
+                    function(o) { return o.selected; }
+                ).map(
+                    function(o) { return o.value; }
+                );
+            else if (widg.useCorrectly)
+                return widg.widget.attr("value");
+            else if (this.selected_field_is_indirect)
+                return widg.widget.attr("displayedValue");
+            else
+                return widg.getFormattedValue();
+        }
+
         /* for ugly special cases in compilation */
         this._null_clause = function() {
             var opname = this.get_selected_operator_name();
@@ -498,8 +680,15 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
         };
 
         this.get_selected_operator_name = function() {
-            var op = this.get_selected_operator();
-            return op ? op.name : null;
+            var item = this.get_selected_operator();
+            if (item) {
+                return this.operator_selector.store.getValue(item, "name");
+            } else {
+                console.warn(
+                    "Could not determine selected operator. " +
+                    "Something is about to break."
+                );
+            }
         };
 
         this.update_selected_operator = function(value) {
@@ -526,17 +715,46 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
             }
         };
 
+        this.serialize = function() {
+            if (!this.selected_field)
+                return;
+
+            var serialized = {
+                "field": this.selected_field,
+                "operator": this.get_selected_operator_name()
+            };
+
+            var values;
+
+            if (this.value_widgets) {
+                values = this.value_widgets.map(
+                    dojo.hitch(
+                        this, function(w) {
+                            return this._value_for_serialize(w);
+                        }
+                    )
+                );
+            }
+
+            /* The following grew organically to be very silly and confusing.
+             * Could use a rethink (PCrudFilterRow.initialize() would also need
+             * matching changes). */
+            if (values.length == 1) {
+                if (dojo.isArray(values[0]))
+                    serialized.values = values[0];
+                else
+                    serialized.value = values[0];
+            } else if (values.length > 1) {
+                serialized.values = values;
+            }
+
+            return serialized;
+        };
+
         this.compile = function() {
             if (this.value_widgets) {
                 var values = this.value_widgets.map(
-                    function(widg) {
-                        if (widg.useCorrectly)
-                            return widg.widget.attr("value");
-                        else if (self.selected_field_is_indirect)
-                            return widg.widget.attr("displayedValue");
-                        else
-                            return widg.getFormattedValue();
-                    }
+                    dojo.hitch(this, this._value_for_compile)
                 );
 
                 if (!values.length) {
@@ -545,7 +763,13 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
                     var clause = {};
                     var op = this.get_selected_operator_name();
 
-                    var prep_function = function(o) { return o; /* no-op */ };
+                    var prep_function = function(o) {
+                        if (dojo.isArray(o) && !o.length)
+                            throw new Error(pcFilterLocaleStrings.EMPTY_LIST);
+
+                        return o;
+                    };
+
                     if (String(op).match(/like/))
                         prep_function = this._add_like_wildcards;
 
@@ -570,19 +794,43 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
         };
 
         this.initialize = function(initializer) {
+            /* and_then is a nasty kludge callback called once at onChange */
+            this.field_selector.and_then = dojo.hitch(
+                this, function() {
+                    this.operator_selector.attr("value", initializer.operator);
+
+                    /* Caller supplies value for one value, values (array) for
+                     * multiple. */
+                    if (typeof initializer.value !== "undefined" &&
+                            !initializer.values) {
+                        initializer.values = [initializer.value];
+                    }
+                    initializer.values = initializer.values || [];
+
+                    if (initializer.operator.match(/^(not ?)in$/)) {
+                        /* "in" and "not in" need special treatement */
+                        dojo.forEach(
+                            initializer.values, dojo.hitch(this, function(v) {
+                                _add_or_at_least_select(
+                                    v, this.value_widgets[0]
+                                );
+                            })
+                        );
+                    } else {
+                        /* other operators work this way: */
+                        for (var i = 0; i < initializer.values.length; i++) {
+                            this.value_widgets[i].widget.attr(
+                                "value", initializer.values[i]
+                            );
+                        }
+                    }
+                }
+            );
             this.field_selector.attr("value", initializer.field);
-            this.operator_selector.attr("value", initializer.operator);
-
-            /* Caller supplies value for one value, values (array) for
-             * multiple. */
-            if (!initializer.values || !dojo.isArray(initializer.values))
-                initializer.values = [initializer.values || initializer.value];
+        };
 
-            for (var i = 0; i < initializer.values.length; i++) {
-                this.value_widgets[i].widget.attr(
-                    "value", initializer.values[i]
-                );
-            }
+        this.is_unset = function() {
+            return !Boolean(this.field_selector.attr("value"));
         };
 
         this._init.apply(this, arguments);
@@ -593,15 +841,16 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
         {
             "useDiv": null, /* should always be null for subclass dialogs */
             "initializers": null,
-            "compact": false,
             "widgetBuilders": null,
             "suppressFilterFields": null,
+            "savedFiltersInterface": null,
 
             "constructor": function(args) {
                 for(var k in args)
                     this[k] = args[k];
                 this.widgetIndex = 0;
                 this.widgetCache = {};
+                this.compact = Boolean(this.useDiv);
 
                 /* Meaningless in a pane, but better here than in
                  * PCrudFilterDialog so that we don't need to load i18n
@@ -609,6 +858,124 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
                 this.title = this.title || pcFilterLocaleStrings.DEFAULT_DIALOG_TITLE;
             },
 
+            "_buildSavedFilterControlsIfPerms": function(holder) {
+                (new openils.User()).getPermOrgList(
+                    "SAVED_FILTER_DIALOG_FILTERS",
+                    dojo.hitch(this, function(id_list) {
+                        this._buildSavedFilterControls(id_list, holder);
+                    }),
+                    true, true
+                );
+            },
+
+            "_buildSavedFilterControls": function(id_list, holder) {
+                if (!id_list || !id_list.length) {
+                    console.info("Not showing saved filter controls; no perm");
+                    return;
+                }
+
+                var fs_list = (new openils.PermaCrud()).search(
+                    "cfdfs", {
+                        "owning_lib": id_list,
+                        "interface": this.savedFiltersInterface
+                    }, {
+                        "order_by": [
+                            {"class": "cfdfs", "field": "owning_lib"},
+                            {"class": "cfdfs", "field": "name"}
+                        ],
+                        "async": true,
+                        "oncomplete": dojo.hitch(this, function(r) {
+                            if (r = openils.Util.readResponse(r)) {
+                                this._buildSavedFilterLoader(r, holder);
+                            }
+                        })
+                    }
+                );
+
+                this._buildSavedFilterSaver(holder);
+            },
+
+            "_buildSavedFilterLoader": function(fs_list, holder) {
+                var self = this;
+                var load_content = dojo.create(
+                    "div", {
+                        "innerHTML": pcFilterLocaleStrings.CHOOSE_FILTER_TO_LOAD
+                    }
+                );
+
+                var selector = dojo.create(
+                    "select", {
+                        "multiple": "multiple",
+                        "size": 4,
+                        "style": {
+                            "verticalAlign": "middle", "margin": "0 0.75em"
+                        }
+                    }, load_content, "last"
+                );
+
+                dojo.forEach(
+                    fs_list, function(fs) {
+                        dojo.create(
+                            "option", {
+                                "innerHTML": fs.name(),
+                                "value": dojo.toJson([fs.id(),
+                                    dojo.fromJson(fs.filters())])
+                            }, selector
+                        );
+                    }
+                );
+
+                var applicator = dojo.create(
+                    "a", {
+                        "href": "javascript:void(0);",
+                        "onclick": function() {
+                            dojo.filter(
+                                selector.options,
+                                function(o){return o.selected;}
+                            ).map(
+                                function(o){return dojo.fromJson(o.value)[1];}
+                            ).forEach(
+                                function(o){
+                                    o.forEach(
+                                        function(p) {
+                                            self.filter_row_manager.add_row(p);
+                                        }
+                                    );
+                                }
+                            );
+                            dijit.popup.close(self.filter_set_loader.dropDown);
+                        },
+                        "innerHTML": pcFilterLocaleStrings.APPLY
+                    }, load_content, "last"
+                );
+
+                this.filter_set_loader = new dijit.form.DropDownButton({
+                    "dropDown": new dijit.TooltipDialog({
+                        "content": load_content
+                    }),
+                    "label": pcFilterLocaleStrings.LOAD_FILTERS
+                }, dojo.create("span", {}, holder));
+            },
+
+            "_buildSavedFilterSaver": function(holder) {
+                this.filter_set_loader = new dijit.form.Button({
+                    "onClick": dojo.hitch(
+                        this, function() {
+                            this.saveFilters(
+                                /* XXX I know some find prompt() objectionable
+                                 * somehow, but I can't seem to type into any
+                                 * text inputs that I put inside TooltipDialog
+                                 * instances, so meh. */
+                                prompt(
+                                    pcFilterLocaleStrings.NAME_SAVED_FILTER_SET
+                                )
+                            );
+                        }
+                    ),
+                    "label": pcFilterLocaleStrings.SAVE_FILTERS
+                }, dojo.create("span", {}, holder));
+            },
+
             "_buildButtons": function() {
                 var self = this;
 
@@ -649,6 +1016,9 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
                         }, dojo.create("span", {}, button_holder)
                     );
                 }
+
+                if (this.savedFiltersInterface)
+                    this._buildSavedFilterControlsIfPerms(button_holder);
             },
 
             "_buildFieldStore": function() {
@@ -692,6 +1062,35 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
                 });
             },
 
+            "saveFilters": function(name, oncomplete) {
+                var filters_value = this.filter_row_manager.serialize();
+                var filter_set = new cfdfs();
+                filter_set.name(name);
+                filter_set.interface(this.savedFiltersInterface);
+                filter_set.owning_lib(openils.User.user.ws_ou());
+                filter_set.creator(openils.User.user.id()); /* not reliable */
+                filter_set.filters(filters_value);
+
+                (new openils.PermaCrud()).create(
+                    filter_set, {
+                        "oncomplete": dojo.hitch(this, function() {
+                            var selector = dojo.query(
+                                "select[multiple]",
+                                this.filter_set_loader.dropDown.domNode
+                            )[0];
+                            dojo.create(
+                                "option", {
+                                    "innerHTML": name,
+                                    "value": dojo.toJson([-1,
+                                        dojo.fromJson(filters_value)])
+                                }, selector
+                            );
+                            if (oncomplete) oncomplete();
+                        })
+                    }
+                );
+            },
+
             "hide": function() {
                 try {
                     this.inherited(arguments);
index 8e76dd5..761c1d3 100644 (file)
@@ -7,6 +7,8 @@
     "OPERATOR_GT": "is greater than",
     "OPERATOR_LTE": "is less than or equal to",
     "OPERATOR_GTE": "is greater than or equal to",
+    "OPERATOR_IN": "is in the set",
+    "OPERATOR_NOT_IN": "is not in the set",
     "OPERATOR_BETWEEN": "is between",
     "OPERATOR_NOT_BETWEEN": "is not between",
     "OPERATOR_LIKE": "is like",
     "DEFAULT_DIALOG_TITLE": "Filter Results",
     "ADD_ROW": "Add Row",
     "APPLY": "Apply",
-    "CANCEL": "Cancel"
+    "CANCEL": "Cancel",
+    "LOAD_FILTERS": "Load Filters",
+    "SAVE_FILTERS": "Save Filters",
+    "CHOOSE_FILTER_TO_LOAD": "Choose filter sets to load",
+    "NAME_SAVED_FILTER_SET": "Enter a name for your saved filter set:",
+    "NEED_NAME": "You must enter a name for the saved filters.",
+    "EMPTY_LIST": "Cannot compile search filter.  Empty lists not allowed."
 }
index 2721d40..b700232 100644 (file)
 <!ENTITY staff.main.menu.cat.vandelay.label "MARC Batch Import/Export">
 <!ENTITY staff.main.menu.cat.z39_50_import.accesskey "Z">
 <!ENTITY staff.main.menu.cat.z39_50_import.label "Import Record from Z39.50">
+<!ENTITY staff.main.menu.cat.url_verify.label "Link Checker">
+<!ENTITY staff.main.menu.cat.url_verify.accesskey "K">
 
 <!ENTITY staff.main.menu.acq.label "Acquisitions">
 <!ENTITY staff.main.menu.acq.accesskey "A">
index 54320d4..322ca8a 100644 (file)
@@ -181,7 +181,7 @@ main.menu.prototype = {
         }
 
 
-        function open_eg_web_page(path, labelKey, event) {
+        function open_eg_web_page(path, labelKey, event, content_params) {
             
             // tab label
             labelKey = labelKey || 'menu.cmd_open_conify.tab';
@@ -190,11 +190,14 @@ main.menu.prototype = {
             // URL
             var loc = urls.XUL_BROWSER + '?url=' + window.escape(obj.url_prefix('EG_WEB_BASE/') + path);
 
+            content_params = content_params || {
+                'no_xulG': false,
+                'show_print_button': true,
+                'show_nav_buttons': true 
+            };
+
             obj.command_tab(
-                event,
-                loc, 
-                {tab_name : label, browser : false }, 
-                {no_xulG : false, show_print_button : true, show_nav_buttons : true }
+                event, loc, {tab_name: label, browser: false}, content_params
             );
         }
 
@@ -1193,6 +1196,21 @@ main.menu.prototype = {
                     obj.command_tab( event, url, {}, { 'id' : obj.data.last_patron } );
                 }
             ],
+
+            'cmd_url_verify' : [
+                ['oncommand'],
+                function(event) {
+                    open_eg_web_page(
+                        "/eg/url_verify/sessions",
+                        "menu.cmd_url_verify.tab",
+                        event, {
+                            'no_xulG': false,
+                            'show_print_button': false,
+                            'show_nav_buttons': true 
+                        }
+                    );
+                }
+            ],
             
             'cmd_retrieve_last_record' : [
                 ['oncommand'],
index 0d2126c..7fe88a2 100644 (file)
@@ -24,6 +24,7 @@
     <command id="cmd_cat_main" />
     <command id="cmd_create_marc" />
     <command id="cmd_authority_manage" />
+    <command id="cmd_url_verify" />
     <command id="cmd_circ_checkout" />
     <command id="cmd_patron_search" />
     <command id="cmd_circ_checkin" />
         <menuitem label="&staff.main.menu.cat.retrieve_last_record.label;" accesskey="&staff.main.menu.cat.retrieve_last_record.accesskey;" command="cmd_retrieve_last_record"/>
         <menuseparator />
         <menuitem label="&staff.main.menu.cat.authority_manage.label;" accesskey="&staff.main.menu.cat.authority_manage.accesskey;" command="cmd_authority_manage"/>
+        <menuseparator />
+        <menuitem label="&staff.main.menu.cat.url_verify.label;" accesskey="&staff.main.menu.cat.url_verify.accesskey;" command="cmd_url_verify"/>
     </menupopup>
 </menu>
 
index 2405b95..c2f6212 100644 (file)
@@ -250,6 +250,7 @@ menu.cmd_acq_po.tab=Purchase Orders
 menu.cmd_acq_user_requests.tab=Patron Requests
 menu.cmd_acq_claim_eligible.tab=Claim-Ready Items
 menu.cmd_serial_batch_receive.tab=Batch Receive
+menu.cmd_url_verify.tab=Link Checker
 menu.cmd_booking_resource.tab=Resources
 menu.cmd_booking_reservation.tab=Reservations
 menu.cmd_booking_reservation_pickup.tab=Reservation Pickup