#77 Allow hiding a user from the frontpage task list.
Merged 9 years ago by mikem. Opened 9 years ago by ralph.
ralph/koji hidden-user  into  master

file modified
+1 -1
@@ -64,7 +64,7 @@ 

  	@git clean -d -q -x

  

  test:

- 	nosetests --with-coverage --cover-package .

+ 	PYTHONPATH=hub/. nosetests --with-coverage --cover-package .

  

  subdirs:

  	for d in $(SUBDIRS); do make -C $$d; [ $$? = 0 ] || exit 1; done

file modified
+30 -5
@@ -9708,11 +9708,17 @@ 

          Options(dictionary):

              option[type]: meaning

              arch[list]: limit to tasks for given arches

+             not_arch[list]: limit to tasks without the given arches

              state[list]: limit to tasks of given state

-             owner[int]: limit to tasks owned by the user with the given ID

-             host_id[int]: limit to tasks running on the host with the given ID

-             channel_id[int]: limit to tasks in the specified channel

-             parent[int]: limit to tasks with the given parent

+             not_state[list]: limit to tasks not of the given state

+             owner[int|list]: limit to tasks owned by the user with the given ID

+             not_owner[int|list]: limit to tasks not owned by the user with the given ID

+             host_id[int|list]: limit to tasks running on the host with the given ID

+             not_host_id[int|list]: limit to tasks running on the hosts with IDs other than the given ID

+             channel_id[int|list]: limit to tasks in the specified channel

+             not_channel_id[int|list]: limit to tasks not in the specified channel

+             parent[int|list]: limit to tasks with the given parent

+             not_parent[int|list]: limit to tasks without the given parent

              decode[bool]: whether or not xmlrpc data in the 'request' and 'result'

                            fields should be decoded; defaults to False

              method[str]: limit to tasks of the given method
@@ -9752,17 +9758,36 @@ 

          aliases = [f[1] for f in flist]

  

          conditions = []

-         for f in ['arch','state']:

+ 

+         for f in ['arch', 'state']:

+             # Include list types

              if opts.has_key(f):

                  conditions.append('%s IN %%(%s)s' % (f, f))

+             # Exclude list types

+             if opts.has_key('not_' + f):

+                 conditions.append('%s NOT IN %%(not_%s)s' % (f, f))

+ 

          for f in ['owner', 'host_id', 'channel_id', 'parent']:

+             # Include int types

              if opts.has_key(f):

                  if opts[f] is None:

                      conditions.append('%s IS NULL' % f)

+                 elif isinstance(opts[f], types.ListType):

+                     conditions.append('%s IN %%(%s)s' % (f, f))

                  else:

                      conditions.append('%s = %%(%s)i' % (f, f))

+             # Exclude int types

+             if opts.has_key('not_' + f):

+                 if opts['not_' + f] is None:

+                     conditions.append('%s IS NOT NULL' % f)

+                 elif isinstance(opts['not_' + f], types.ListType):

+                     conditions.append('%s NOT IN %%(not_%s)s' % (f, f))

+                 else:

+                     conditions.append('%s != %%(not_%s)i' % (f, f))

+ 

          if opts.has_key('method'):

              conditions.append('method = %(method)s')

+ 

          time_opts = [

                  ['createdBefore', 'create_time', '<'],

                  ['createdAfter', 'create_time', '>'],

empty or binary file added
@@ -0,0 +1,78 @@ 

+ import unittest

+ import mock

+ 

+ import kojihub

+ 

+ 

+ class TestListing(unittest.TestCase):

+     def setUp(self):

+         self.hub = kojihub.RootExports()

+         self.standard_processor_kwargs = dict(

+             tables=mock.ANY,

+             columns=mock.ANY,

+             values=mock.ANY,

+             joins=mock.ANY,

+             clauses=mock.ANY,

+             opts=mock.ANY,

+             aliases=mock.ANY,

+         )

+ 

+     @mock.patch('kojihub.QueryProcessor')

+     def test_list_tasks_basic_invocation(self, processor):

+         generator = self.hub.listTasks()

+         list(generator)  # Exhaust the generator

+         processor.assert_called_once_with(**self.standard_processor_kwargs)

+ 

+     @mock.patch('kojihub.QueryProcessor')

+     def test_list_tasks_by_owner_as_int(self, processor):

+         generator = self.hub.listTasks(opts={'owner': 1})

+         results = list(generator)  # Exhaust the generator

+         arguments = self.standard_processor_kwargs.copy()

+         arguments['clauses'] = ['owner = %(owner)i']

+         processor.assert_called_once_with(**arguments)

+         self.assertEqual(results, [])

+ 

+     @mock.patch('kojihub.QueryProcessor')

+     def test_list_tasks_by_not_owner_as_int(self, processor):

+         generator = self.hub.listTasks(opts={'not_owner': 1})

+         results = list(generator)  # Exhaust the generator

+         arguments = self.standard_processor_kwargs.copy()

+         arguments['clauses'] = ['owner != %(not_owner)i']

+         processor.assert_called_once_with(**arguments)

+         self.assertEqual(results, [])

+ 

+     @mock.patch('kojihub.QueryProcessor')

+     def test_list_tasks_by_arch(self, processor):

+         generator = self.hub.listTasks(opts={'arch': ['x86_64']})

+         results = list(generator)  # Exhaust the generator

+         arguments = self.standard_processor_kwargs.copy()

+         arguments['clauses'] = ['arch IN %(arch)s']

+         processor.assert_called_once_with(**arguments)

+         self.assertEqual(results, [])

+ 

+     @mock.patch('kojihub.QueryProcessor')

+     def test_list_tasks_by_not_arch(self, processor):

+         generator = self.hub.listTasks(opts={'not_arch': ['x86_64']})

+         results = list(generator)  # Exhaust the generator

+         arguments = self.standard_processor_kwargs.copy()

+         arguments['clauses'] = ['arch NOT IN %(not_arch)s']

+         processor.assert_called_once_with(**arguments)

+         self.assertEqual(results, [])

+ 

+     @mock.patch('kojihub.QueryProcessor')

+     def test_list_tasks_by_owner_as_list(self, processor):

+         generator = self.hub.listTasks(opts={'owner': [1, 2]})

+         results = list(generator)  # Exhaust the generator

+         arguments = self.standard_processor_kwargs.copy()

+         arguments['clauses'] = ['owner IN %(owner)s']

+         processor.assert_called_once_with(**arguments)

+         self.assertEqual(results, [])

+ 

+     @mock.patch('kojihub.QueryProcessor')

+     def test_list_tasks_by_not_owner_as_list(self, processor):

+         generator = self.hub.listTasks(opts={'not_owner': [1, 2]})

+         results = list(generator)  # Exhaust the generator

+         arguments = self.standard_processor_kwargs.copy()

+         arguments['clauses'] = ['owner NOT IN %(not_owner)s']

+         processor.assert_called_once_with(**arguments)

+         self.assertEqual(results, [])

file modified
+5
@@ -28,3 +28,8 @@ 

  # If False, then the footer will be included as another Kid Template.

  # Defaults to True

  LiteralFooter = True

+ 

+ # This can be a space-delimited list of the numeric IDs of users that you want

+ # to hide from tasks listed on the front page. You might want to, for instance,

+ # hide the activity of an account used for continuous integration.

+ #HiddenUsers = 5372 1234

file modified
+13 -2
@@ -272,14 +272,25 @@ 

      values = _initValues(environ)

      server = _getServer(environ)

  

+     opts = environ['koji.options']

      user = environ['koji.currentUser']

  

-     values['builds'] = server.listBuilds(userID=(user and user['id'] or None), queryOpts={'order': '-build_id', 'limit': 10})

+     values['builds'] = server.listBuilds(

+         userID=(user and user['id'] or None),

+         queryOpts={'order': '-build_id', 'limit': 10}

+     )

  

      taskOpts = {'parent': None, 'decode': True}

      if user:

          taskOpts['owner'] = user['id']

-     values['tasks'] = server.listTasks(opts=taskOpts, queryOpts={'order': '-id', 'limit': 10})

+     if opts.get('HiddenUsers'):

+         taskOpts['not_owner'] = [

+             int(userid) for userid in opts['HiddenUsers'].split()

+         ]

+     values['tasks'] = server.listTasks(

+         opts=taskOpts,

+         queryOpts={'order': '-id', 'limit': 10}

+     )

  

      values['order'] = '-id'

  

This adds new query arguments to the taskList hub xmlrpc endpoint, and then
makes use of those arguments in koji-web. A new optional configuration value
is added for koji-web: HiddenUser, which can be used to specify which user
account should be hidden. This could be useful for deployments that have a
continuous-integration account, the spam from which makes the frontpage
difficult to read.

Unit test cases are also added for some functions of the hub taskList endpoint.

We want to use this feature in the Fedora deployment to hide the koschei user.

rebased

9 years ago

I feel like this should be HiddenUsers, and it should allow multiple userIDs to be specified. We're definitely going to have more than one CI user.

Can do, but that will make the whole change a little more complicated.

If we allow a plural not_owner argument, we should probably also allow a plural owner argument (and so on, and so on...).

I'll do that. The test suite will help to verify that it's not totally bananas. :banana:

2 new commits added

  • Make HiddenUser into HiddenUsers.
  • Remove unused var.
9 years ago

OK, see df13b4d. It's plural now, but it makes the whole change a little more invasive, so, review with caution. :)

1 new commit added

  • Split the HiddenUsers string pulled from our config.
9 years ago

1 new commit added

  • Space-delimited.
9 years ago

+1

I don't see a mechanism for users that do want to see all of the tasks.

Otherwise, the changes look good, and thanks for including tests :clap:

Commit 37ff8d2 fixes this pull-request

Pull-Request has been merged by mikem@redhat.com

9 years ago