Talis Aspire / Primo integration (old UI)

Preamble

Making use of the ELTA it is possible to add tabs to a Primo results page.
The original ELTA waited for each call to EXLTA_addTab() to finish and blocked the page from loading smoothly. We’ve extended the original functionality to allow the evaluator to run asynchronously. This does mean that tabs will appear after a short while, but this could be re-written to include the tab in a disabled state which becomes enabled depending on the result of the evaluator.
We then store all our methods inside the ABWASPIRE object which is pulled in by the Primo footer.

So, how does it all work?

First we need to know a few things about our Aspire environment and our future tab.

    label: "Aspire Reading Lists",
    tabclass: 'AspireTab',
    aspire: {
        baseurl: 'http://aspire.aber.ac.uk/',
        urlsuffix: '/lists.json',
        outputstringprefix: '<li><span class="EXLDetailsLinksBullet"></span><span class="EXLDetailsLinksTitle"><a target="_blank" href="',
        outputstringsuffix: '</a></span></li>'
    }
  • label: The name of the tab
  • tabclass: Class for the tab, becomes the value of tabType in EXLTA_addTab()
  • baseurl: Your aspire tenency URL. If you are using Primo over HTTPS you need to use your HTTPS aspire URL
  • urlsuffix: Leave as is, should probably be a const inside the ABWASPIRE object tbh.
  • outputstring*: The pre & post HTML. As above should be left alone, I should probably replace these with bulletclass and linkclass tbh

When we init our ABWASPIRE object we’re adding a load event with the ELTA which is an anonymous function which calls EXLTA_addTab().
This is what I’ve been able to deduce about ELTA_addTab():

function EXLTA_addTab(tabName, tabType, url, tabHandler, firstTab, evaluator)

  • tabName: As described
  • tabType: additional class to be added along side EXLResultTab
  • url: URL for the tab (if it has no content?)
  • tabHandler: js to run when the tab is clicked. Use the return value of a call to EXLTA_createWidgetTabHandler(function(element /*The tab content area*/) {}, boolean)
  • tabFirst: trilean: true – Prepend the existing first tab, false / undefined – Add as the last tab, any other value – replace the first tab.
  • evaluator: callback with the tab as the parameter, returns true if the tab is to be created, false if not.

The original EXLTA_addTab was blocking, so we have added an async parameter to the existing EXLTA_addTab to modify the evaluator call from:
evaluator(tab) to evaluator(tab,callback), where callback operates with the existing evaluator logic for adding the tab.

(This was before promises were discovered so this could probably be done much nicer now!).

Our modified evaluator works by:
Getting the recordId from the tab // EXLTA_recordId(tab);
Getting the ISBNs from the recordId // EXLTA_isbn_all(recordId); /* This is an additional local modification to the ELTA */
Getting the local collection number for the recordId // EXLTA_lcn(recordId); /* This is an additional local modification to the ELTA */

If we have a LCN (why wouldn’t we?), we fire off an asynchronous jsonp request to the Aspire reverse LCN lookup at {{urlbase}}/lcn/{{our lcn}}/{{urlsuffix}}
Our success jsonp callback then checks for any other lists that the record’s ISBNs may be included on (this is for where Aspire’s LCN matching hasn’t refreshed but a list with a matching ISBN exists). The process is the same as checking the LCN with the lookup taking place at {{urlbase}}/isbn/{{our isbn}}/{{urlsuffix}}
Our failure jsonp callback returns ‘false’ to indicate we don’t need the tab creating at this time.

(It should be noted that we’re using jquery.jsonp.js, as that catches error codes and executes a callback, rather than just terminating like the vanilla jQuery jsonp).

At this point, we should now have a new tab appearing if an item is on a reading list (based on LCN and ISBN matching).

Now, when we click that tab our createWidgetTabHandler callback is fired.
We created this with EXLTA_createWidgetTabHandler(function(tab){}, true /*reentrant*/);

In our anonymous function we fire off more asynchronous jsonp requests to fetch the details of the lists this item has been found on and return a new, empty unsorted list (<ul>) with a unique id of aspireListFor{{recordId}}.
The callback then populates the names of the lists a record is associated with, or in the event of an error (e.g. if a list is private) just adds a link to the list without any extra detail.

And that’s about all there is to it.

Examples

The local modifications to EXLTabAPI.03a.js

--- local_EXLTabAPI.03a.js	2018-03-28 11:48:06.348645100 +0100
+++ local_EXLTabAPI.03a.aew.js	2018-03-28 11:48:09.690645100 +0100
@@ -9,7 +9,6 @@
  ** or email: jacob.hanan@exlibrisgroup.com
  **
  ****************************************************/
-
 function EXLTA_addHeadlessTab(tabType, content, evaluator) {
     $('.EXLResultTabs').each(function () {
         if (!evaluator || (evaluator && evaluator(this))) {
@@ -36,36 +35,67 @@
     EXLTA_addTab(tabName, tabType, url, tabHandler, firstTab);
     $('.' + tabType).click();
 }
-function EXLTA_addTab(tabName, tabType, url, tabHandler, firstTab, evaluator) {
-    EXLTA_addTabBySelector('.EXLResultTabs', tabName, tabType, url, tabHandler, firstTab, evaluator);
+
+function EXLTA_addTab(tabName, tabType, url, tabHandler, firstTab, evaluator, async) {
+    async = async || false;
+    EXLTA_addTabBySelector('.EXLResultTabs', tabName, tabType, url, tabHandler, firstTab, evaluator, async);
 }
-function EXLTA_addTabBySelector(selector, tabName, tabType, url, tabHandler, firstTab, evaluator) {
+
+function EXLTA_addTabBySelector(selector, tabName, tabType, url, tabHandler, firstTab, evaluator, async) {
     $(selector).each(function () {
         var customTab = $('<li class="EXLResultTab ' + tabType + '"><a href="' + url + '">' + tabName + '</a>');
         var customTabContainer = $('
'); - if (!evaluator || (evaluator && evaluator(this))) { - if (firstTab == true) { - $(this).find('li').removeClass('EXLResultFirstTab'); - $(customTab).addClass('EXLResultFirstTab'); - $(this).prepend(customTab); - } else if (firstTab == undefined || firstTab == false) { - $(this).find('li').removeClass('EXLResultLastTab'); - $(customTab).addClass('EXLResultLastTab'); - $(this).append(customTab); - } else { - $(this).find(firstTab).replaceWith(customTab); - - } - // Modification to make customTabContainer to be correctly - // appended on Full View - if ($(this).parents('.EXLResult').find('.EXLSummary').length == 0) { - $(this).parents('.EXLResult').append(customTabContainer); - } else { - $(this).parents('.EXLResult').find('.EXLSummary').append(customTabContainer); + if (!async) { + if (!evaluator || (evaluator && evaluator(this))) { + if (firstTab == true) { + $(this).find('li').removeClass('EXLResultFirstTab'); + $(customTab).addClass('EXLResultFirstTab'); + $(this).prepend(customTab); + } else if (firstTab == undefined || firstTab == false) { + $(this).find('li').removeClass('EXLResultLastTab'); + $(customTab).addClass('EXLResultLastTab'); + $(this).append(customTab); + } else { + $(this).find(firstTab).replaceWith(customTab); + } + // Modification to make customTabContainer to be correctly + // appended on Full View + if ($(this).parents('.EXLResult').find('.EXLSummary').length == 0) { + $(this).parents('.EXLResult').append(customTabContainer); + } else { + $(this).parents('.EXLResult').find('.EXLSummary').append(customTabContainer); + } + // End modification + $('#' + $(this).attr('id') + ' .' + tabType + ' a').click(function (e) { + tabHandler(e, this, tabType, url, $(this).parents('.EXLResultTab').hasClass('EXLResultSelectedTab')); + }); } - // End modification - $('#' + $(this).attr('id') + ' .' + tabType + ' a').click(function (e) { - tabHandler(e, this, tabType, url, $(this).parents('.EXLResultTab').hasClass('EXLResultSelectedTab')); + } else { + evaluator(this, function (res, myself) { + if (res) { + if (firstTab == true) { + $(myself).find('li').removeClass('EXLResultFirstTab'); + $(customTab).addClass('EXLResultFirstTab'); + $(myself).prepend(customTab); + } else if (firstTab == undefined || firstTab == false) { + $(myself).find('li').removeClass('EXLResultLastTab'); + $(customTab).addClass('EXLResultLastTab'); + $(myself).append(customTab); + } else { + $(myself).find(firstTab).replaceWith(customTab); + } + // Modification to make customTabContainer to be correctly + // appended on Full View + if ($(myself).parents('.EXLResult').find('.EXLSummary').length == 0) { + $(myself).parents('.EXLResult').append(customTabContainer); + } else { + $(myself).parents('.EXLResult').find('.EXLSummary').append(customTabContainer); + } + // End modification + $('#' + $(myself).attr('id') + ' .' + tabType + ' a').click(function (e) { + tabHandler(e, this, tabType, url, $(this).parents('.EXLResultTab').hasClass('EXLResultSelectedTab')); + }); + } }); } $(this).parents('.EXLSummary').find('.' + tabType + '-Container').hide(); @@ -86,6 +116,7 @@ var body = '
' + htmlcontent + '
'; return header + body; } + function EXLTA_closeTab(element) { if (!EXLTA_isFullDisplay()) { $(element).parents('.EXLResultTab').removeClass('EXLResultSelectedTab'); @@ -93,6 +124,7 @@ $(element).parents('.EXLResult').find('.EXLResultTabContainer').hide(); } } + function EXLTA_openTab(element, tabType, content, reentrant) { $(element).parents('.EXLTabsRibbon').removeClass('EXLTabsRibbonClosed'); $(element).parents('.EXLResultTab').siblings().removeClass('EXLResultSelectedTab').end().addClass('EXLResultSelectedTab'); @@ -133,9 +165,11 @@ function EXLTA_isFullDisplay() { return $('.EXLFullView').size() > 0; } + function EXLTA_searchTerms() { return $('#search_field').val(); } + function EXLTA_recordId(element) { return $(element).parents('.EXLResult').find('.EXLResultRecordId').attr('id'); } @@ -179,10 +213,26 @@ } return undefined; } + function EXLTA_isbn(recordId) { var pnx = EXLTA_getPNX(recordId); - return $(pnx).find('isbn').eq(0).text(); + return $(pnx).find('isbn').eq(0).text().toUpperCase(); +} + +function EXLTA_isbn_all(recordId) { + var pnx = EXLTA_getPNX(recordId); + var isbns = []; + $(pnx).find('isbn').each(function () { + isbns.push($(this).text().toUpperCase()); + }); + return isbns; +} + +function EXLTA_lcn(recordId) { + var pnx = EXLTA_getPNX(recordId); + return $(pnx).find('lsr29').eq(0).text(); } + function EXLTA_issn(recordId) { //contributed by Karsten Kryger Hansen var pnx = EXLTA_getPNX(recordId); return $(pnx).find('issn').eq(0).text();

Shibboleth 3 IdP – BeanManagedConnection configuration

As the original documentation isn’t explicit with where all the settings for a BeanManagedConnection used by a RelationalDatabaseConnector Attribute Resolver, here’s my crib sheet.

  1. Add a connection pooling library to %{idp.home}/edit-webapp/WEB-INF/lib (or use the provided C3P0 library, dealer’s choice). For Apache Commons DBCP, DCBP 1.4 and Commons Pool 1.5.7 pair nicely
  2. Define your bean, this appears to work best in %{idp.home}/conf/global.xml
    <bean id="SQLiteDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
    p:driverClassName="%{datasource.driverClass}"
    p:url="%{datasource.jdbcUrl}"
    p:testOnBorrow="true"
    p:validationQuery="select 1" />

    Other properties are available, depending on your driver (which you’ll want to add to %{idp.home}/edit-webapp/WEB-INF/lib too)

Once the bean is in place, you can configure the DataConnector to use it as described in the usual documentation.

Adding Modules to Helix Categories (post rollover)

insert into [BBLEARN].[dbo].[HELIX_COURSE_CATEGORIES]
select [BbAStRAIntegration].[dbo].[pau_v_GetAddModCSV].EXTERNAL_COURSE_KEY AS course_code,COURSE_NAME as course_title, hlx_c_xr.CategoryId as category_id, hlx_c_xr.CategoryName as category_name from
[BbAStRAIntegration].[dbo].[pau_v_GetModDeptCSV]
JOIN [BbAStRAIntegration].[dbo].[pau_v_GetAddModCSV] on [BbAStRAIntegration].[dbo].[pau_v_GetModDeptCSV].EXTERNAL_COURSE_KEY = [BbAStRAIntegration].[dbo].[pau_v_GetAddModCSV].EXTERNAL_COURSE_KEY
JOIN OPENDATASOURCE(‘SQLNCLI’ , ‘Data Source=SQL3\SYSTEMS;Integrated Security=SSPI’).vls.dbo.tbl_Group hlx_g ON hlx_g.GroupName = (CASE
WHEN EXTERNAL_CATEGORY_KEY = ‘A’ THEN ‘_filmtv’
WHEN EXTERNAL_CATEGORY_KEY = ‘B’ THEN ‘_education’
WHEN EXTERNAL_CATEGORY_KEY = ‘C’ THEN ‘_english’
WHEN EXTERNAL_CATEGORY_KEY = ‘D’ THEN ‘_eurolang’
WHEN EXTERNAL_CATEGORY_KEY = ‘E’ THEN ‘_history’
WHEN EXTERNAL_CATEGORY_KEY = ‘F’ THEN ‘_arts’
WHEN EXTERNAL_CATEGORY_KEY = ‘G’ THEN ‘_welsh’
WHEN EXTERNAL_CATEGORY_KEY = ‘I’ THEN ‘_ibers’
WHEN EXTERNAL_CATEGORY_KEY = ‘K’ THEN ‘_interpol’
WHEN EXTERNAL_CATEGORY_KEY = ‘L’ THEN ‘_lawdept’
WHEN EXTERNAL_CATEGORY_KEY = ‘M’ THEN ‘_maths’
WHEN EXTERNAL_CATEGORY_KEY = ‘N’ THEN ‘_compsci’
WHEN EXTERNAL_CATEGORY_KEY = ‘P’ THEN ‘_instes’
WHEN EXTERNAL_CATEGORY_KEY = ‘Q’ THEN ‘_sportsci’
WHEN EXTERNAL_CATEGORY_KEY = ‘T’ THEN ‘_physics’
WHEN EXTERNAL_CATEGORY_KEY = ‘U’ THEN ‘_dis’
WHEN EXTERNAL_CATEGORY_KEY = ‘V’ THEN ‘_llcentre’
WHEN EXTERNAL_CATEGORY_KEY = ‘W’ THEN ‘_psychology’
WHEN EXTERNAL_CATEGORY_KEY = ‘Y’ THEN ‘_smba’
WHEN EXTERNAL_CATEGORY_KEY = ‘Z’ THEN ‘_cont.ed’
WHEN EXTERNAL_CATEGORY_KEY = ‘S’ THEN ‘_ibers’
ELSE EXTERNAL_CATEGORY_KEY END
)
JOIN OPENDATASOURCE(‘SQLNCLI’ , ‘Data Source=SQL3\SYSTEMS;Integrated Security=SSPI’).vls.dbo.tbl_Group_Category_Xref hlx_xr ON hlx_xr.GroupID = hlx_g.GroupID AND hlx_xr.AccessType = 1 AND hlx_xr.CategoryID >= 9 and hlx_xr.CategoryID <= 28
JOIN OPENDATASOURCE('SQLNCLI' , 'Data Source=SQL3\SYSTEMS;Integrated Security=SSPI').vls.dbo.tbl_Category hlx_c_xr ON hlx_c_xr.CategoryID = hlx_xr.CategoryID

Getting child course users who may have been disabled when the parent course id was passed in to SIS

SELECT course_id, user_id
FROM [BBLEARN].[dbo].[course_users] child
JOIN [BBLEARN].[dbo].[course_users] parent on parent.child_crsmain_pk1 = child.crsmain_pk1 and parent.users_pk1 = child.users_pk1
JOIN [BBLEARN].[dbo].[course_main] on child.crsmain_pk1 = [BBLEARN].[dbo].[course_main].pk1
JOIN [BBLEARN].[dbo].[users] on [BBLEARN].[dbo].[users].pk1 = child.users_pk1
where child.available_ind = ‘N’ and parent.child_crsmain_pk1 is not null

Getting active uploaders from Panopto

SELECT distinct replace(replace(replace([UserName],'blackboard\',''),'-ac',''),'pau.local\','') as UserName
FROM
(
SELECT [UserName]
,DATEADD(ms,
((cast((startTime*10000000) as BIGINT)) / CAST(10000 AS bigint)) % 86400000,
DATEADD(day, (cast((startTime*10000000) as BIGINT)) / CAST(864000000000 AS bigint) - 109207, 0)) AS realStartTime
FROM [PanoptoDB_3].[dbo].[activityLog]
JOIN [PanoptoDB_3].[dbo].[aspnet_Users] ON [PanoptoDB_3].[dbo].[aspnet_Users].[UserId] = [PanoptoDB_3].[dbo].[activityLog].[userID]
WHERE [PanoptoDB_3].[dbo].[activityLog].[action] > 0
AND [PanoptoDB_3].[dbo].[activityLog].[SessionId] is not null
) AS t
WHERE realStartTime > '2013-09-01'
order by UserName

Creating user – course enrollments based on membership on existing courses

DECLARE @AY varchar(7);
SET @AY = '2013-14';
SELECT DISTINCT USERS.Batch_uid, 'DEPT-I-0001' FROM Users
JOIN COURSE_USERS ON COURSE_USERS.users_pk1 = USERS.PK1
JOIN COURSE_MAIN ON COURSE_USERS.crsmain_pk1 = COURSE_MAIN.PK1
WHERE COURSE_MAIN.lower_course_id in ('rd23520_'+@AY,'rg23520_'+@AY,'br23420_'+@AY,'bg23420_'+@AY)
AND COURSE_USERS.ROLE = 'S'

Checking Bb SIS logs against current course IDs

SELECT * FROM [BBLEARN].[dbo].[data_intgr_log]
LEFT JOIN [BBLEARN].[dbo].[course_main] ON lower(rtrim(ltrim(RIGHT(
SUBSTRING([log_message], CHARINDEX('[',[log_message]), CHARINDEX(']',[log_message], CHARINDEX('[',[log_message])+1)-CHARINDEX('[',[log_message]))
, CHARINDEX(' ',
reverse(
SUBSTRING([log_message], CHARINDEX('[',[log_message]), CHARINDEX(']',[log_message], CHARINDEX('[',[log_message])+1)-CHARINDEX('[',[log_message]))
))
)))) = [course_main].lower_course_id

where data_intgr_pk1 = 2 and log_message like '%di.error.associate%' and [course_main].course_name is not null

Generating user – course enrollment file by DSK and Course Catalog


declare @usr as varchar;
declare @dsk as varchar;
declare @dpt as varchar;

set @usr= 'CHANGEME';
set @dsk= 'CHANGEME';
set @dpt= 'CHANGEME';

SELECT [COURSE_ID] AS EXTERNAL_COURSE_KEY, '' + @usr + '' AS EXTERNAL_PERSON_KEY, 'D' AS ROLE, 'enabled' AS ROW_STATUS, 'Y' AS AVAILABLE_IND
FROM [bb_bb60].[dbo].[COURSE_MAIN]
JOIN [bb_bb60].[dbo].[GATEWAY_COURSE_CATEGORIES] ON [GATEWAY_COURSE_CATEGORIES].CRSMAIN_PK1 = COURSE_MAIN.PK1
JOIN [bb_bb60].[dbo].[GATEWAY_CATEGORIES] ON [GATEWAY_COURSE_CATEGORIES].GATEWAYCAT_PK1 = [GATEWAY_CATEGORIES].PK1
JOIN [bb_bb60].[dbo].[DATA_SOURCE] ON COURSE_MAIN.DATA_SRC_PK1 = [DATA_SOURCE].PK1
WHERE DATA_SOURCE.BATCH_UID = @dsk AND GATEWAY_CATEGORIES.BATCH_UID = @dpt