0% found this document useful (0 votes)
3 views39 pages

Pat 162007723

Patch PAT162007723 addresses the issue SFWK-12709 in Content Server version 16.2.0, specifically a hot-fix for the recommender functionality that fails to populate the UI due to NULL values in the database. The patch includes modifications to the GatherProcessing function, which processes data from the RECD_OperationTracking table into summary tables while ensuring data integrity and handling NULL checks. The document outlines various functions related to data processing, cleanup, and error handling within the recommender system.

Uploaded by

testcorreo03
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views39 pages

Pat 162007723

Patch PAT162007723 addresses the issue SFWK-12709 in Content Server version 16.2.0, specifically a hot-fix for the recommender functionality that fails to populate the UI due to NULL values in the database. The patch includes modifications to the GatherProcessing function, which processes data from the RECD_OperationTracking table into summary tables while ensuring data integrity and handling NULL checks. The document outlines various functions related to data processing, cleanup, and error handling within the recommender system.

Uploaded by

testcorreo03
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 39

# Patch PAT162007723 created at Wed Aug 14 10:43:02 EDT 2024

#
# Issue: SFWK-12709
# Module:
# Version: Content Server 16.2.0 64-bit for Windows
# Update: 24.2.0 (2024-03),24.1.0 (2023-12)
#
# Description:
# 24.1 - 24.2 hot-fix: Recommender fails to populate Content Server UI due to NULL
value in database table
#
# #
m {'kernel','lliapi','recommender'}
o {'pat::GatherProcessing',&&recommender::GatherProcessing}
N GatherProcessing
f CursorThroughData
s
/*
* Recommender Agent processing script.
* This script processes the data stored in the RECD_OperationTracking table
into the Recd_OperationSummary and Recd_Hot tables.
*
* @param {Object} prgCtx A program context object.
*
* @return {Void}
*
*/
function Void CursorThroughData(
Object prgCtx )

Record rec
Integer recordCounter
Dynamic interestData
Dynamic endTick
String stmt
List recordData

Boolean done = FALSE


CAPIconnect conn = prgCtx.fDBConnect.fConnection
Boolean ok = TRUE

Echo( Str.Format( "%1.CursorThroughData: Start of processing of the Gather


thread", OS.Name( this ) ) )

ok = ._Setup( prgCtx, TRUE )

if ( ok )

endTick = Date.Tick() + ( .fTimeOfExecutionInSec * 1000 )

repeat

ok = ._Setup( prgCtx )

breakIf ( ! ok )

// Process all of the interest rows.


stmt = "OperationIden, OperationDate, UserID, DataID, ObjType,
Action, PathInfo FROM Recd_OperationTracking ORDER BY OperationDate ASC"
stmt = prgCtx.fDBConnect.SelectTop( .fMaxRowsProcessed, stmt )

interestData = CAPI.Exec( conn, stmt )

if ( IsNotError( interestData ) )

if ( Length( interestData ) > 0 )

// Process for any -1 DataIDs first


._ProcessUnknownDataIDs( interestData )

// Retrieve only the DataID's that are still valid


(not deleted) from DTree, along with their SubType
// Only valid ID's are inserted/updated into the
Summary table

ok = ._ValidateDataIDs( prgCtx,
RecArray.ColumnToList( interestData, "DataID" ) )

// Problems getting the validation info of the


DataIDs.
breakIf ( ! ok )

for rec in interestData

ok = ._ProcessInterest( rec )

breakIf ( ! ok )

// The row processes OK, add its ID to the


commit list.
// Build a RecArray needed for the delete in
_UpdateOperationSummaryTables().
recordCounter += 1
recordData += { rec.OperationIden }

if ( recordCounter == .fBLOCKSIZE )

RecArray.AddRecord( .fInterestRowsToCommitRecArray, recordData )


recordCounter = 0
recordData = {}
end

// Always increment the rows processed count,


// otherwise tracking rows for deleted items
are not reflected in the count or timing.
.fNumRowsProcessed += 1

breakIf ( Date.Tick() > endTick )


end

breakIf ( ! ok )

// Add any partial list of processed IDs to the


RecArray for delete.
if ( recordCounter > 0 )

RecArray.AddRecord( .fInterestRowsToCommitRecArray, recordData )


recordCounter = 0
recordData = {}
end
else

// There is no data so only do this full loop/repeat


the one time.
done = TRUE
end
else

ok = FALSE
EchoError( Str.Format( '%1.CursorThroughData: Failed to get
all of the interest rows: %2',
OS.Name( this ), Str.String( interestData
) ) )

break
end

ok = ._UpdateOperationSummaryTables()

breakIf ( ! ok )

ok = ._CleanUpSystemStatsTables()

breakIf ( ! ok )

until ( done || ( Date.Tick() >= endTick ) || ! ok )

if ( ok )

._FilterSummaryTable()

// Filter summary table of all admin stuff, before populating the


hot table.
._PopulateHotTable()
end
end

._Cleanup()

if ( ok )

Echo( Str.Format( "%1.CursorThroughData: End of processing of the


Gather thread", OS.Name( this ) ) )
else

EchoError( Str.Format( "%1.CursorThroughData: Error occurred during


runtime. Processing halted for this run.", OS.Name( this ) ) )
end

end

sEND
f _BuildAccessKey
s

function String _BuildAccessKey( Integer userId, Integer dataId, Integer objType,


String action )

// Function no longer used

return Undefined

end

sEND
g _CheckForNulls
s
/*
* Checks that all the database values are not null
*
* @param {Record} rec The record from the database
*
* @return {Boolean} FALSE if one or more values is NULL, TRUE
otherwise
*
*/
function Boolean _CheckForNulls(
Record rec )

String column

Boolean ok = TRUE
List fieldNames = RecArray.FieldNames( rec )

for column in fieldNames

if ( IsUndefined( rec.( column ) ) )

ok = FALSE

break
end
end

return ok
end
sEND
f _CleanUpSystemStatsTables
s
/*
* Cleanup the Recd_SystemStatsSummary table
*
* @return {Boolean} TRUE if OK, FALSE otherwise
*
*/
function Boolean _CleanUpSystemStatsTables()

Dynamic dbResult
String stmt
List bindValues

Boolean ok = TRUE
CAPIConnect conn = .fPrgCtx.fDbConnect.fConnection
Object dapiCtx = .fPrgCtx.DSession()

if ( dapiCtx.StartTrans() )

// Update the system stats information


stmt = "UPDATE Recd_SystemStatsSummary SET MeanDate = :A1, MeanSqDate =
:A2, MeanTime = :A3, MeanSqTime = :A4, RefCount = :A5"
bindValues =
{ .fSystemStats.meanDate, .fSystemStats.meanSqDate, .fSystemStats.meanTime, .fSyste
mStats.meanSqTime, .fSystemStats.refCount }

dbResult = CAPI.Exec( conn, stmt, bindValues )

// We will fail silently because there is no recourse for not


succeeding
if ( IsError( dbResult ) )

ok = FALSE
EchoError( Str.Format( '%1._CleanUpSystemStatsTables: Could not
update Recd_SystemStatsSummary table: %2', OS.Name( this ),
Str.String( dbResult ) ) )
else

// Need to update the refcount in the RDB and this can be done in
a single update statement
stmt = "UPDATE Recd_SystemStatsSummary SET RefCount = COALESCE( (
SELECT SUM( RefCount ) AS refCount FROM Recd_OperationSummary ), 0 )"

// Update the system stats information


dbResult = CAPI.Exec( conn, stmt )

if ( IsError( dbResult ) )

ok = FALSE
EchoError( Str.Format( "%1._CleanUpSystemStatsTables: Could
not update Recd_SystemStatsSummary table's RefCount: %2", OS.Name( this ),
Str.String( dbResult ) ) )
end
end

if ( ! dapiCtx.EndTrans( ok ) )

EchoError( Str.Format( "%1._CleanUpSystemStatsTables: Error


ending DB Transaction", OS.Name( this ) ) )
end
else

// Can not create a new transaction


ok = FALSE
EchoError( Str.Format( "%1._CleanUpSystemStatsTables: Could not start a
new transaction", OS.Name( this ) ) )
end

return ok
end

sEND
f _Cleanup
s
/*
* Reset all the features in this object to their defaults
*
* @return {Void}
*
*/
function Void _Cleanup()

.fPrgCtx = Undefined
.fSystemStats = Undefined
.fRollUpDataIDs = {}
.fUpdateAssoc = Undefined
.fInsertAssoc = Undefined
.fValidSubTypeMapping = Undefined
.fInterestRowsToCommitRecArray = Undefined
.fNumRowsProcessed = 0
.fStartTick = Undefined
end

sEND
f _ClearCounts
s

function void _ClearCounts()

// Function no longer used

end

sEND
f _FillInMissingSubTypes
s

function Assoc _FillInMissingSubTypes()

// Function no longer used

return Undefined
end

sEND
f _FilterSummaryTable
s
/*
* Filters out any unnecessary data from the Summary table.
*
* @return {Boolean} TRUE if OK, FALSE otherwise
*
*/
function Boolean _FilterSummaryTable()

Date earliestAllowableDate
Dynamic dbResult
String stmt
String tableName
String template

Boolean ok = TRUE
Object dbConnect = .fPrgCtx.fDbConnect
Object recommenderUtils =
$RECOMMENDER.RecommenderUtils
String excludedNodesSQL =
recommenderUtils.GetExcludedNodesSQL( .fPrgCtx, "DTreeOwners.SubType" )
Assoc config =
recommenderUtils.GetRecommenderAdminConfig( .fPrgCtx )
CAPIConnect conn = .fPrgCtx.fDbConnect.fConnection
List tablesList = { "Recd_OpinionTracking",
"Recd_OpinionSummary", "Recd_OperationSummary" }
Integer dateDays = Date.DateInteger( Date.Now() )
Object dapiCtx = .fPrgCtx.DSession()
String LOGPREFIX =
Str.Format( '%1._FilterSummaryTable:', OS.Name( this ) )

if ( dapiCtx.StartTrans() )

if ( IsFeature( config.general, "watchAdmin" ) && !


config.general.watchAdmin )

// If wanted clean up the admin from the Summary table.


dbResult = CAPI.Exec( conn, "DELETE FROM Recd_OperationSummary
WHERE UserID = 1000" )

if ( IsError( dbResult ) )

ok = FALSE
EchoError( Str.Format( '%1 Delete from
Recd_OperationSummary for UserID failed. Error: %2', LOGPREFIX,
Str.String( dbResult ) ) )
end
end

if ( ok )

if ( .fDateInDaysCleanup != dateDays )

// Do this cleanup once a day only


.fDateInDaysCleanup = dateDays

if ( IsFeature( config.general, "expireDays" ) &&


( config.general.expireDays != recommenderUtils.fNoExpire ) )

// Clean up the Operations Summary table, based on


the expire
// Subtact off expireDays number of days from the
current date.
earliestAllowableDate = Date.Now() -
( config.general.expireDays * 60 * 60 * 24 )

// Delete everything before then.


dbResult = CAPI.Exec( conn, "DELETE FROM
Recd_OperationSummary WHERE RecentAccess < :A1", earliestAllowableDate )
if ( IsError( dbResult ) )

ok = FALSE
EchoError( Str.Format( '%1 Delete from
Recd_OperationSummary for RecentAccess failed. Error: %2', LOGPREFIX,
Str.String( dbResult ) ) )
end
end
end
end

if ( ok && IsUndefined( .fSystemDate ) )

dbResult = $LLIAPI.SystemDataUtil.Get( dbConnect.fLogin,


"Recommender", "SystemDate" )

if ( IsNotError( dbResult ) )

// there is a successful retrieval


.fSystemDate = Str.StringToValue( dbResult )
else

EchoWarn( Str.Format( "%1 Could not get SystemDate from


LLSystemData. This may result in a slower than normal query.", LOGPREFIX ) )
end
end

if ( ok )

//Filter out all the temporary objects from the operation summary
table
if ( IsDefined( excludedNodesSQL ) )

template = "DELETE FROM %1 WHERE%2 EXISTS ( SELECT 1 FROM


DTree DTreeItems, DTree DTreeOwners " +
"WHERE %1.DataID = DTreeItems.DataID AND
ABS( DTreeItems.OwnerID ) = DTreeOwners.DataID AND " + excludedNodesSQL + ")"

for tableName in tablesList

if ( IsDefined( .fSystemDate ) && ( tableName in


{ "Recd_OpinionTracking", "Recd_OperationSummary" } ) )

// If there is a system date defined, then this


is to be used to
// constrain the query to the object that have
changed since the last time we ran.
// this will significantly reduce the number of
rows that
// will be examined in the delete.

if ( tableName == "Recd_OpinionTracking" )

stmt = Str.Format( template, tableName, "


OpinionDate >= :A1 AND" )
else

stmt = Str.Format( template, tableName, "


RecentAccess >= :A1 AND" )
end

dbResult = CAPI.Exec( conn,


stmt, .fSystemDate )
else

stmt = Str.Format( template, tableName, "" )

dbResult = CAPI.Exec( conn, stmt )


end

if ( IsError( dbResult ) )

ok = FALSE
EchoError( Str.Format( '%1 Delete failed on
table %2. Error: %3', LOGPREFIX, tableName, Str.String( dbResult ) ) )

break
end
end
end
end

if ( ok )

//delete all workflow attachments


template = "DELETE FROM %1 WHERE%2 EXISTS ( SELECT 1 FROM DTree
DTreeItems, DTree DTreeParents " +
"WHERE %1.DataID = DTreeItems.DataID AND
DTreeItems.ParentID = DTreeParents.DataID AND " +
"DTreeParents.SubType = " +
Str.String( $TypeWorkflowAttachments ) + ")"

for tableName in tablesList

if ( IsDefined( .fSystemDate ) && ( tableName in


{ "Recd_OpinionTracking", "Recd_OperationSummary" } ) )

// If there is a system date defined, then this is to


be used to
// constrain the query to the object that have
changed since the last time we ran.
// this will significantly reduce the number of rows
that
// will be examined in the delete.

if ( tableName == "Recd_OpinionTracking" )

stmt = Str.Format( template, tableName, "


OpinionDate >= :A1 AND" )
else

stmt = Str.Format( template, tableName, "


RecentAccess >= :A1 AND" )
end

dbResult = CAPI.Exec( conn, stmt, .fSystemDate )


else
stmt = Str.Format( template, tableName, "" )
dbResult = CAPI.Exec( conn, stmt )
end

if ( IsError( dbResult ) )

ok = FALSE
EchoError( Str.Format( '%1 Delete failed on table %2.
Error: %3', LOGPREFIX, tableName, Str.String( dbResult ) ) )

break
end
end
end

if ( ok )

// Go and update the date of this run for the next batch
// using the RDB date
.fSystemDate = CAPI.Now( conn )

if ( IsNotError( .fSystemDate ) )

dbResult = $LLIAPI.SystemDataUtil.Put( dbConnect.fLogin,


"Recommender", "SystemDate", Str.ValueToString( .fSystemDate ) )

if ( IsError( dbResult ) )

ok = FALSE
EchoError( Str.Format( "%1 Error saving SystemDate to
database. Error: %2", LOGPREFIX, Str.String( dbResult ) ) )
end
else

ok = FALSE
EchoError( Str.Format( "%1 Error retrieving the SystemDate
from the database. Error: %2", LOGPREFIX, Str.String( .fSystemDate ) ) )
.fSystemDate = Undefined
end
end

if ( ok )

//Filter out any EL object Types


// Will be removed in future: SFWK-10404
if ( IsDefined( $ELService ) && IsDefined( $ELSearch ) )

List ELSubTypesList
String ELSubTypesString
Integer typeCount
Integer typeLength

ELSubTypesList = { $TypeELLibraryObj, $TypeELExtractor,


$TypeELTypeVolume, $TypeELType, $TypeELVolume, $TypeELFolder }
ELSubTypesString = Str.String( ELSubTypesList[1] )
typeLength = Length( ELSubTypesList )
typeCount = 2

while ( typeCount <= typeLength )


ELSubTypesString = Str.Format( "%1, %2",
ELSubTypesString, ELSubTypesList[typeCount] )
typeCount = typeCount + 1
end

template = Str.Format( "DELETE from %%1 where DataID in


( select DataID from DTree where SubType in ( %1 ))", ELSubTypesString )

for tableName in tablesList

stmt = Str.Format( template, tableName )

dbResult = CAPI.Exec( conn, stmt )

if ( IsError( dbResult ) )

ok = FALSE
EchoError( Str.Format( "%1 Error executing
delete for Enterprise Library. Error: %2", LOGPREFIX, Str.String( dbResult ) ) )

break
end
end
end
end

if ( ! dapiCtx.EndTrans( ok ) )

EchoError( Str.Format( "%1._CleanUpSystemStatsTables: Could not


end the transaction", OS.Name( this ) ) )
end
else

// Can not create a new transaction


ok = FALSE
EchoError( Str.Format( "%1._CleanUpSystemStatsTables: Could not start a
new transaction", OS.Name( this ) ) )
end

return ok
end

sEND
g _FindDataFromPathInfo
s
/*
* Retrieve the DataID from a given path
*
* @param {String} pathInfo The path information
*
* @return {Integer} dataID The DataID retrieved (Undefined if
not found)
*
*/
function Integer _FindDataFromPathInfo( String pathInfo )

Dynamic parentID
Dynamic dbResult
Integer i
String checkFetch
String nodeName

Boolean keepGoing = TRUE


List elements = Str.Elements( pathInfo, '/' )
CAPIConnect conn = .fPrgCtx.fDbConnect.fConnection
Integer checkID = Str.StringToInteger( elements[ Length( elements )
] )
Integer dataID = Undefined

// First check to see if there is an integer at the end already...

if ( IsDefined( checkID ) )

// Only take the number if its a fetch operation.


for checkFetch in elements

if ( Str.Lower( checkFetch ) == "fetch" )

dataID = checkID
keepGoing = FALSE

break
end
end
end

if ( keepGoing )

// Search from the end to find the first integer value


for i = Length( elements ) downto 1

if ( IsDefined( Str.StringToInteger( elements[ i ] ) ) )

break
end
end

nodeName = elements[ i + 1 ]
parentID = elements[ i ]

// Now we have a starting point


while ( i < Length( elements ) )

dbResult = CAPI.Exec( conn, "SELECT DataID FROM DTree WHERE


ParentID = :A1 AND Name = :A2",
parentID, nodeName )

if ( IsNotError( dbResult ) && ( Length( dbResult ) > 0 ) )

dataID = dbResult[ 1 ].DataID


else

dataID = Undefined

// There is no valid response, just exit


break
end

i += 1
nodeName = elements[ i + 1 ]
parentID = dataID
end
end

return dataID
end
sEND
g _FindDataFromPathInfoForHH
s
/*
* Retrieve the node information from a given path
*
* @param {String} pathInfo The path information
*
* @return {Integer} dataID The DataID retrieved (Undefined if
not found)
*
*/
function Integer _FindDataFromPathInfoForHH( String pathInfo )

Integer i
String nodeName
String theDataID

List elements = Str.Elements( pathInfo, '/' )


Integer dataID = Undefined

// Search from the end to find the first integer value


for i = Length( elements ) downto 1

if ( IsDefined( Str.LocateI( elements[ i ], "DataID=" ) ) )

break
end
end

nodeName = elements[ i ]

if ( IsDefined( nodeName ) )

// Find the one with the dataID


elements = Str.Elements( nodename, '&' )

for i = Length( elements ) downto 1

theDataID = Str.Replace( elements[ i ], "DataID=", "" )

if ( IsDefined( theDataID ) )

dataID = Str.StringToInteger( theDataID )


end
end
end
return dataID
end
sEND
g _GetDataAndInsertIntoHotTable
s
/*
* Retrieves the data to be inserted into the Recd_Hot table.
*
* @param {Object} prgCtx A program context object.
* @param {List} subTypes List of subtypes supported
* @param {Integer} numResults Number of results to retrieve
* @param {Integer} sectionID Section ID
* @param {String} functionCall The function name under RecommenderUtils
to run
* @param {String} section The section name
*
* @return {Boolean} TRUE if OK, FALSE otherwise
*
*/
function Boolean _GetDataAndInsertIntoHotTable(
Object prgCtx,
List subTypes,
Integer numResults,
Integer sectionID,
String functionCall,
String section )

Assoc checkVal
Dynamic totalTick
Dynamic dbResult
Real rowspersec
Record rec

Boolean ok = TRUE
RecArray recs = RecArray.Create( { 'DataID', 'SectionID',
'SubType' } )
CAPIConnect conn = prgCtx.fDbConnect.fConnection
Dynamic startTick = Date.Tick()

// Don't want to call database methods if no subtypes are specified. However,


if it's Top Picks,
// subtypes will be undefined since Top Picks only includes documents

if ( ( Length( subTypes) > 0 ) || ( Str.CmpI( functionCall,


'SysPathToTopPicks' ) == 0 ) )

checkVal = $RECOMMENDER.RecommenderUtils.( functionCall ) ( prgCtx,


numResults, subtypes )

if ( checkVal.ok == TRUE )

for rec in checkVal.result

RecArray.AddRecord( recs, { rec.dataID, sectionID,


rec.SubType } )
end

if ( Length( recs ) > 0 )


// We have something to insert
if ( ! prgCtx.fDbConnect.fConnection.SupportIdentity )

dbResult = CAPI.ExecN( conn, "INSERT INTO Recd_Hot


( OrderIden, DataID, SectionID, SubType ) VALUES
( Recd_HotSeq.NextVal, :A1, :A2, :A3 )", recs )
else

dbResult = CAPI.ExecN( conn, "INSERT INTO Recd_Hot


( DataID, SectionID, SubType ) VALUES ( :A1, :A2, :A3 )", recs )
end

if ( IsError( dbResult ) )

ok = FALSE

EchoError( Str.Format( "%1._GetDataAndInsertIntoHotTable: Could not insert


data into Recd_Hot table. Error: %2", OS.Name( this ), Str.String( dbResult ) ) )
end
end
else

ok = FALSE
EchoError( Str.Format( "%1._GetDataAndInsertIntoHotTable: Error
running RecommenderUtils function [%2]. Error: %3", OS.Name( this ), functionCall,
checkVal.errMsg ) )
end

if ( ok )

totalTick = Date.Tick() - startTick

if ( Length( recs ) > 0 )

// report the stats values only if there is something to


report
if ( totalTick > 0 )

rowspersec = Math.Round( Length( recs ) /


( ( totalTick ) / 1000.0 ) )
end

Echo( Str.Format( "HotTable.%1:[%2] rows have been added in


[%3] msecs for approx [%4] rows/secs for types [%5].", section, Length( recs ),
totalTick, rowspersec, subTypes ) )
else

Echo( Str.Format( "HotTable.%1: No rows have been added for


types [%2]", section, subTypes ) )
end
end
end

return ok
end
sEND
f _GetRecArrayInsert
s
function RecArray _GetRecArrayInsert()

// Function no longer used

return Undefined
end

sEND
f _GetRecArrayUpdate
s

function RecArray _GetRecArrayUpdate()

// Function no longer used

return Undefined
end

sEND
f _GetReference
s

function Assoc _GetReference( \


String keyString )

// Function no longer used

return Undefined
end

function Assoc NewRefAssoc()

// Function no longer used

return Undefined
end

sEND
f _GetRollupUserRecArrayInsert
s

function RecArray _GetRollupUserRecArrayInsert()

// Function no longer used

return Undefined
end

sEND
f _PopulateHotTable
s
/*
* Populates the Recd_Hot table.
*
* @return {Boolean} TRUE if OK, FALSE otherwise
*
*/
function Boolean _PopulateHotTable()

Assoc adminConfig
Dynamic dbResult
Integer i
Integer subtype
List enabledPopularSections
List subtypeList
String section
String functionCall

Boolean ok = TRUE
Object recommenderUtils = $RECOMMENDER.RecommenderUtils
Object prgCtx = .fPrgCtx
Object dapiCtx = .fPrgCtx.DSession()

if ( dapiCtx.StartTrans() )

adminConfig = recommenderUtils.GetRecommenderAdminConfig( prgCtx, "Hot"


)

if ( IsDefined( adminConfig ) && ( adminConfig.ok == TRUE ) )

enabledPopularSections = adminConfig.aboutMeSections
else

ok = FALSE
EchoError( Str.Format( "%1._PopulateHotTable: Could not retrieve
Recommender settings.", OS.Name( this ) ) )
end

// CLEAR RECD_HOT TABLE

if ( ok )

// Delete the records that are currently in the Hot Table


dbResult = prgCtx.fDbConnect.EmptyTable( "Recd_Hot" )

if ( IsError( dbResult ) )

ok = FALSE
EchoError( Str.Format( "%1._PopulateHotTable: Could not
empty Recd_Hot table. Error: %2", OS.Name( this ), Str.String( dbResult ) ) )
end
end

// POPULATE WHAT'S NEW, MOST ACTIVE, TOP PICKS

if ( ok )

//integer i is the sectionID whatsnew=1, mostactive=2, toppicks=3


for section in enabledPopularSections

functionCall = "SysPathTo" + section

if ( IsFeature( adminConfig, section ) &&


IsDefined( adminConfig.( section ) ) )
//sometimes sections are turned off, so make sure the
SectionID # is correct.
switch( section )

case "whatsnew"

i = 1
end

case "mostactive"

i = 2
end

case "toppicks"

i = 3
end
end

subtypeList = adminConfig.( section ).subtypes

if ( IsUndefined( subtypeList ) ||
( Length( subtypeList ) == 0 ) )

ok = ._GetDataAndInsertIntoHotTable( prgCtx,
subtypeList,
adminConfig.
( section ).samplesize, i, functionCall,

recommenderUtils.GetSectionDisplayname( section ) )

breakIf ( ! ok )
else

for subtype in adminConfig.( section ).subtypes

ok
= ._GetDataAndInsertIntoHotTable( prgCtx, { subtype },
adminConfig.
( section ).samplesize, i, functionCall,

recommenderUtils.GetSectionDisplayname( section ) )

breakIf ( ! ok )
end
end
end
end
end

if ( ! dapiCtx.EndTrans( ok ) )

EchoError( Str.Format( "%1._PopulateHotTable: Could not end the


transaction", OS.Name( this ) ) )
end
end
return ok
end

function Assoc GetDataAndInsertIntoHotTable( \


Object prgCtx,\
List subTypes, \
Integer numResults, \
Integer sectionID,\
String functionCall,\
String section )

// Function no longer used

return Undefined
end

sEND
f _ProcessInterest
s

/*
* For the given record, process its tracking data.
*
* @param {Record} rec The record of data to process.
*
* @return {Boolean} TRUE if OK, FALSE otherwise
*
*/
function Boolean _ProcessInterest( \
Record rec )

Dynamic dbResult
Assoc refAssoc
Integer subType
List bindArgs
String keyString

Boolean ok = TRUE
CAPIConnect conn = .fPrgCtx.fDbConnect.fConnection
List keyNames = Assoc.Keys( .fValidSubTypeMapping )
Integer dateInt = Date.DateInteger( rec.OperationDate )
Integer timeInt = Date.TimeInteger( rec.OperationDate )

// Only skip row if it isn't in DTree (has been deleted since it was added to
Recd_OperationTracking)

if ( rec.DataID in keyNames )

subType = .fValidSubTypeMapping.( rec.DataID )


bindArgs = { rec.DataID, rec.Action, rec.UserID, subType }
keyString = Str.Join( bindArgs, 'X' )

// We are given the keyString. First we need to check to see if it is


in the
// Insert or Update Assoc's already. If it's not, proceed to get the
row.
if ( ! Assoc.IsKey( .fUpdateAssoc, keyString ) && !
Assoc.IsKey( .fInsertAssoc, keyString ) )

// We need to now go and see if it is in the RDB


dbResult = CAPI.Exec( conn,
"SELECT RefCount, MeanDate, MeanSqDate, MeanTime,
MeanSqTime FROM Recd_OperationSummary WHERE DataID = :A1 AND " +
"Action = :A2 AND UserID = :A3 AND SubType = :A4",
bindArgs )

if ( IsNotError( dbResult ) )

if ( Length( dbResult ) > 0 )

// Increase the existing RefCount


dbResult[ 1 ].RefCount += 1

.fUpdateAssoc.( keyString ) =
RecArray.CreateRecord( .fSummaryTableListUpdate, { rec.OperationDate,

$RECOMMENDER.RecommenderUtils.ComputeNewMean( dbResult[ 1 ].MeanDate,


dateInt, dbResult[ 1 ].RefCount ),

$RECOMMENDER.RecommenderUtils.ComputeNewMean( dbResult[ 1 ].MeanSqDate,


dateInt * dateInt, dbResult[ 1 ].RefCount ),

$RECOMMENDER.RecommenderUtils.ComputeNewMean( dbResult[ 1 ].MeanTime,


timeInt, dbResult[ 1 ].RefCount ),

$RECOMMENDER.RecommenderUtils.ComputeNewMean( dbResult[ 1 ].MeanSqTime,


timeInt * timeInt, dbResult[ 1 ].RefCount ),

dbResult[ 1 ].RefCount,

rec.UserID,

rec.DataID,

subType,

rec.Action } )
else

.fInsertAssoc.( keyString ) =
RecArray.CreateRecord( .fSummaryTableList, { rec.OperationDate,

rec.OperationDate,

$RECOMMENDER.RecommenderUtils.ComputeNewMean( 0, dateInt, 1 ),

$RECOMMENDER.RecommenderUtils.ComputeNewMean( 0, dateInt * dateInt, 1 ),


$RECOMMENDER.RecommenderUtils.ComputeNewMean( 0, timeInt, 1 ),

$RECOMMENDER.RecommenderUtils.ComputeNewMean( 0, timeInt * timeInt, 1 ),

rec.UserID,

rec.DataID,

rec.ObjType,

rec.Action,

1,

subType } )
end

// Indicate this DataID needs to have rollup row


calculation done for userid and actions
// (avoid doing it multiple times)
.fRollUpDataIDs = List.SetAdd( .fRollUpDataIDs, bindArgs[ 1
: 3 ] )
else

ok = FALSE
EchoError( Str.Format( "%1._ProcessInterest: Could not
retrieve data from Recd_OperationSummary table. Error: %2", OS.Name( this ),
Str.String( dbResult ) ) )
end
else

// Keystring already in insert or update Assoc. Recalculate the


fields.

refAssoc = Assoc.IsKey( .fUpdateAssoc,


keyString ) ? .fUpdateAssoc : .fInsertAssoc

// Use the latest recent access date


// Since the original select was by oldest to newest, we don't
need to check if this is a newer date since it should be
refAssoc.( keyString ).RecentAccess = rec.OperationDate

refAssoc.( keyString ).RefCount += 1

// Re-compute the means


refAssoc.( keyString ).MeanDate =
$RECOMMENDER.RecommenderUtils.ComputeNewMean( refAssoc.( keyString ).MeanDate,
dateInt, refAssoc.( keyString ).RefCount )
refAssoc.( keyString ).MeanSqDate =
$RECOMMENDER.RecommenderUtils.ComputeNewMean( refAssoc.( keyString ).MeanSqDate,
dateInt * dateInt, refAssoc.( keyString ).RefCount )
refAssoc.( keyString ).MeanTime =
$RECOMMENDER.RecommenderUtils.ComputeNewMean( refAssoc.( keyString ).MeanTime,
timeInt, refAssoc.( keyString ).RefCount )
refAssoc.( keyString ).MeanSqTime =
$RECOMMENDER.RecommenderUtils.ComputeNewMean( refAssoc.( keyString ).MeanSqTime,
timeInt * timeInt, refAssoc.( keyString ).RefCount )
end

// Update the System Stats


._UpdateSystemStats( rec )
end

return ok

end

function Assoc FindDataFromPathInfo( String pathInfo )

// Function no longer used

return Undefined
end

function Assoc FindDataFromPathInfoForHH( String pathInfo )

// Function no longer used

return Undefined
end

sEND
g _ProcessUnknownDataIDs
s
/**
*
* Tries to retrieve any unknown DataID's (-1) that were included in the tracking
table.
* We do that by seeing if there is a value in the PathInfo column. If so, we can
process that to extract the DataID.
* Updates the original record in the RecArray.
*
*/
function Void _ProcessUnknownDataIDs(
RecArray recs )

Integer dataID
Integer i

for i = 1 To Length( recs )

if ( ( recs[ i ].DataID == -1 ) && IsDefined( recs[ i ].PathInfo ) && (


Length( Str.Trim( recs[ i ].PathInfo ) ) > 0 ) )

// We have a special case of navigating a relative link, or its a


Hit Highlight
// If its a hh.hhframe, grab its info from the path_info
if ( recs[ i ].Action != "hh.hhframe" )

dataID
= ._FindDataFromPathInfo( Str.Trim( recs[ i ].PathInfo ) )
else
dataID
= ._FindDataFromPathInfoForHH( Str.Trim( recs[ i ].PathInfo ) )
end

if ( IsDefined( dataID ) )

recs[ i ].DataID = dataID


end
end
end
end
sEND
f _Setup
s
/*
* Set up data strunctures and default values used in processing
*
* @param {Object} prgCtx A program context.
* @param {Boolean} localSetup Run the complete setup, or not.
*
* @return {Boolean} TRUE if OK, FALSE otherwise
*
*/
function Boolean _Setup(
Object prgCtx,
Boolean localSetup = FALSE )

Dynamic dbResult
integer i

Boolean ok = TRUE
CAPIConnect conn = prgCtx.fDbConnect.fConnection

.fPrgCtx = prgCtx

if ( ! localSetup )

.fSystemStats = Assoc.CreateAssoc()

// Set some defaults


.fSystemStats.meanDate = 1
.fSystemStats.meanSqDate = 1
.fSystemStats.meanTime = 1
.fSystemStats.meanSqTime = 1

dbResult = CAPI.Exec( conn, "SELECT * FROM Recd_SystemStatsSummary" )

if ( IsNotError( dbResult ) )

if ( Length( dbResult ) > 0 )

.fSystemStats = Assoc.FromRecord( dbResult[ 1 ] )


end
else

ok = FALSE
EchoError( Str.Format( "%1._Setup: Error retrieving data from the
Recd_SystemStatsSummary table. Error: %2", OS.Name( this ),
Str.String( dbResult ) ) )
end

if ( ok && IsDefined( .fSystemStats ) )

// Update the transaction count based on the current data in the


Operation summary table
// Not the absolute iterations.

dbResult = CAPI.Exec( conn, "SELECT SUM( RefCount ) AS refCount


FROM Recd_OperationSummary" )

if ( IsNotError( dbResult ) )

.fSystemStats.RefCount = dbResult[ 1 ].refCount


else

EchoError( Str.Format( "%1._Setup: Error retrieving


RefCount from Recd_OperationSummary table. Error: %2", OS.Name( this ), Str.String(
dbResult ) ) )
end

if ( IsUndefined( .fSystemStats.RefCount ) )

.fSystemStats.RefCount = 1
end
end
end

if ( ok )

.fRollUpDataIDs = {}
.fUpdateAssoc = Assoc.CreateAssoc()
.fInsertAssoc = Assoc.CreateAssoc()
.fValidSubTypeMapping = Assoc.CreateAssoc()

// Setup fColNames and fBindVars once in this thread.


// These featues are used as part of the delete processing in
_UpdateOperationSummaryTables()
// fBLOCKSIZE is used to determine the number of columns to create for
the RecArray which
// will hold the items to delete.

if ( Length( .fColNames ) == 0 )

for i = 1 to .fBLOCKSIZE

.fColNames += { Str.Format( ":A%1", i ) }


end
end

if ( Length( .fBindVars ) == 0 )

.fBindVars = Str.Join( .fColNames, ',' )


.fBindVars = Str.Format( "( %1 )", .fBindVars )
end

.fInterestRowsToCommitRecArray = RecArray.Create( .fColNames )


.fNumRowsProcessed = 0
.fStartTick = Date.Tick()

// The min amount of time the gather should run for. As soon as this
value has been exceeded then stop the gather
.fTimeOfExecutionInSec =
$KERNEL.SystemPreferences.GetPrefInteger( 'Recommender', 'MinExecutionTimeInSecs',
120 )

if ( .fTimeOfExecutionInSec < 0 )

.fTimeOfExecutionInSec = 120
end
end

return ok
end

sEND
f _UpdateOperationSummaryTables
s
/*
* Insert the data processed from the Recd_OperationTracking table into the
* Recd_OperationSummary table and Recd_Hot table.
* Delete the processed rows from the Recd_OperationTracking table.
*
* @return {Boolean} TRUE if OK, FALSE otherwise
*
*/
function Boolean _UpdateOperationSummaryTables()

Integer totalTick
Dynamic dbResult
Dynamic rowspersec
Integer dataID
Integer userID
Integer user
Integer i
Integer assocLength
List item
List binds
List rollupTwoThreeUsers
List assocRecords
RecArray recs
Record selectRec
String action
String stmt
String stmtDelete

Boolean ok = TRUE
Object dbConnect = .fPrgCtx.fDbConnect
CAPIConnect conn = .fPrgCtx.fDbConnect.fConnection
Object dapiCtx = .fPrgCtx.DSession()
Integer startTick = Date.Tick()
// actionViewableWhereClause will return a "Action IN ( x, x )" string with
all the actions that are tracked by Recommender
String actionViewableWhereClause =
$RECOMMENDER.RecommenderUtils.GetViewableList( "" )
List viewableActions =
$RECOMMENDER.RecommenderUtils.GetViewableActions()
String rollupUserSQLSelect = "SELECT MIN( FirstAccess ) AS
FirstAccess, MAX( RecentAccess ) AS RecentAccess, SUM( MeanDate * RefCount ) / SUM(
RefCount ) AS MeanDate, SUM( MeanSqDate * RefCount ) / SUM( RefCount ) AS
MeanSqDate, " +
"SUM( MeanTime *
RefCount ) / SUM( RefCount ) AS MeanTime, SUM( MeanSqTime * RefCount ) /
SUM( RefCount ) AS MeanSqTime, SUM( RefCount ) AS RefCount, MAX( ObjType ) AS
ObjType, MAX( SubType ) AS SubType " +
"FROM
Recd_OperationSummary WHERE DataID = :A1 AND UserID != :A2 AND Action = :A3 GROUP
BY SubType"
String rollupTwoThreeSQLSelect = Str.Substitute( "SELECT
MIN( FirstAccess ) AS FirstAccess, MAX( RecentAccess ) AS RecentAccess,
SUM( MeanDate * RefCount ) / SUM( RefCount ) AS MeanDate, SUM( MeanSqDate *
RefCount ) / SUM( RefCount ) AS MeanSqDate, " +
"SUM( MeanTime *
RefCount ) / SUM( RefCount ) AS MeanTime, SUM( MeanSqTime * RefCount ) /
SUM( RefCount ) AS MeanSqTime, SUM( RefCount ) AS RefCount, MAX( ObjType ) AS
ObjType, MAX( SubType ) AS SubType " +
"FROM
Recd_OperationSummary WHERE %Action% AND DataID = :A1 AND UserID %1= :A2 GROUP BY
SubType", { { "Action", actionViewableWhereClause } } )
String rollupExistingSelect = "SELECT 1 FROM
Recd_OperationSummary WHERE DataID = :A1 AND UserID = :A2 AND Action = :A3"
String rollupUpdateStmt = "UPDATE Recd_OperationSummary SET
RecentAccess = :A1, MeanDate = :A2, MeanSqDate = :A3, MeanTime = :A4, MeanSqTime
= :A5, RefCount = :A6 " +
"WHERE DataID
= :A7 AND UserID = :A8 AND Action = :A9"
String rollupInsertStmt = "INSERT INTO Recd_OperationSummary
( FirstAccess, RecentAccess, MeanDate, MeanSqDate, MeanTime, MeanSqTime, UserID,
DataID, ObjType, Action, RefCount, SubType ) " +
"VALUES( :A1, :A2,
:A3, :A4, :A5, :A6, :A7, :A8, :A9, :A10, :A11, :A12 )"

// The use of GROUP BY ensures that if there is no data and a math operation
is performed, that undefined values are not returned (instead no rows are
returned).
// We also want to ensure that there is only 1 row returned (and not multiple
when using GROUP BY) so we need to ensure the GROUP BY column only has one value
(hence using the maximum SubType number. Using minimum might return -1 which is
unknown values).

if ( dapiCtx.StartTrans() )

// This first part insert new rows into the summary table when
// there is a new row based on UserID/DataID/Action

assocLength = Length( .fInsertAssoc )

if ( assocLength > 0 )

// Convert the Assoc into a RecArray for insert.


assocRecords = Assoc.Items( .fInsertAssoc )
recs = RecArray.Create( .fSummaryTableList, assocLength )

for i = 1 To assocLength
RecArray.SetRecord( recs, assocRecords[ i ], i )
end

dbResult = CAPI.ExecN( conn,


"INSERT INTO Recd_OperationSummary ( FirstAccess,
RecentAccess, MeanDate, " +
"MeanSqDate, MeanTime, MeanSqTime, UserID, DataID,
ObjType, Action, RefCount, " +
"SubType ) VALUES
( :A1, :A2, :A3, :A4, :A5, :A6, :A7, :A8, :A9, :A10, :A11, :A12 )",
recs )

// We will fail silently because there is no recourse for not


succeeding.

if ( IsError( dbResult ) )

ok = FALSE
EchoError( Str.Format( "%1._UpdateOperationSummaryTables:
Error inserting rows into Recd_OperationSummary table. Error: %2", OS.Name( this ),
Str.String( dbResult ) ) )
end
end

// This part updates existing rows in the summary table, again based
// on a row anchored by UserID/DataID/SubType/Action

if ( ok )

assocLength = Length( .fUpdateAssoc )

if ( assocLength > 0 )

// Convert the Assoc into a RecArray for the update.


assocRecords = Assoc.Items( .fUpdateAssoc )
recs = RecArray.Create( .fSummaryTableListUpdate,
assocLength )

for i = 1 To assocLength

RecArray.SetRecord( recs, assocRecords[ i ], i )


end

dbResult = CAPI.ExecN( conn,


"UPDATE Recd_OperationSummary SET RecentAccess = :A1,
" +
"MeanDate = :A2, MeanSqDate = :A3, MeanTime = :A4,
MeanSqTime = :A5, RefCount = :A6 " +
"WHERE UserID = :A7 AND DataID = :A8 AND SubType
= :A9 AND Action = :A10",
recs )

// We will fail silently because there is no recourse for


not succeeding.

if ( IsError( dbResult ) )

ok = FALSE
EchoError( Str.Format( "%1._UpdateOperationSummaryTables: Error updating rows
in Recd_OperationSummary table. Error: %2", OS.Name( this ), Str.String( dbResult )
) )
end
end
end

// This section deals with the rollups of the summary table entries
that have been added or updated.

// There are three Roll-up rows.


// - User independent, action specific ( user = -1, action = [specific
action] )
// - User independent, action independent ( user = -1, action = x )
// - User specific, action independent ( user = [userID], action = x )
//
// This processing is taking the latest RecentAccess, MeanDate,
MeanTime, and Refcount from
// the User specific, action specific row and transferring it to the
roll-up rows.

if ( ok && ( Length( .fRollUpDataIDs ) > 0 ) )

for item in .fRollUpDataIDs

dataID = item[ 1 ]
action = item[ 2 ]
userID = item[ 3 ]

// Don't care if the dataid == -1. The rollups only will


work on actual objects.
continueIf ( dataID == -1 )

// for each of the dataIDs that have been operated on, see
if there is
// an existing rollup row in the summary table.

// ----User independent, action specific handling----

// Only do this if it's an action we care about

if ( action in viewableActions )

binds = { dataID, -1, action }

dbResult = CAPI.Exec( conn, rollupUserSQLSelect,


binds )

if ( IsNotError( dbResult ) && ( Length( dbResult ) >


0 ) )

// The exists a rollup row for this dataID,


update it.
selectRec = dbResult[ 1 ]

// Check for NULL values first

if ( ._CheckForNulls( selectRec ) )
// Check to see if the row already exists
dbResult = CAPI.Exec( conn,
rollupExistingSelect, binds )

if ( IsNotError( dbResult ) )

if ( Length( dbResult ) > 0 )

dbResult = CAPI.Exec( conn,


rollupUpdateStmt, { selectRec.RecentAccess,

selectRec.MeanDate,

selectRec.MeanSqDate,

selectRec.MeanTime,

selectRec.MeanSqTime,

selectRec.RefCount,

@binds } )

if ( IsError( dbResult ) )

ok = FALSE

EchoError( Str.Format( "%1._UpdateOperationSummaryTables: Error updating user


rollup row in Recd_OperationSummary table. Error: %2", OS.Name( this ), Str.String(
dbResult ) ) )

break
end
else

// ObjectType and SubType


should be the same (both represent SubType of the DataID) but
// there may be cases with
old data where they aren't. Only DataID's that were valid (and have a SubType)
// should be in this table.
dbResult = CAPI.Exec( conn,
rollupInsertStmt, { selectRec.FirstAccess,

selectRec.RecentAccess,

selectRec.MeanDate,

selectRec.MeanSqDate,

selectRec.MeanTime,

selectRec.MeanSqTime,

-1,

dataID,

selectRec.ObjType,
action,

selectRec.RefCount,

selectRec.SubType } )

// We will fail silently


because there is no recourse for not succeeding.

if ( IsNotError( dbResult ) )

if ( dbResult != 1 )

// If the insert
returns zero rows then skip the next update

List.SetRemove( .fRollUpDataIDs, item )


end
else

ok = FALSE

EchoError( Str.Format( "%1._UpdateOperationSummaryTables: Error inserting


user rollup row into Recd_OperationSummary table. Error: %2", OS.Name( this ),
Str.String( dbResult ) ) )

break
end
end
else

ok = FALSE

EchoError( Str.Format( "%1._UpdateOperationSummaryTables: Error selecting


unique user rollup row in Recd_OperationSummary table. Error: %2", OS.Name( this ),
Str.String( dbResult ) ) )

break
end
end

elseIf ( IsError( dbResult ) )

EchoError( Str.Format( "%1._UpdateOperationSummaryTables: Error selecting


user rollup rows in Recd_OperationSummary table. Error: %2", OS.Name( this ),
Str.String( dbResult ) ) )
end
end

// -----Action independent handling----

if ( item in .fRollUpDataIDs )

// If the object is still in the list.


rollupTwoThreeUsers = { -1, // User
independent, action independent
userID } //
User specific, action independent

for user in rollupTwoThreeUsers

// For user independent, we don't want the user


to be -1 versus user specific, we want results with the user's ID
stmt = ( user == -1 ) ?
Str.Format( rollupTwoThreeSQLSelect, "!" ) : Str.Format( rollupTwoThreeSQLSelect,
"" )
binds = { dataID, user }
dbResult = CAPI.Exec( conn, stmt, binds )

if ( IsNotError( dbResult ) &&


( Length( dbResult ) > 0 ) )

selectRec = dbResult[ 1 ]

// Check for NULL values first

if ( ._CheckForNulls( selectRec ) )

// Check to see if the row already


exists
dbResult = CAPI.Exec( conn,
rollupExistingSelect, { @binds, 'x' } )

if ( IsNotError( dbResult ) )

if ( Length( dbResult ) > 0 )

dbResult =
CAPI.Exec( conn, rollupUpdateStmt, { selectRec.RecentAccess,

selectRec.MeanDate,

selectRec.MeanSqDate,

selectRec.MeanTime,

selectRec.MeanSqTime,

selectRec.RefCount,

@binds,

'x' } )

if ( IsError( dbResult )
)

ok = FALSE

EchoError( Str.Format( "%1._UpdateOperationSummaryTables: Error updating


user/action rollup row in Recd_OperationSummary table. Error: %2", OS.Name( this ),
Str.String( dbResult ) ) )

break
end
else

// No row exists, insert


it instead
// ObjectType and
SubType should be the same (both represent SubType of the DataID) but
// there may be cases
with old data where they aren't. Only DataID's that were valid (and have a SubType)
// should be in this
table.
dbResult =
CAPI.Exec( conn, rollupInsertStmt, { selectRec.FirstAccess,

selectRec.RecentAccess,

selectRec.MeanDate,

selectRec.MeanSqDate,

selectRec.MeanTime,

selectRec.MeanSqTime,

user,

dataID,

selectRec.ObjType,

'x',

selectRec.RefCount,

selectRec.SubType } )
// We will fail silently
because there is no recourse for not succeeding.

if ( IsError( dbResult )
)

ok = FALSE

EchoError( Str.Format( "%1._UpdateOperationSummaryTables: Error inserting


user/action rollup row into Recd_OperationSummary table. Error: %2",
OS.Name( this ), Str.String( dbResult ) ) )

break
end
end
else

ok = FALSE

EchoError( Str.Format( "%1._UpdateOperationSummaryTables: Error selecting


unique user/action rollup row in Recd_OperationSummary table. Error: %2",
OS.Name( this ), Str.String( dbResult ) ) )

break
end
end

elseIf ( IsError( dbResult ) )

EchoError( Str.Format( "%1._UpdateOperationSummaryTables: Error selecting


user/action rollup rows in Recd_OperationSummary table. Error: %2",
OS.Name( this ), Str.String( dbResult ) ) )
end

breakIf ( !ok )
end
end
end
end

if ( ok )

// Delete the records we process from the interest table.

if ( Length( .fInterestRowsToCommitRecArray ) > 0 )

stmtDelete = "DELETE FROM Recd_OperationTracking WHERE


OperationIden IN " + .fBindVars

dbResult = CAPI.ExecN( dbConnect.fConnection,


stmtDelete, .fInterestRowsToCommitRecArray )

if ( IsError( dbResult ) )

ok = FALSE

EchoError( Str.Format( "%1._UpdateOperationSummaryTables: Error deleting


OperationIden rows from Recd_OperationTracking table. Error: %2", OS.Name( this ),
Str.String( dbResult ) ) )
end
end
end

if ( ! dapiCtx.EndTrans( ok ) )

EchoError( Str.Format( "%1._UpdateOperationSummaryTables: Could


not end the transaction", OS.Name( this ) ) )
end
else

// Can not create a new transaction.


ok = FALSE
EchoError( Str.Format( "%1._UpdateOperationSummaryTables: Could not
start a new transaction", OS.Name( this ) ) )
end

if ( ok )

totalTick = Date.Tick() - startTick

if ( totalTick > 0 )

rowspersec = Math.Round( .fNumRowsProcessed / ( ( totalTick ) /


1000.0 ) )
end

if ( .fNumRowsProcessed > 0 )

Echo( Str.Format( "Recommender Processing:[%1] Rows have been


processed by the recommender in [%2] msecs for approx [%3]
rows/secs.", .fNumRowsProcessed, totalTick, rowspersec * 1 ) )
else

Echo( "Recommender Processing: No rows to process" )


end
end

return ok
end

sEND
f _UpdateSystemStats
s
/*
* Updates the SystemStats table with the latest dates/times.
*
* @param {Record} rec The record that contains the
OperationDate
*
* @return {Void}
*
*/
function void _UpdateSystemStats( Record rec )

Real dateInt = Date.DateInteger( rec.OperationDate )


Real timeInt = Date.TimeInteger( rec.OperationDate )

.fSystemStats.meanDate =
$RECOMMENDER.RecommenderUtils.ComputeNewMean( .fSystemStats.MeanDate,
dateInt, .fSystemStats.RefCount )
.fSystemStats.meanSqDate =
$RECOMMENDER.RecommenderUtils.ComputeNewMean( .fSystemStats.MeanSqDate, dateInt *
dateInt, .fSystemStats.RefCount )
.fSystemStats.meanTime =
$RECOMMENDER.RecommenderUtils.ComputeNewMean( .fSystemStats.MeanTime,
timeInt, .fSystemStats.RefCount )
.fSystemStats.meanSqTime =
$RECOMMENDER.RecommenderUtils.ComputeNewMean( .fSystemStats.MeanSqTime, timeInt *
timeInt, .fSystemStats.RefCount )

end

sEND
f _ValidateDataIDs
s
/*
* For the list of IDs passed in, check to see if they exist in DTree.
* If they do not, they will be skipped for processing in _ProcessInterest().
* Also, do not process any IDs if their catalog value is set to hidden.
*
* @param {Object} prgCtx A program context object.
* @param {List} dataIDs A list of DataIDs to check.
*
* @return {Boolean} TRUE if OK, FALSE otherwise
*
*/
function Boolean _ValidateDataIDs(
Object prgCtx,
List dataIDs )

Assoc checkVal

Boolean ok = TRUE
Object dbConnect = prgCtx.fDBConnect
String stmt = "SELECT DataID, SubType FROM DTree WHERE %1 AND
Catalog != 2"
List IDsToCheck = $KERNEL.DataTools.RemoveDuplicates( dataIDs )

// No longer process items which are hidden. ( SFWK-1648 )


// This is also trapped inside the DispatchCBScript, but must be re-checked
here due to
// the delay from tracking to processing.
// Retrieve the SubType at the same time to save processing time later.

checkVal = dbConnect.ExecInClauses( stmt, IDsToCheck, 'DataID', {}, FALSE )

if ( checkVal.OK == TRUE )

// Convert our results into an Assoc


checkVal = $LLIAPI.RecArrayPkg.AssocFromColumns( checkVal.records,
"DataID", "SubType" )

if ( checkVal.ok == TRUE )

.fValidSubTypeMapping = checkVal.theAssoc
else

ok = FALSE
EchoError( Str.Format( "%1._ValidateDataIDs: Duplicate DataIDs
found in response when there shouldn't be.", OS.Name( this ) ) )
end
else

ok = FALSE
EchoError( Str.Format( "%1._ValidateDataIDs: Error selecting rows from
DTree table. Error: %2", OS.Name( this ), checkVal.errMsg ) )
end

return ok

end

sEND
f fBindVars
v ''
f fPrgCtx
v ?
f fRollUpDataIDs
v {}
g fValidSubTypeMapping
v ?
o {'pat::RecArrayPkg',&&lliapi::RecArrayPkg}
N RecArrayPkg
g AssocFromColumns
s
/**
* Converts 2 columns in a RecArray into a single Assoc
*
* @param theArray {RecArray} The RecArray
* @param keyNameColumn {Dynamic} The Assoc's key value column. Can either
be the name of the column
* or the index. Each value
in the column must be unique.
* @param valueNameColumn {Dynamic} The Assoc's key's value column. Can
either be the name of the column
* or the index.
* @param skipChecking {Boolean} (Optional) By default will check for
duplicate key names. Set to TRUE
* to allow key value
overwriting if duplicate keys are found.
*
* @return {Assoc}
* @returnFeature {Boolean} ok TRUE if OK, FALSE if
duplicates found and skipping was not skippped.
* @returnFeature {Assoc} theAssoc The new Assoc
*/
function Assoc AssocFromColumns(
RecArray theArray,
Dynamic keyNameColumn,
Dynamic valueNameColumn,
Boolean skipChecking = FALSE )

Dynamic keyName
Dynamic value
Integer i
Assoc retVal
Assoc newAssoc
Record row

Boolean ok = TRUE
Boolean isKeyNameString = ( Type( keyNameColumn ) == StringType )
Boolean isValueNameString = ( Type( valueNameColumn ) == StringType
)

for i = 1 To Length( theArray )

row = theArray[ i ]
keyName = isKeyNameString ? row.( keyNameColumn ) :
row[ keyNameColumn ]
value = isValueNameString ? row.( valueNameColumn ) :
row[ valueNameColumn ]

if ( ! skipChecking && Assoc.IsKey( newAssoc, keyName ) )

ok = FALSE
EchoError( Str.Format( "%1.AssocFromColumns: Duplicate key name
found, cannot convert RecArray. Duplicate key name: %2",
OS.Name( this ),
keyName ) )

break
else

newAssoc.( keyName ) = value


end
end

retVal = Assoc{ 'ok' : ok,


'theAssoc' : newAssoc }

return retVal
end
sEND
o {'pat::DataTools',&&kernel::UtilityClasses::DataTools}
N DataTools
g RemoveDuplicates
s
/**
* Remove any duplicates from a list or RecArray.
*
* @param {Dynamic} original A list or RecArray to remove the duplicates from
* @param {Dynamic} column (Optional) If a RecArray, specify the column
that you would like to remove the duplicates from.
* @param {Boolean} keepOrder (Optional) If FALSE, we use a faster method of
removing duplicates from a list by converting to a set,
* then converting back into a list.
This will have the side effect of sorting the list. If ordering matters,
* then set this to TRUE to use the
slower method of iteration over each item.
*
* @return {Dynamic} A list or RecArray (same as what was passed in)
*/

public function Dynamic RemoveDuplicates(


Dynamic original,
Dynamic column = Undefined,
Boolean keepOrder = FALSE )

Dynamic item
HashMap uniqueItems
Dynamic retVal
Dynamic columnValue
Set uniqueSet

Boolean isNumber = FALSE

if ( Type( original ) == ListType )

if ( ! keepOrder )

uniqueSet = Set.FromList( original )


retVal = Set.ToList( uniqueSet )
else

retVal = {}
for item in original

if ( IsUndefined( uniqueItems.( item ) ) )

retVal += { item }
uniqueItems.( item ) = TRUE
end
end
end

elseIf ( Type( original ) == RecArray.RecArrayType )

// Check to make sure the column is specified, it's a number or string,


if number it's within the field range,
// and if string, the column exists.
if ( IsUndefined( column ) || ( ! ( Type( column ) in { IntegerType,
StringType } ) ) ||
( ( Type( column ) == IntegerType ) && ( column >
Length( RecArray.FieldNames( original ) ) ) ) ||
( ( Type( column ) == StringType ) &&
( RecArray.IsColumn( original, column ) == 0 ) ) )

EchoError( Str.Format( "%1.RemoveDuplicates: Column not specified


correctly or doesn't exist, cannot perform de-duplication. Returning original
RecArray", OS.Name( this ) ) )
retVal = original
else

isNumber = Type( column ) == IntegerType


retVal = RecArray.Create( RecArray.FieldNames( original ) )

for item in original

columnValue = isNumber ? item[ column ] : item.( column )

if ( IsUndefined( uniqueItems.( columnValue ) ) )

RecArray.AddRecord( retVal,
RecArray.GetRecord( item ) )
uniqueItems.( columnValue ) = TRUE
end
end
end
else

EchoWarn( Str.Format( "%1.RemoveDuplicates: Original is not a list or


RecArray type. Skipping processing and returning original.", OS.Name( this ) ) )
retVal = original
end

return retVal
end
sEND
o {'pat::RecommenderUtils',&&recommender::RecommenderUtils}
N RecommenderUtils
f GetViewableList
s
/**
* Purpose: Returns a list of "viewable actions" for use in a SELECT SQL statement
*
* @param {String} keyName Temporary table name
*
* @return {String} sqlStmt A string of the form "( Action in ( 'doc.view',
'doc.fetch') )"
* that will be added to an SQL database SELECT
statement
*
* Documents - doc.fetch, doc.view, ll:download (doc.viewdoc occurs at the same
time as doc.view)
* Folders - ll:browse
* Compound Document - ll:browse
* Generation, alias, task list, discussion, project, poll, URL - ll:open
* XML DTD - doc.fetch, ll:download
*/
function String GetViewableList(
String keyName )

List viewableList
List inClause
String viewable
String retVal

// This list is static, so build the string the first time, then store it on
a feature
// for use later.
if ( IsUndefined( .fInClauseViewable ) )

viewableList = .GetViewableActions()

for viewable in viewableList

inClause += { Str.Format( "'%1'", viewable ) }


end

.fInClauseViewable = Str.Join( inClause, ", " )


end

retVal = ( Length( keyName ) > 0 ) ? Str.Format( "(%1.", keyName ) : "("


retVal += Str.Format( "Action IN (%1))", .fInClauseViewable )

return retVal
end

sEND

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy