#16 WIP: Packager Dashboard
Closed 3 years ago by frantisekz. Opened 3 years ago by frantisekz.

@@ -0,0 +1,34 @@ 

+ """Dashboard Users Table

+ 

+ Revision ID: f0b018ebc214

+ Revises: f37edffe0265

+ Create Date: 2020-05-04 14:14:51.481925

+ 

+ """

+ from alembic import op

+ import sqlalchemy as sa

+ 

+ 

+ # revision identifiers, used by Alembic.

+ revision = 'f0b018ebc214'

+ down_revision = 'f37edffe0265'

+ branch_labels = None

+ depends_on = None

+ 

+ 

+ def upgrade():

+     # ### commands auto generated by Alembic - please adjust! ###

+     op.create_table('dashboard_user_data',

+     sa.Column('id', sa.Integer(), nullable=False),

+     sa.Column('username', sa.Text(), nullable=True),

+     sa.Column('last_accessed', sa.DateTime(), nullable=True),

+     sa.PrimaryKeyConstraint('id'),

+     sa.UniqueConstraint('username')

+     )

+     # ### end Alembic commands ###

+ 

+ 

+ def downgrade():

+     # ### commands auto generated by Alembic - please adjust! ###

+     op.drop_table('dashboard_user_data')

+     # ### end Alembic commands ###

file modified
+10 -1
@@ -11,5 +11,14 @@ 

  

  # MAX_DB_AGE will be ignored if FORCE_CACHED_DATA, DB cache will be used no matter how old it is

  # make sure to set up cron with "runcli.py sync" if set to True

+ # The idea is to have sync run every hour and leave MAX_DB_AGE big enough not to drop local_cache

+ # before sync finishes

  FORCE_CACHED_DATA = False

- MAX_DB_AGE = 1800 # Max cache age allowed in seconds (30 minutes)

+ MAX_DB_AGE = 7200 # Max cache age allowed in seconds (120 minutes)

+ 

+ ACTIVITY_REQUIRED = 14 # Cache data for users who used the service in at least last 14 days

+ 

+ PACKAGE_MAINTAINERS_JSON_URL = "https://src.fedoraproject.org/extras/pagure_owner_alias.json"

+ ORPHANS_JSON_URL = "https://churchyard.fedorapeople.org/orphans.json"

+ 

+ EPEL_RELEASES = [7, 8]

file modified
+7 -1
@@ -44,7 +44,13 @@ 

      OIDC_SCOPES = ['openid', 'email', 'profile']

  

      FORCE_CACHED_DATA = False

-     MAX_DB_AGE = 1800  # keep data cached for 30 minutes

+     MAX_DB_AGE = 7200  # keep data cached for 120 minutes

+     ACTIVITY_REQUIRED = 14 # cache users who used the service in at least last 14 days

+ 

+     PACKAGE_MAINTAINERS_JSON_URL = 'https://src.fedoraproject.org/extras/pagure_owner_alias.json'

+     ORPHANS_JSON_URL = 'https://churchyard.fedorapeople.org/orphans.json'

+ 

+     EPEL_RELEASES = [7, 8]

  

  

  class ProductionConfig(Config):

file modified
+77 -9
@@ -32,7 +32,7 @@ 

  

  from oraculum import app, login_manager, oidc, CACHE

  from oraculum.data_providers import PROVIDERS

- from oraculum.utils import fedocal, schedule, blockerbugs, meetbot, cache_utils, libkarma

+ from oraculum.utils import fedocal, schedule, blockerbugs, meetbot, cache_utils, libkarma, dashboard

  

  @app.before_first_request

  def register_cache_providers():
@@ -40,11 +40,27 @@ 

      Add providers to be cached here

      """

      app.logger.debug('Registering cache refreshers')

+     packages = []

      CACHE.register('landing_page', api_v1_landing_page)

-     CACHE.register('bodhi', api_v1_all_bodhi_updates)

+     CACHE.register('packages_owners_json', lambda PACKAGE_MAINTAINERS_JSON_URL=app.config['PACKAGE_MAINTAINERS_JSON_URL']:

+                     dashboard.get_json(PACKAGE_MAINTAINERS_JSON_URL))

+     CACHE.register('pagure_groups', dashboard.get_pagure_groups)

+     CACHE.register('orphans_json', lambda ORPHANS_JSON_URL=app.config['ORPHANS_JSON_URL']:

+                     dashboard.get_json(ORPHANS_JSON_URL))

+     CACHE.register('bodhi_updates', api_v1_all_bodhi_updates)

+     CACHE.register('bodhi_overrides', dashboard.get_overrides)

      for p_name, p_module in PROVIDERS.items():

          CACHE.register(p_name, p_module.get_actions)

  

+     users = dashboard.get_users_for_sync()

+     for user in users:

+         packages.extend(dashboard.get_packages(user, CACHE.get('packages_owners_json'), CACHE.get('pagure_groups'))["combined"])

+         CACHE.register('api_v1_packager_dashboard_data_%s' % user, lambda user=user: api_v1_packager_dashboard_data(user))

+ 

+     for package in packages:

+         CACHE.register('packager-dashboard_bugs_%s' % package, lambda package=package: dashboard.get_package_bugs(package))

+         CACHE.register('packager-dashboard_pull_requests_%s' % package, lambda package=package: dashboard.get_package_prs(package))

+ 

  # FIXME: move somewhere else?

  class User(UserMixin):

      def __init__(self, email):
@@ -53,23 +69,25 @@ 

  

  @app.route('/api/v1/libkarma/<release>', methods=['GET'])

  def route_api_v1_libkarma(release):

-     landing_page = CACHE.get('landing_page')

-     current_releases = [str(i) for i in [(landing_page["stable"] - 1), landing_page["stable"], landing_page["devel"]]]

+     current_releases = get_current_releases()

      if release in current_releases:

-         return jsonify(CACHE.get('bodhi')["F" + release])

+         return jsonify(CACHE.get('bodhi_updates')[release])

      if release.lower() == 'all':

-         return jsonify(CACHE.get('bodhi'))

+         return jsonify(CACHE.get('bodhi_updates'))

      return jsonify({'error': 'Release %s not found' % release}), 404

  

  def api_v1_all_bodhi_updates():

-     landing_page = CACHE.get('landing_page')

-     current_releases = [str(i) for i in [(landing_page["stable"] - 1), landing_page["stable"], landing_page["devel"]]]

+     current_releases = get_current_releases()

      updates = {}

      for single_release in current_releases:

          app.logger.debug("Appending updates for release: %s" % single_release)

-         updates["F" + single_release] = libkarma.get_updates(single_release, app.logger)

+         updates[single_release] = libkarma.get_updates(single_release, app.logger)

      return updates

  

+ def get_current_releases():

+     stable = CACHE.get('landing_page')["stable"]

+     return ["F" + str(i) for i in [(stable - 1), stable, (stable + 1)]] + ["EPEL-" + str(i) for i in app.config['EPEL_RELEASES']]

+ 

  @app.route('/api/v1/landing_page')

  def route_api_v1_landing_page():

      return jsonify(CACHE.get('landing_page'))
@@ -134,6 +152,56 @@ 

  

      return jsonify(resp)

  

+ @app.route('/api/v1/packager_dashboard/<user>', methods=['GET'])

+ def route_api_v1_packager_dashboard_data(user):

+     dashboard.update_user_access_time(user)

+     try:

+         return jsonify(CACHE.get('api_v1_packager_dashboard_data_%s' % user))

+     except cache_utils.RefresherNotRegisteredException:

+         CACHE.register('api_v1_packager_dashboard_data_%s' % user, lambda user=user: api_v1_packager_dashboard_data(user))

+         return jsonify(CACHE.get('api_v1_packager_dashboard_data_%s' % user))

+ 

+ def api_v1_packager_dashboard_data(user):

+     packages = dashboard.get_packages(user, CACHE.get('packages_owners_json'), CACHE.get('pagure_groups'))

+     # Just throw out empty lists and dicts for users without any packages

+     if len(packages["combined"]) == 0:

+         return {

+             'packages': [],

+             'group_packages': [],

+             'primary_packages': [],

+             'orphans': {},

+             'updates': {},

+             'overrides': {},

+             'pull_requests': {},

+             'bugs': {}

+         }

+     orphans = dashboard.get_orphans(packages, CACHE.get('orphans_json'))

+     updates = dashboard.get_updates(packages, CACHE.get('bodhi_updates'))

+     overrides = dashboard.get_user_overrides(packages, CACHE.get('bodhi_overrides'))

+     pull_requests = {}

+     bugzillas = {}

+     for package in packages["combined"]:

+         try:

+             pull_requests[package] = CACHE.get('packager-dashboard_pull_requests_%s' % package)

+             bugzillas[package] = CACHE.get('packager-dashboard_bugs_%s' % package)

+         except cache_utils.RefresherNotRegisteredException:

+             CACHE.register('packager-dashboard_bugs_%s' % package, lambda package=package: dashboard.get_package_bugs(package))

+             CACHE.register('packager-dashboard_pull_requests_%s' % package, lambda package=package: dashboard.get_package_prs(package))

+             pull_requests[package] = CACHE.get('packager-dashboard_pull_requests_%s' % package)

+             bugzillas[package] = CACHE.get('packager-dashboard_bugs_%s' % package)

+     return {

+         'packages': packages["combined"],

+         'group_packages': packages["group"],

+         'primary_packages': packages["primary"],

+         'orphans': orphans,

+         'updates': updates,

+         'overrides': overrides,

+         'pull_requests': pull_requests,

+         'bugs': bugzillas

+     }

+ 

+ 

+ 

  method_requests_mapping = {

      'GET': requests.get,

      'HEAD': requests.head,

@@ -0,0 +1,32 @@ 

+ #

+ # dashboard_users.py - Database model for Packager Dashboard user info

+ #

+ # Copyright 2020, Red Hat, Inc

+ #

+ # This program is free software; you can redistribute it and/or modify

+ # it under the terms of the GNU General Public License as published by

+ # the Free Software Foundation; either version 2 of the License, or

+ # (at your option) any later version.

+ #

+ # This program is distributed in the hope that it will be useful,

+ # but WITHOUT ANY WARRANTY; without even the implied warranty of

+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the

+ # GNU General Public License for more details.

+ #

+ # You should have received a copy of the GNU General Public License along

+ # with this program; if not, write to the Free Software Foundation, Inc.,

+ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

+ #

+ # Authors:

+ #   Frantisek Zatloukal <fzatlouk@redhat.com>

+ 

+ from oraculum import db

+ 

+ class DashboardUserData(db.Model):

+     id = db.Column(db.Integer, primary_key=True)

+     username = db.Column(db.Text, unique=True)

+     last_accessed = db.Column(db.DateTime, unique=False)

+ 

+     def __init__(self, username, last_accessed):

+         self.username = username

+         self.last_accessed = last_accessed

file modified
+17 -2
@@ -27,6 +27,11 @@ 

  from oraculum import app, db

  from oraculum.models.db_cache import CachedData

  

+ from sqlalchemy import exc

+ 

+ class RefresherNotRegisteredException(Exception):

+     pass

+ 

  class CachedObject():

      def __init__(self, time_created, data):

          self.time_created = time_created
@@ -53,7 +58,7 @@ 

  

      def get(self, what):

          if what not in self._refreshers:

-             raise Exception

+             raise RefresherNotRegisteredException

  

          if self._new_enough(self._local_cache.get(what)):

              app.logger.debug("_local_cache cache hit on %s" % what)
@@ -92,7 +97,17 @@ 

              row = CachedData(what, json.dumps(data))

              db.session.add(row)

  

-         db.session.commit()

+         try:

+             db.session.commit()

+         except exc.IntegrityError:

+             # User probably refreshed page during loading and we hit IntegrityError

+             # Try to rollback queued changes

+             db.session.rollback()

+             if what not in self._local_cache:

+                 # Refresh might happen before writing out to _local_cache, make sure we save data there too

+                 row = CachedData.query.filter_by(provider=what).first()

+                 data = row.data

+ 

          self._local_cache[what] = CachedObject(row.time_created, data)

  

  if __name__ == "__main__":

@@ -0,0 +1,340 @@ 

+ #

+ # dashboard.py - Utilities to parse data sources for packager dashboard

+ #

+ # Copyright 2020, Red Hat, Inc

+ #

+ # This program is free software; you can redistribute it and/or modify

+ # it under the terms of the GNU General Public License as published by

+ # the Free Software Foundation; either version 2 of the License, or

+ # (at your option) any later version.

+ #

+ # This program is distributed in the hope that it will be useful,

+ # but WITHOUT ANY WARRANTY; without even the implied warranty of

+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the

+ # GNU General Public License for more details.

+ #

+ # You should have received a copy of the GNU General Public License along

+ # with this program; if not, write to the Free Software Foundation, Inc.,

+ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

+ #

+ # Authors:

+ #   Frantisek Zatloukal <fzatlouk@redhat.com>

+ #   Josef Skladanka <jskladan@redhat.com>

+ 

+ import json

+ import datetime

+ import requests

+ import hawkey

+ import bugzilla

+ 

+ from hawkey._hawkey import ValueException

+ from bodhi.client.bindings import BodhiClient

+ from urllib3.util.retry import Retry

+ from requests.adapters import HTTPAdapter

+ 

+ from oraculum import app, db

+ from oraculum.models.dashboard_users import DashboardUserData

+ 

+ def update_user_access_time(user):

+     """

+     Updates user last_accessed with current timestamp

+     """

+     row = DashboardUserData.query.filter_by(username=user).first()

+     if not row:

+         row = DashboardUserData(user, datetime.datetime.utcnow())

+         db.session.add(row)

+     else:

+         row.last_accessed = datetime.datetime.utcnow()

+     db.session.commit()

+ 

+ def get_users_for_sync():

+     """

+     Returns list of usernames to be included in sync if conditions specified in settings.py are met

+     """

+     now = datetime.datetime.utcnow() - datetime.timedelta(days=app.config['ACTIVITY_REQUIRED'])

+     row = DashboardUserData.query.filter(DashboardUserData.last_accessed >= now)

+     return [user.username for user in row]

+ 

+ def name_in_nevra(name, nevra):

+     """

+     Checks if name/nevra matches

+     """

+     # Work around package naming bug in known "bad" packages

+     if "fedora-obsolete-packages" in nevra:

+         if "fedora-obsolete-packages" in name:

+             return True

+         return False

+     if "epel-rpm-macros" in nevra:

+         if "epel-rpm-macros" in name:

+             return True

+         return False

+ 

+     try:

+         if name == hawkey.split_nevra(nevra).name:

+             return True

+         else:

+             return False

+     except ValueException:

+         app.logger.error("Hawkey/bodhi issue, skipping name/nevra compare, FIX THIS!!! (%s, %s)" % (name, nevra))

+         return False

+ 

+ def release_from_nevra(nevra):

+     """

+     Returns fcXX from nevra

+     """

+     split = nevra.split(".")[-1]

+     if len(split) > 1:

+         return split

+     return "Unknown Release"

+ 

+ def process_update(update):

+     """

+     Cleans up single update dictionary to contain only data frontend needs

+     """

+     return {

+         "pretty_name": update["title"],

+         "updateid": update["alias"],

+         "submission_date": update["date_submitted"],

+         "release": release_from_nevra(update["title"]),

+         "url": update["url"],

+         "status": update["status"],

+         "karma": update["karma"],

+         "comments": len(update["comments"])

+     }

+ 

+ def process_override(override):

+     """

+     Cleans up single override dictionary to contain only data frontend needs

+     """

+     return {

+         "pretty_name": override["nvr"],

+         "url": "https://bodhi.fedoraproject.org/overrides/" + override["nvr"],

+         "submission_date": override["submission_date"],

+         "expiration_date": override["expiration_date"],

+         "release": release_from_nevra(override["nvr"])

+     }

+ 

+ def get_orphans(packages, orphans_json):

+     """

+     Returns dict of dictionaries (all packages), where orphanned package has orphanned == True

+     """

+     orphans = {}

+     for package in packages:

+         orphans[package] = {

+             "orphanned": False,

+             "orphanned_since": None

+         }

+         if package in orphans_json:

+             orphans[package] = {

+                 "orphanned": True,

+                 "orphanned_since": orphans_json[package]

+             }

+     return orphans

+ 

+ def get_json(json_url):

+     """

+     Returns json data from provided url

+     """

+     session = requests.Session()

+     retries = Retry(total=5,

+                     backoff_factor=0.1,

+                     status_forcelist=[ 500, 502, 503, 504 ])

+     session.mount('https://', HTTPAdapter(max_retries=retries))

+     resp = session.get(json_url)

+     return json.loads(resp.text)

+ 

+ 

+ def get_pagure_groups():

+     """

+     Returns dictionary mapping all packager groups in pagure, it's members and packages

+     "group_name" : {

+         "users": [user_a, ...],

+         "packages": [package_a, ...]

+     }

+     """

+     groups_users = {}

+     resp = get_json("https://src.fedoraproject.org/api/0/groups?per_page=100") # TODO: Hanlde pagination properly

+ 

+     for group in resp["groups"]:

+         app.logger.debug("Checking out Pagure group %s" % group)

+         group_resp = get_json("https://src.fedoraproject.org/api/0/group/%s?projects=1&acl=commit" % group)

+         try:

+             groups_users[group] = {

+                 "users": group_resp["members"],

+                 "packages": [project["name"] for project in group_resp["projects"]]

+             }

+         except TypeError:

+             app.logger.error("Skipped Pagure group %s because of an error" % group)

+             continue

+     return groups_users

+ 

+ def get_user_group_packages(user, groups_map):

+     """

+     Returns list of packages user owns through a group

+     """

+     group_packages = []

+     for group in groups_map:

+         if user in groups_map[group]["users"]:

+             group_packages.extend(groups_map[group]["packages"])

+     return group_packages

+ 

+ def get_packages(user, pkg_owners_map, groups_map):

+     """

+     Returns all packages owned by user (including those owned through a group)

+     returns dict of lists: "combined" - returns all packages, owned both directly or through group

+                            "group" - returns packages owned only through group

+                            "primary" - returns packages owned only directly

+     """

+     # packages_owners_map['rpms']['some_package_name'] contains list of 'some_package_name' maintainers

+     if user == "orphan": # blacklist orphan user which has lots of unnecessary packages and would choke up our servers

+         return {

+             "primary": [],

+             "group": [],

+             "combined": []

+         }

+     packages = {}

+     packages["primary"] = [package for package in pkg_owners_map['rpms'] if user in pkg_owners_map['rpms'][package]]

+     packages["group"] = get_user_group_packages(user, groups_map)

+     packages["combined"] = packages["primary"].copy()

+     packages["combined"].extend(x for x in packages["group"] if x not in packages["primary"])

+     return packages

+ 

+ def get_updates(packages, raw_updates):

+     """

+     Gets list of user owned packages and libkarma dump of all updates

+     Returns dict of user owned packages and updates for them

+     package_name: {

+         "pretty_name": update["title"],

+         "updateid": update["alias"],

+         "submission_date": update["date_submitted"],

+         "release": release_from_nevra(update["title"]),

+         "url": update["url"],

+         "status": update["status"],

+         "karma": update["karma"],

+         "comments": len(update["comments"])

+     }

+     """

+     if len(packages) == 0:

+         return {}

+     data = {}

+     # Prepare dict items for each package

+     for package in packages:

+         data[package] = []

+ 

+     # raw_updates["Fxx"][0...n]["builds"][0...n]["nvr"] = 'package_name-version-release'

+     # raw_updates["Fxx"][0...n]["alias"] = FEDORA-YYYY-UPDATE_ID

+     for release in raw_updates:

+         for package in packages:

+             present_updates = set() # helper set to deduplicate updates containing more than one package

+             for update in raw_updates[release]:

+                 for build in update["builds"]:

+                     if name_in_nevra(package, build["nvr"]) and update["alias"] not in present_updates:

+                         data[package].append(process_update(update))

+                         present_updates.add(update["alias"])

+ 

+     return data

+ 

+ def get_package_prs(package):

+     """

+     Returns all open Pull Requests for a single package

+     """

+     data = []

+     resp_package_prs = get_json("https://src.fedoraproject.org/api/0/rpms/%s/pull-requests" % package)

+     if not "requests" in resp_package_prs:

+         app.logger.error("Skipping PRs from package %s because Pagure returned invalid data" % package)

+         return [] # Return early if pagure sent invalid data

+     for request in resp_package_prs["requests"]:

+         if request["status"] == "Open":

+             data.append({

+                 "title": request["title"],

+                 "author": request["user"]['name'],

+                 "comments": len(request["comments"]),

+                 "date_created": str(datetime.datetime.fromtimestamp(int(request["date_created"]))),

+                 "last_updated": str(datetime.datetime.fromtimestamp(int(request["last_updated"]))),

+                 "ci_status": None, # Awaiting pagure api integration

+                 "url": "https://src.fedoraproject.org/rpms/%s/pull-request/%s" % (package, request["id"])

+             })

+     return data

+ 

+ def get_package_bugs(package):

+     """

+     Returns all open Bugs for a single package

+     """

+     data = []

+     bzapi = bugzilla.Bugzilla("bugzilla.redhat.com")

+ 

+     query = bzapi.url_to_query("https://bugzilla.redhat.com/buglist.cgi?bug_status=NEW&bug_status=__open__&"

+                                 "classification=Fedora&product=Fedora&component=%s" % package)

+     query["include_fields"] = ["id", "summary", "version", "severity", "status", "last_change_time", "creation_time", "keywords"]

+     bugs = bzapi.query(query)

+     if len(bugs) == 0:

+         return []

+     for bug in bugs:

+         data.append({

+             "title": bug.summary,

+             "bug_id": bug.id,

+             "severity": bug.severity,

+             "status": bug.status,

+             "modified": str(bug.last_change_time),

+             "reported": str(bug.creation_time),

+             "release": bug.version,

+             "keywords": bug.keywords,

+             "url": "https://bugzilla.redhat.com/%s" % bug.id

+         })

+     return data

+ 

+ def get_overrides():

+     """

+     Returns list of all active buildroot overrides

+     """

+     bc = BodhiClient(username="oraculum",

+                      useragent="Fedora Easy Karma/GIT",

+                      retries=3)

+ 

+     query_args = {"expired": False,

+                  "rows_per_page": 50,

+                  }

+ 

+     overrides = []

+     try:

+         # since bodhi has a query limit but multiple pages, get ALL of the

+         # updates before starting to process

+         result = bc.list_overrides(**query_args)

+         overrides.extend(result['overrides'])

+         while result.page < result.pages:

+             next_page = result['page'] + 1

+             app.logger.debug("Fetching overrides page {} of {}".format(

+                 next_page, result['pages']))

+             result = bc.list_overrides(page=next_page, **query_args)

+             app.logger.debug("Queried Bodhi page %s" % next_page)

+             overrides.extend(result['overrides'])

+     # There is no clear indication which Exceptions bc.query() might

+     # throw, therefore catch all (python-fedora-0.3.32.3-1.fc19)

+     except Exception as e:

+         app.logger.error("Error while querying Bodhi: {0}".format(e))

+ 

+     return overrides

+ 

+ def get_user_overrides(packages, raw_overrides):

+     """

+     Returns all active buildroot overrides for single user

+     Formatted as dict of dicts:

+     package_name: {

+         "pretty_name": override["nvr"],

+         "url": "https://bodhi.fedoraproject.org/overrides/" + override["nvr"],

+         "submission_date": override["submission_date"],

+         "expiration_date": override["expiration_date"],

+         "release": release_from_nevra(override["nvr"])

+     }

+     """

+     data = {}

+     for package in packages:

+         data[package] = []

+     for override in raw_overrides:

+         for package in packages:

+             if name_in_nevra(package, override["nvr"]):

+                 data[package].append(process_override(override))

+     return data

+ 

+ if __name__ == "__main__":

+     import ipdb; ipdb.set_trace()

file modified
+2 -2
@@ -8,8 +8,8 @@ 

      if not logger:

          logger = logging.getLogger(__name__)

  

-     query_args = {"release": "F" + str(release),

-                     "rows_per_page": 20,

+     query_args = {"release": str(release),

+                     "rows_per_page": 50,

                      }

      if pending:

          query_args["request"] = "testing"

Provides api/v1/packager_dashboard/<user> endpoint

Missing:
- FTBFS and FTI reporting

Postponed for later inclusion:
- Modules and Flatpaks support in bodhi data source

1 new commit added

  • Endpoint rename dashboard to packager_dashboard
3 years ago

2 new commits added

  • Remove forgotten code from dashboard_users model
  • Move around few pieces of code
3 years ago

2 new commits added

  • Exit early for users without any packages
  • Dashboard: Blacklist "orphan" user
3 years ago

Instead of query.all() you could (should) use filter() to select the users directly in DB, instead of doing post-processing here. Have a look at https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.filter

I guess this will be used later on?

if the intent here is to do a copy of the keys out of all_packages['rpms'] dict, consider list(all_packages['rpms'].keys())

In the future, it might be worth using some retry-logic. Requests should be able to do that on its own: https://stackoverflow.com/a/15431343

You could query for multiple packages at once, using: ... &component=X&component=Y&component=Z Not sure it makes sense here, but might be worth using sometimes in the future

I feel like this should be converted to a standalone cache 'refresher', and the results cached, given the dashboard.get_updates() method is basically four nested for-cycles.

I'd like to see all_packages changed to something more descriptive, but this really is a nitpick

The expected format of raw_updates should be mentioned in the docstring or (maybe better yet) just get rid of the param, and do raw_updates = CACHE.get('bodhi') in the code directly. I don't see anybody replicating that structure just for kicks :)

Please add a comment explaining why this is needed. I'd like to see this be nicer/less weird, but I don't have a better idea yet.

This is used by register_cache_providers() to get all users with packages:

...
CACHE.register('packages_owners_json', dashboard.packages_json)
... 

Mea culpa, for some reason, I missed it yesterday. THX

7 new commits added

  • Dashboard: Move user activity threshold to config file
  • Dashboard: List oprhanned packages
  • Dashboard: Filter users for sync directly in query
  • Dashboard: Try to workaround exception on page refresh during load without cache
  • Dashboard: cleaner user cache management
  • Dashboard: Bugzilla: Include bug keywords
  • PR feedback
3 years ago

1 new commit added

  • Dashboard: EPEL Support
3 years ago

1 new commit added

  • Dashboard: Support for bodhi overrides parsing
3 years ago

1 new commit added

  • Dashboard: Fetch severity from bugzilla and include bugid in the API endpoint
3 years ago

1 new commit added

  • Dashboard: Add submit date and release to updates
3 years ago

1 new commit added

  • Dashboard: Add More Bugzilla data, query all open bugs
3 years ago

1 new commit added

  • Unify some data formats
3 years ago

1 new commit added

  • Dashboard: Add more data to pull requests
3 years ago

1 new commit added

  • Dashboard: Support packager groups
3 years ago

1 new commit added

  • Drop requests_futures for now
3 years ago

1 new commit added

  • Code Style
3 years ago

1 new commit added

  • Code cleanup
3 years ago

1 new commit added

  • Add retry support to requests
3 years ago

1 new commit added

  • Documentation
3 years ago

1 new commit added

  • Code Style
3 years ago

1 new commit added

  • Update data fromat for package-less users to match the format of users with packages
3 years ago

1 new commit added

  • Overrides is list containing lists of dicts
3 years ago

1 new commit added

  • Imports
3 years ago

3 new commits added

  • Support to query primary/group packages separately
  • Reorder cycles in get_updates() a little
  • Skip getting PRs for package if Pagure returned invalid data
3 years ago

1 new commit added

  • Cleanup
3 years ago

1 new commit added

  • Saner defaults for now
3 years ago

Pull-Request has been closed by frantisekz

3 years ago