Skip to content

Commit

Permalink
Summary: feat: roundup-admin history command has human interpretable …
Browse files Browse the repository at this point in the history
…output

Reformat journal entries and make try to make readable sentences out
of them.

Set up translation markers and added hints for the tanslators by
marking translator comments with;

  # .Hint text for translator

on the line before _() markers.

Doc'ed changes in roundup-admin docs and added info to upgrading.txt.

If the user wants old format, they can call

  history designator raw
  • Loading branch information
rouilj committed Mar 11, 2024
1 parent d37721b commit fb8f4ed
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 21 deletions.
3 changes: 3 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ Features:
- save roundup-admin history between sessions. Load
~/.roundup_admin_rlrc file to set history-size persistently. Add
pragma history_length to override for a session. (John Rouillard)
- the roundup-admin history command now dumps the journal entries
in a more human readable format. Use the raw option to get the older
machine parsible output. (John Rouillard)


2023-07-13 2.3.0
Expand Down
6 changes: 4 additions & 2 deletions doc/admin_guide.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1245,7 +1245,9 @@ Examples above show how to use it to:
* creating a new user from the command line
* list/find users in the tracker

The basic usage is::
The basic usage is:

.. code-block:: text

Usage: roundup-admin [options] [<command> <arguments>]

Expand Down Expand Up @@ -1282,7 +1284,7 @@ The basic usage is::
genconfig <filename>
get property designator[,designator]*
help topic
history designator [skipquiet]
history designator [skipquiet] [raw]
import import_dir
importtables export_dir
initialise [adminpw]
Expand Down
68 changes: 68 additions & 0 deletions doc/upgrading.txt
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,74 @@ before changing your PostgreSQL configuration, try changing the pragma
``10000``. In some cases this may be too high. See the `administration
guide`_ for further details.

roundup-admin's History Command Produces Readable Output
--------------------------------------------------------

The history command of roundup-admin used to print the raw journal
data. In this release the default is to produce more human readable
data. The original output (not pretty printed as below) was::

[('1', <Date 2013-02-18.20:30:34.125>, '1', 'create', {}),
('1',
<Date 2013-02-19.21:24:20.391>,
'1',
'set',
{'messages': (('+', ['3']),)}),
('1', <Date 2013-02-19.21:24:24.797>, '1', 'set', {'priority': '1'}),
('1',
<Date 2013-02-20.03:16:52.000>,
'1',
'link',
('issue', '2', 'dependson')),
('1', <Date 2013-02-21.20:51:40.750>, '1', 'link', ('issue', '2',
'seealso')),
('1',
<Date 2013-02-22.05:33:08.875>,
'1',
'set',
{'dependson': (('+', ['3']),), 'private': None, 'queue': None}),
('1',
<Date 2013-02-22.05:33:19.406>,
'1',
'set',
{'dependson': (('+', ['2']),)}),
('1',
<Date 2013-02-27.03:24:42.844>,
'1',
'unlink',
('issue', '2', 'seealso')),
...

Now it produces (Each entry is on one line, lines wrapped
and indented for display)::

admin(2013-02-18.20:30:34) create issue
admin(2013-02-19.21:24:20) set modified messages: added: msg3
admin(2013-02-19.21:24:24) set priority was critical(1)
admin(2013-02-20.03:16:52) link added issue2 to dependson
admin(2013-02-21.20:51:40) link added issue2 to seealso
admin(2013-02-22.05:33:08) set modified dependson: added: issue3;
private was None; queue was None
admin(2013-02-22.05:33:19) set modified dependson: added: issue2
admin(2013-02-27.03:24:42) unlink removed issue2 from seealso
...


A few things to note: set operations can either assign a property or
report a modification of a multilink property. If an assignment
occurs, the value reported is the **old value** that was there before
the assignment. It is **not** the value that is assigned. In the
example above I don't know what the current value of priority is. All
I know it was set to critical when the issue was created.

Modifications to multilink properties work differently. I know that
``msg3`` was present in the messages property after 2013-02-19 at
21:24:20 UTC.

The history command gets a new optional argument ``raw`` that produces
the old style output. The old style is (marginally) more useful for
script automation.

.. index:: Upgrading; 2.2.0 to 2.3.0

Migrating from 2.2.0 to 2.3.0
Expand Down
1 change: 1 addition & 0 deletions locale/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ roundup.pot: $(SOURCES) $(TEMPLATES)
VERSION="`${RUN_PYTHON} -c 'from roundup import __version__; \
print(__version__)';`"; \
${XGETTEXT} -j -w 80 -F \
--add-comments=".Hint " \
--package-name=Roundup \
--package-version=$$VERSION \
--msgid-bugs-address=roundup-devel@lists.sourceforge.net \
Expand Down
196 changes: 181 additions & 15 deletions roundup/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -953,16 +953,16 @@ def do_help(self, args, nl_re=nl_re, indent_re=indent_re):
return 0

def do_history(self, args):
''"""Usage: history designator [skipquiet]
''"""Usage: history designator [skipquiet] [raw]
Show the history entries of a designator.
A designator is a classname and a nodeid concatenated,
eg. bug1, user10, ...
Lists the journal entries viewable by the user for the
node identified by the designator. If skipquiet is the
second argument, journal entries for quiet properties
are not shown.
Lists the journal entries viewable by the user for the node
identified by the designator. If skipquiet is added, journal
entries for quiet properties are not shown. If raw is added,
the output is the raw representation of the journal entries.
"""

if len(args) < 1:
Expand All @@ -972,15 +972,181 @@ def do_history(self, args):
except hyperdb.DesignatorError as message:
raise UsageError(message)

skipquiet = False
if len(args) == 2:
if args[1] != 'skipquiet':
raise UsageError("Second argument is not skipquiet")
skipquiet = True
valid_args = ['skipquiet', 'raw']

if len(args) >= 2:
check = [a for a in args[1:] if a not in valid_args]
if check:
raise UsageError(
_("Unexpected argument(s): %s. "
"Expected 'skipquiet' or 'raw'.") % ", ".join(check))

skipquiet = 'skipquiet' in args[1:]
raw = 'raw' in args[1:]

getclass = self.db.getclass
def get_prop_name(key, prop_name):
# getclass and classname from enclosing method
klass = getclass(classname)
try:
property_obj = klass.properties[prop_name]
except KeyError:
# the property has been removed from the schema.
return None
if isinstance(property_obj,
(hyperdb.Link, hyperdb.Multilink)):
prop_class = getclass(property_obj.classname)
key_prop_name = prop_class.key
if key_prop_name:
return prop_class.get(key, key_prop_name)
# None indicates that there is no key_prop
return None
return None

def get_prop_class(prop_name):
# getclass and classname from enclosing method
klass = getclass(classname)
try:
property_obj = klass.properties[prop_name]
except KeyError:
# the property has been removed from the schema.
return None
if isinstance(property_obj,
(hyperdb.Link, hyperdb.Multilink)):
prop_class = getclass(property_obj.classname)
return prop_class.classname
return None # it's not a link

def _format_tuple_change(data, prop):
''' ('-', ['2', '4'] ->
"removed fred(2), jim(6)"
'''
if data[0] == '-':
op = _("removed")
elif data[0] == '+':
op = _("added")
else:
raise ValueError(_("Unknown history set operation '%s'. "
"Expected +/-.") % op)
op_params = data[1]
name = get_prop_name(op_params[0], prop)
if name is not None:
list_items = ["%s(%s)" %
(get_prop_name(o, prop), o)
for o in op_params]
else:
propclass = get_prop_class(prop)
if propclass: # noqa: SIM108
list_items = ["%s%s" % (propclass, o)
for o in op_params]
else:
list_items = op_params

return "%s: %s" % (op, ", ".join(list_items))

def format_report_class(_data):
"""Eat the empty data dictionary or None"""
return classname

def format_link (data):
'''data = ('issue', '157', 'dependson')'''
# .Hint added issue23 to superseder
f = _("added %(class)s%(item_id)s to %(propname)s")
return f % {
'class': data[0], 'item_id': data[1], 'propname': data[2]}

def format_set(data):
'''data = set {'fyi': None, 'priority': '5'}
set {'fyi': '....\ned through cleanly', 'priority': '3'}
'''
result = []

# Note that set data is the old value. So don't use
# current/future tense in sentences.

for prop, value in data.items():
if isinstance(value, str):
name = get_prop_name(value, prop)
if name:
result.append(
# .Hint read as: assignedto was admin(1)
# .Hint where assignedto is the property
# .Hint admin is the key name for value 1
_("%(prop)s was %(name)%(value)s)") % {
"prop": prop, "name": name, "value": value })
else:
# use repr so strings with embedded \n etc. don't
# generate newlines in output. Try to keep each
# journal entry on 1 line.
result.append(_("%(prop)s was %(value)s") % {
"prop": prop, "value": repr(value)})
elif isinstance(value, list):
# test to see if there is a key prop.
# Assumption, geting None here means no key
# is defined for the property's class.
name = get_prop_name(value[0], prop)
if name is not None:
list_items = ["%s(%s)" %
(get_prop_name(v, prop), v)
for v in value]
else:
prop_class = get_prop_class(prop)
if prop_class: # noqa: SIM108
list_items = [ "%s%s" % (prop_class, v)
for v in value ]
else:
list_items = value

result.append(_("%(prop)s was [%(value_list)s]") % {
"prop": prop, "value_list": ", ".join(list_items)})
elif isinstance(value, tuple):
# operation data
decorated = [_format_tuple_change(data, prop)
for data in value]
result.append(# .Hint modified nosy: added demo(3)
_("modified %(prop)s: %(how)s") % {
"prop": prop, "how": ", ".join(decorated)})
else:
result.append(_("%(prop)s was %(value)s") % {
"prop": prop, "value": value})

return '; '.join(result)

def format_unlink (data):
'''data = ('issue', '157', 'dependson')'''
return "removed %s%s from %s" % (data[0], data[1], data[2])

formatters = {
"create": format_report_class,
"link": format_link,
"restored": format_report_class,
"retired": format_report_class,
"set": format_set,
"unlink": format_unlink,
}

try:
print(self.db.getclass(classname).history(nodeid,
skipquiet=skipquiet))
# returns a tuple: (
# [0] = nodeid
# [1] = date
# [2] = userid
# [3] = operation
# [4] = details
raw_history = self.db.getclass(classname).history(nodeid,
skipquiet=skipquiet)
if raw:
print(raw_history)
return 0

def make_readable(hist):
return "%s(%s) %s %s" % (self.db.user.get(hist[2], 'username'),
hist[1],
hist[3],
formatters.get(hist[3], lambda x: x)(
hist[4]))
printable_history = [make_readable(hist) for hist in raw_history]

print("\n".join(printable_history))
except KeyError:
raise UsageError(_('no such class "%(classname)s"') % locals())
except IndexError:
Expand Down Expand Up @@ -2103,7 +2269,7 @@ def interactive(self):
".roundup_admin_history")

try:
import readline # noqa: F401
import readline
readline.read_init_file(initfile)
try:
readline.read_history_file(histfile)
Expand Down Expand Up @@ -2168,10 +2334,10 @@ def main(self): # noqa: PLR0912, PLR0911
self.print_designator = 0
self.verbose = 0
for opt, arg in opts:
if opt == '-h': # noqa: RET505 - allow elif after returns
if opt == '-h':
self.usage()
return 0
elif opt == '-v':
elif opt == '-v': # noqa: RET505 - allow elif after returns
print('%s (python %s)' % (roundup_version,
sys.version.split()[0]))
return 0
Expand Down
8 changes: 4 additions & 4 deletions share/man/man1/roundup-admin.1
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,11 @@ Retrieves the property value of the nodes specified
by the designators.

.TP
\fBhistory\fP \fIdesignator [skipquiet]\fP
\fBhistory\fP \fIdesignator [skipquiet] [raw]\fP
Lists the journal entries viewable by the user for the
node identified by the designator. If skipquiet is the
second argument, journal entries for quiet properties
are not shown.
node identified by the designator. If skipquiet is added, journal
entries for quiet properties are not shown. Without the raw option
a more human readable output format is used.
.TP
\fBimport\fP \fIimport_dir\fP
Import a database from the directory containing CSV files,
Expand Down

0 comments on commit fb8f4ed

Please sign in to comment.