Setting Time-Series Data Dissemination Flags
Managing Time Series Dissemination in Schema 22.2.1
Export Script
from hec.cwmsVue import CwmsListSelection
from javax.swing import JFileChooser
from javax.swing import JOptionPane
from javax.swing import UIManager
from javax.swing.filechooser import FileFilter
import datetime, DBAPI, hashlib, os, sys, threading
script_name = "Export TS Dissemenation"
output_filename = None
sql = '''
select
cwms_ts_id,
cwms_data_dissem.get_dest(ts_code)
from
cwms_v_ts_id
where
db_office_code = cwms_util.get_db_office_code()
order by
cwms_ts_id
'''
#
# FileFilter class to restrict choices to .txt output file (for Excel Query)
#
class TextFileFilter(FileFilter) :
def __init__(self) :
pass
def getDescription(self) :
return "Text files (.txt)"
def accept(self, f) :
return os.path.splitext(f.getName())[1] == ".txt"
#
# routine to perform export (in a separate thread in CWMS-Vue)
#
def do_export(output_filename, main_window) :
# open the output file
output_file = open(output_filename, "w")
#
# get a database connection
#
print("\t%s: Connecting to database..." % str(datetime.datetime.now())[:19])
if main_window :
db = DBAPI.open()
else :
# command line - try credentials in environment
dburl = os.getenv("dburl")
dbusr = os.getenv("dbusr")
dbpwd = os.getenv("dbpwd")
dbofc = os.getenv("dbofc")
if all((dburl, dbusr, dbpwd, dbofc)) :
db = DBAPI.open(dburl, dbusr, dbpwd, dbofc)
else :
# no credentials in environment, use login dialog (slow....)
db = DBAPI.open()
#
# excute the query to get the data
#
conn = db.getConnection()
stmt = conn.prepareStatement(sql)
print("\t%s: Running query..." % str(datetime.datetime.now())[:19])
rs = stmt.executeQuery()
count = 0
#
# write the data to the output file
#
print("\t%s: Exporting data..." % str(datetime.datetime.now())[:19])
while rs.next() :
count += 1
ts_id = rs.getString(1)
ts_dest = rs.getInt(2)
h = hashlib.new("sha256")
h.update(ts_id)
ts_hash = h.hexdigest()[-8:]
output_file.write("%s\t%s\t%s\n" % (ts_hash, "\t".join(ts_id.split(".")), ts_dest))
output_file.close()
print("\t%s: %d ts exported to %s" % (str(datetime.datetime.now())[:19], count, output_filename))
if main_window :
# pop up a message box when done if in CWMS-Vue
JOptionPane.showMessageDialog(
main_window,
"Done\n%s records exported" % count,
"TS Dissemination File",
JOptionPane.INFORMATION_MESSAGE)
#
# clean up and exit script
#
rs.close()
stmt.close()
conn.close()
if main_window :
print("#\n# Script %s done\n#" % script_name)
else :
db.close()
#
# main script code
#
def main() :
global output_filename
if len(sys.argv) > 1 :
output_filename = sys.argv[1]
#
# get the CWMS-Vue window (will be None if running from command line)
#
main_window = CwmsListSelection.getMainWindow()
if main_window :
print("#\n# Running script %s\n#" % script_name)
else :
# set the look and feel to Windows
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel")
#
# choose an output file
#
if not output_filename :
dlg = JFileChooser()
dlg.setDialogTitle("TS Dissemination File")
dlg.setDialogType(JFileChooser.SAVE_DIALOG)
dlg.setFileFilter(TextFileFilter())
dlg.setAcceptAllFileFilterUsed(False)
if dlg.showSaveDialog(main_window) == JFileChooser.APPROVE_OPTION :
# file selected
output_filename = dlg.getSelectedFile().getCanonicalPath()
if os.path.splitext(output_filename)[1] != ".txt" :
output_filename += ".txt"
else :
# Cancel button pressed
if main_window is not None :
JOptionPane.showMessageDialog(
main_window,
"Canceled. No ts exported",
script_name,
JOptionPane.INFORMATION_MESSAGE)
return
if os.path.exists(output_filename) :
# confirm file overwrite
answer = JOptionPane.showConfirmDialog(
main_window,
"The file %s already exists\nDo you want to replace the existing file?" % os.path.split(output_filename)[1],
"TS Dissemination File",
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE)
if answer != JOptionPane.OK_OPTION :
return
if main_window :
threading.Thread(target=do_export, args=(output_filename, main_window)).start()
else :
do_export(output_filename, main_window)
if __name__ == "__main__" : main()
Import Script
from hec.cwmsVue import CwmsListSelection
from javax.swing import JFileChooser
from javax.swing import JOptionPane
from javax.swing import UIManager
from javax.swing.filechooser import FileFilter
import datetime, DBAPI, hashlib, os, sys, threading
script_name = "Import TS Dissemenation"
input_filename = None
#
# FileFilter class to restrict choices to .txt input file (for Excel Query)
#
class TextFileFilter(FileFilter) :
def __init__(self) :
pass
def getDescription(self) :
return "Text files (.txt)"
def accept(self, f) :
return os.path.splitext(f.getName())[1] == ".txt"
#
# routine to remove quote characters
#
def unquote(str) :
if len(str) > 1 and str[0] in "\"'" and str[-1] == str[0] :
return str[1:-1]
return str
#
# routine to perform export (in a separate thread in CWMS-Vue)
#
def do_import(input_filename, main_window) :
#
# parse the input file
#
print("\t%s: Parsing input file..." % str(datetime.datetime.now())[:19])
with open(input_filename) as f :
lines = f.read().strip().split("\n")
ts_ids = []
ts_dests = []
for i in range(len(lines)) :
line = lines[i]
line_number = i + 1
parts = map(unquote, line.split("\t"))
if line_number == 1 and parts[0] == "Column1" :
continue
try :
assert len(parts) == 8
ts_hash = parts[0]
ts_id = ".".join(parts[1:7])
ts_dest = int(parts[7])
except :
raise Exception("Line %d is invalid: %s" % (line_number, line))
h = hashlib.new("sha256")
h.update(ts_id)
if ts_hash != h.hexdigest()[-8:] :
raise Exception("Time series ID on line %d has been modified since export: %s" % (line_number, line))
if not 0 <= ts_dest <= 2 :
raise Exception("Time series destination line %d is not 0, 1, or 2: %s" % (line_number_line))
ts_ids.append(ts_id)
ts_dests.append(ts_dest)
#
# determine destination filtering
#
if ts_dests == [2] * len(ts_dests) :
filter_to_corpsnet = filter_to_dmz = 'F'
elif 0 in ts_dests :
filter_to_corpsnet = filter_to_dmz = 'T'
else :
filter_to_corpsnet = 'F'
filter_to_dmz = 'T'
#
# get a database connection
#
print("\t%s: Connecting to database..." % str(datetime.datetime.now())[:19])
if main_window :
db = DBAPI.open()
else :
# command line - try credentials in environment
dburl = os.getenv("dburl")
dbusr = os.getenv("dbusr")
dbpwd = os.getenv("dbpwd")
dbofc = os.getenv("dbofc")
if all((dburl, dbusr, dbpwd, dbofc)) :
db = DBAPI.open(dburl, dbusr, dbpwd, dbofc)
else :
# no credentials in environment, use login dialog (slow....)
db = DBAPI.open()
conn = db.getConnection()
#
# set the destination filtering
#
print("\t%s: Setting destination filtering..." % str(datetime.datetime.now())[:19])
stmt = conn.prepareCall('''
begin
cwms_data_dissem.set_ts_filtering(:1, :2, cwms_util.get_db_office_id);
end;''')
stmt.setString(1, filter_to_corpsnet)
stmt.setString(2, filter_to_dmz)
stmt.execute()
stmt.close()
#
# clear the destination filters in the ts groups
#
print("\t%s: Clearing existing destination filters in ts groups..." % str(datetime.datetime.now())[:19])
stmt = conn.prepareCall('''
declare
l_office_id cwms_v_ts_id.db_office_id%type := cwms_util.get_db_office_id;
l_ts_groups cwms_t_str_tab := cwms_t_str_tab(
'CorpsNet Include List',
'CorpsNet Exclude List',
'DMZ Include List',
'DMZ Exclude List');
begin
for i in 1.. l_ts_groups.count loop
cwms_ts.unassign_ts_group(
p_ts_category_id => 'Data Dissemination',
p_ts_group_id => l_ts_groups(i),
p_ts_id => null,
p_unassign_all => 'T',
p_db_office_id => l_office_id);
end loop;
end;''')
stmt.execute()
stmt.close()
#
# create the new destination filters in the ts groups
#
print("\t%s: Setting new destination filters in ts groups..." % str(datetime.datetime.now())[:19])
stmt = conn.prepareStatement('''
declare
l_office_id cwms_v_ts_id.db_office_id%type := cwms_util.get_db_office_id;
begin
cwms_ts.assign_ts_group (
p_ts_category_id => 'Data Dissemination',
p_ts_group_id => :1,
p_ts_id => :2,
p_db_office_id => l_office_id);
end;''')
if filter_to_dmz == 'T' :
for i in range(len(ts_ids)) :
if ts_dests[i] == 2 :
stmt.setString(1, "DMZ Include List")
stmt.setString(2, ts_ids[i])
stmt.execute()
elif ts_dests[i] == 1 and filter_to_corpsnet == 'T' :
stmt.setString(1, "CorpsNet Include List")
stmt.setString(2, ts_ids[i])
stmt.execute()
stmt.close()
conn.commit()
print("\t%s: %d ts imported from %s" % (str(datetime.datetime.now())[:19], len(ts_ids), input_filename))
if main_window :
# pop up a message box when done if in CWMS-Vue
JOptionPane.showMessageDialog(
main_window,
"Done\n%s records imported" % len(ts_ids),
"TS Dissemination File",
JOptionPane.INFORMATION_MESSAGE)
#
# clean up and exit script
#
conn.close()
if main_window :
print("#\n# Script %s done\n#" % script_name)
else :
db.close()
#
# main script code
#
def main() :
global input_filename
if len(sys.argv) > 1 :
input_filename = sys.argv[1]
#
# get the CWMS-Vue window (will be None if running from command line)
#
main_window = CwmsListSelection.getMainWindow()
if main_window :
print("#\n# Running script %s\n#" % script_name)
else :
# set the look and feel to Windows
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel")
#
# choose an input file
#
if not input_filename :
dlg = JFileChooser()
dlg.setDialogTitle("TS Dissemination File")
dlg.setDialogType(JFileChooser.OPEN_DIALOG)
dlg.setFileFilter(TextFileFilter())
dlg.setAcceptAllFileFilterUsed(False)
if dlg.showOpenDialog(main_window) == JFileChooser.APPROVE_OPTION :
# file selected
input_filename = dlg.getSelectedFile().getCanonicalPath()
if os.path.splitext(input_filename)[1] != ".txt" :
input_filename += ".txt"
else :
# Cancel button pressed
if main_window is not None :
JOptionPane.showMessageDialog(
main_window,
"Canceled. No ts imported",
script_name,
JOptionPane.INFORMATION_MESSAGE)
return
if main_window :
threading.Thread(target=do_import, args=(input_filename, main_window)).start()
else :
do_import(input_filename, main_window)
if __name__ == "__main__" : main()
Background
Local time series are possibly disseminated to one of the following destinations:
- CorpsNet
- DMZ
All Time series values are assigned a dissemination destination of one of three values:
- 0 - Do not disseminate
- 1 - Disseminate to CorpsNet only
- 2 - Disseminate to (CorpsNet and) DMZ
There is no setting for disseminating to DMZ only.
Data dissemination is controlled by whether a time series is included in a materialized view named MV_TS_CODE_FILTER that is refreshed once per hour. The CMA application is used to set whether a time series is disseminated, and all disseminated time series have a destination of 2 (DMZ).
With database schema 22.2.1 control of dissemination is changed to:
- Whether 0, 1, or 2 of dissemination filter switches are turned on:
- Filter to CorpsNet
- OFF - All time series are disseminated to CorpsNet, regardless of assignment to data dissemination time series groups
- ON - Time series are disseminated based on their assignment to the various data dissemination time series groups
- Filter to DMZ
- OFF (Requires Filter to CorpsNet to also be OFF) - All time series are disseminated to DMZ, regardless of assignment to data dissemination time series groups
- ON - Time series are disseminated based on their assignment to the various data dissemination time series groups
- Filter to CorpsNet
- Based, on the state of the dissemination filter switches, whether the time series is assigned to one or more of the data dissemination time series groups that are used as destination filters.
- If Filter to CorpsNet is ON, time series are disseminated to CorpsNet only if both of the following are true:
- The time series is assigned to the "CorpsNet Include List" data dissemination time series group
- The time series is not assigned to the "CorpsNet Exclude List" data dissemination time series group
- If Filter to DMZ is ON, the time series are disseminated to DMZ only if all of the following are true:
- The time series is assigned to the "DMZ Include List" data dissemination time series group
- The time series is not assigned to the "DMZ Exclude List" data dissemination time series group
- The time series is not assigned to the "CorpsNet Exclude List" data dissemination time series group (it need not be assigned to the "CorpsNet Include LIst" dissemination time series group.
- If Filter to CorpsNet is ON, time series are disseminated to CorpsNet only if both of the following are true:
Before Schema Update
- Dissemination is controlled by MV_TS_CODE_FILTER
- No time series are assigned to the data dissemination time series groups
After Schema Update
- Dissemination is controlled by the dissemination filter switches and assignment to data dissemination time series groups
- Both dissemination filter switches are ON and all time series in MV_TS_CODE_FILTER are assigned to the "DMZ Include List" group. No time series are assigned to any other data dissemination group. (Caveat: If all your time series were in MV_TS_CODE_FILTER then both switches are ON and no time series will be assigned to any data dissemination group, though this situation is unexpected.)
While you technically can manage which time series are assigned to the data dissemination groups in CWMS-Vue, it isn't easy for groups with many assignments. The assigned time series are neither sortable nor filterable, and you cannot select multiple time series at once. Assigning time series to a group is a little more sophisticated as the catalog may be sorted and filtered by a single string, and multiple selections can be made.
Managing Time Series Dissemination with CWMS-Vue Scripts and Excel
Until CWMS-Vue is updated to more reasonably manage the time series dissemination, Excel with CWMS-Vue scripts to export and import the time series dissemination provides a workable alternative.
Running the Export Script
Just execute the script and select the name of the output file.
Editing the Dissemination Information in Excel
Import your output file with Data→From Text/CSV and select the default load option.
- Don't edit data in any columns except for the destination column (H, labeled "Column8"). If the values in any other column are changed, the import script will fail to execute. Column A, labeled "Column1" is a short hash of the time series identifier that is used by the import script to ensure the time series identifier hasn't changed.
- Don't add rows to the data. If a time series has been added to the database since you ran the export script, simply run it again to include the new time series.
- Don't delete rows from the data. In most cases this would simply result in the deleted time series not being disseminated at all. However if no rows remain with destination values of 0 then the import script will off the Filter to DMZ and/or the Filter to CorpsNet filter switches, which is probably not what you want.
- Re-run the export script to make corrections. If the data gets to be in an undesired state (even after the import script has been run), simply re-run the export script and start editing again.
Use the sorting and filtering to display desired time series. In the example below I'm moving everything with a version of Rev-SCADA and a parameter other than Elev or Elev-Tailwater from the DMZ destination (2) to the CorpsNet only destination (1).
First I filter the destination column to keep me from accidentally adding any time series to CorpsNet that were previously not disseminated.
In a similar manner I filter on the version and parameter columns to select my desired time series, then set the destination for all of them to 1
After making all the modifications, save the file using File→Save As. Be sure to select "Text (Tab delimited) (*.txt)" as the file type. I'm saving over the file I exported, but that is not necessary. Note that you will get a warning about not being able to save multiple sheets to the file type. Select OK.
Running the Import Script
Just run the script and select the name of the file you saved from Excel.
After the Import
You should see the changes you made reflected in CWMS-Vue after refreshing the catalog.
You can use CWMS-Vue to make changes in time series assignments to the data dissemination time series groups if you want. Any changes you make will be reflected in the data exported the next time the export script is run.