queryJournal does not have same 'search' feature as Alarm Journal component

I am trying to build an export functionality for the exact alarms that are displayed on my Alarm Journal component.
The following bindings have been utilized on my Alarm Journal component:

  • startDate
  • endDate
  • plus the following:

image

Unfortunately it seems the system.alarm.queryJournal function is not set up quite the same way, which is leading to inconsistency between what i can see in the Vision page, and what i am exporting.

First problem is the "Search String" on the Alarm Journal component. The scripting version only offers:

But the handy thing about "Search String" is that it can search in both source and displayPath simultaneously.

I have a free form text entry component that is bound to "SearchString" to allow people to search for either keywords in displayPath or folders in the tag structure.

I also combine the tag browser tree whcih is bound to the sourceFilter component so that we have more flexibility.

You could probably create your export file directly from the table model of the alarm journal rather than trying to recreate its query. I've never attempted anything like that with the alarm journal component specifically, but here is a script I've developed for the alarm status table that exports the alarms to a CSV file on the user's desktop. Perhaps it can be adapted easily to your usage case. It will only export the rows that are being displayed in the alarm status table. It does not export any data that has been filtered out.

Here is the script:

alarmStatusTable = event.source.parent.getComponent('Alarm Status Table')#path to alarm status table component
import getpass
import time
def getHeaders(table):
	columnList = []
	for column in range(table.getModel().columnCount):
	    columnList.append(table.getModel().getColumnName(column))
	return(columnList)
def getDataset(alarmStatusTable):
	if alarmStatusTable.componentCount > 0:
		for component in alarmStatusTable.getComponents():
			if 'AlarmStatusTable$1' in str(component.__class__):
				headers = getHeaders(component)
				model = component.getModel()
				data = []
				for row in range(model.rowCount):
					rowData = []
					for column in range(model.columnCount):
						rowData.append(model.getValueAt(row, column))
					data.append(rowData)
				dataset = system.dataset.toDataSet(headers, data)
				return dataset
			else:
				dataset = getDataset(component)
				if dataset is not None:
					return dataset
	return None
def getFilename():
	current_time = time.localtime()
	username = getpass.getuser()
	filepath = 'C:/Users/' + username + '/Desktop/'
	fileName = 'alarmExport_' + time.strftime('%Y-%m-%d_%H-%M-%S', time.localtime())
	return filepath + fileName
filename = getFilename()
dataset = getDataset(alarmStatusTable)
system.dataset.exportCSV(filename, True, dataset)
1 Like

I don't have an alarm journal set up at the moment to test this on, but looking at it, I believe there are only two changes that would be needed to make the above script work on an alarm journal:
• Correct the path in line one of the code
• Replace 'AlarmStatusTable$1' with 'AlarmJournalTable$3' in the line if 'AlarmStatusTable$1' in str(component.__class__):

Here is a revised script that I believe will work specifically with the journal table:

Revised Alarm Journal Script
alarmJournalTable = event.source.parent.getComponent('Alarm Journal')#path to alarm status table component
import getpass
import time
def getHeaders(table):
	columnList = []
	for column in range(table.getModel().columnCount):
	    columnList.append(table.getModel().getColumnName(column))
	return(columnList)
def getDataset(alarmJournalTable):
	if alarmJournalTable.componentCount > 0:
		for component in alarmJournalTable.getComponents():
			if 'AlarmJournalTable$3' in str(component.__class__):
				headers = getHeaders(component)
				model = component.getModel()
				data = []
				for row in range(model.rowCount):
					rowData = []
					for column in range(model.columnCount):
						rowData.append(model.getValueAt(row, column))
					data.append(rowData)
				dataset = system.dataset.toDataSet(headers, data)
				return dataset
			else:
				dataset = getDataset(component)
				if dataset is not None:
					return dataset
	return None
def getFilename():
	current_time = time.localtime()
	username = getpass.getuser()
	filepath = 'C:/Users/' + username + '/Desktop/'
	fileName = 'alarmExport_' + time.strftime('%Y-%m-%d_%H-%M-%S', time.localtime())
	return filepath + fileName
filename = getFilename()
dataset = getDataset(alarmJournalTable)
system.dataset.exportCSV(filename, True, dataset)
1 Like

This works like a charm, thank you!

Is there anyway to access the alarm event object itself?
Like how in the results of the system.alarm.queryJournal function you can do alarm.get(propName) to get any property you want.

1 Like

Column zero of the journal table model will always be the event ID regardless of how the user has arranged the journal table. Therefore, you could do something like this:

eventID = model.getValueAt(row, 0)
prop = [("EventId","=", eventID)]
alarmArray = system.alarm.queryJournal(all_properties = prop)
alarm = alarmArray[0]

What is returned by the journal query will actually be an iterable array. However, since in this example, you've structured your query in a way that will only return one result, you can immediately access the alarm object by adding [0] to the end of the array. In this regard, queryJournal and queryStatus both work the same.

Once you have the alarm object you an access any of the properties with the following map:

ackNotes = alarm.getOrElse('ackNotes', None)
ackTime = alarm.getOrElse('eventTime', None) if alarm.acked else None
ackedBy = alarm.getOrElse('ackUser', None)
activePipeline = alarm.getOrElse('activePipeline', None)
clearedPipeline = alarm.getOrElse('clearPipeline', None)
ackPipeline = alarm.getOrElse('ackPipeline', None)
activeTime = alarm.getOrElse('activeTime', None)
clearTime = alarm.getOrElse('clearTime', None)
eventValue = alarm.getOrElse('eventValue', None)
deadBand = alarm.getOrElse('deadband', None)
displayPath = alarm.displayPath									#This also Works:.getDisplayPath() AND This also works: .getOrElse('displayPath', None)
acked = alarm.acked 											#This also works: .getOrElse('isAcked', None)
active = alarm.getOrElse('isActive', None) 						#In my experimentation, .active didn't work
clear = alarm.cleared 											#This also works: .getOrElse('isClear', None)
name = alarm.name												#This also works.getName() AND This also works: .getOrElse('name', None)
notes = alarm.notes 											#This also works: .getNotes() AND This also works: .getOrElse('notes', None)
priority = alarm.priority										#This also works: .getPriority() AND This also works:.getOrElse('priority', None)
sourcePath = alarm.source										#This also works: getSource() AND This also works: .getOrElse('source', None)
currentState = alarm.state										#This also works: .getState() #The following DID NOT return the expected result: .getOrElse('eventState', None)
eventID = alarm.id												#This also works: .getID()
unacknowledgedDuration = alarm.getOrElse('ackDuration', None)
activeDuration = alarm.getOrElse('activeDuration', None)
label = alarm.label

Note, if you have structured your alarm query in a way that will return more than one result, you will need to iterate through it to get all of the alarm objects.

Example:

for alarm in alarmArray:
	alarmObject = alarmArray[alarm]

Thanks,
I'd be a bit concerned with calling system.alarm.queryJournal for X amount of times where X is the number of events in the table component. We could easily get into thousands of items and I do not want to bog it down too much.

It seems like it would be better to start with the full results from the queryJournal in that regard, and build up the dataset from that.
Something like:

	for alarm in journalResults:
		# NOTE: Casting alarm.getPriority() as string shows string equivilant instead of decimal
		data.append([alarm.get(eventTimeObject), alarm.getId(),alarm.getSource(),alarm.getDisplayPath(),alarm.getName(),alarm.getState(),str(alarm.getPriority()),alarm.getOrElse("AlarmClass",None),alarm.getOrElse("AlarmType",None),alarm.getOrElse("TagType", None)])
		
	# end for
	journalData = system.dataset.toDataSet(headers, data)

But then it's back to the difficulties of ensuring the filters match. I'll sleep on it and see if any inspiration strikes.

1 Like

Agreed. That would be a big performance problem, and it doesn't make sense to build a dataset from a table that already provides one. For me, it would be much simpler to filter unwanted columns from the table's existing data than to build a new dataset from scratch because I know that the order of the column model doesn't change no matter how the user arranges the table and no matter which columns the user decides to hide or show.

That said, if I were building a custom journal table from scratch, I would probably build a list of properties first, so I could obtain my dataset from a single query.