#1512 EOL repositories page for user (in opposite to project)
Merged 3 years ago by praiskup. Opened 3 years ago by frostyx.
copr/ frostyx/copr user-repositories  into  master

@@ -0,0 +1,30 @@ 

+ """

+ Add reviewed_outdated_chroot table

+ 

+ Revision ID: 9b7211be5017

+ Revises: de903581465c

+ Create Date: 2020-10-08 11:27:27.588111

+ """

+ 

+ import sqlalchemy as sa

+ from alembic import op

+ 

+ 

+ revision = '9b7211be5017'

+ down_revision = '63db6872060f'

+ 

+ def upgrade():

+     op.create_table('reviewed_outdated_chroot',

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

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

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

+     sa.ForeignKeyConstraint(['copr_chroot_id'], ['copr_chroot.id'], ondelete='CASCADE'),

+     sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),

+     sa.PrimaryKeyConstraint('id')

+     )

+     op.create_index(op.f('ix_reviewed_outdated_chroot_user_id'), 'reviewed_outdated_chroot', ['user_id'], unique=False)

+ 

+ 

+ def downgrade():

+     op.drop_index(op.f('ix_reviewed_outdated_chroot_user_id'), table_name='reviewed_outdated_chroot')

+     op.drop_table('reviewed_outdated_chroot')

@@ -1301,6 +1301,8 @@ 

  class CoprChrootExtend(FlaskForm):

      extend = wtforms.StringField("Chroot name")

      expire = wtforms.StringField("Chroot name")

+     ownername = wtforms.HiddenField("Owner name")

+     projectname = wtforms.HiddenField("Project name")

  

  

  class CoprLegalFlagForm(FlaskForm):

@@ -273,7 +273,7 @@ 

      def get_coprs_permissible_by_user(cls, user):

          coprs = CoprsLogic.filter_without_group_projects(

                      CoprsLogic.get_multiple_owned_by_username(

-                         flask.g.user.username, include_unlisted_on_hp=False)).all()

+                         user.username, include_unlisted_on_hp=False)).all()

  

          for group in user.user_groups:

              coprs.extend(CoprsLogic.get_multiple_by_group_id(group.id).all())

@@ -0,0 +1,90 @@ 

+ import flask

+ from datetime import datetime, timedelta

+ from coprs import db

+ from coprs import app

+ from coprs import models

+ from coprs.logic.complex_logic import ComplexLogic

+ from coprs.logic.coprs_logic import CoprChrootsLogic

+ 

+ 

+ class OutdatedChrootsLogic:

+     @classmethod

+     def has_not_reviewed(cls, user):

+         """

+         Does a user have some projects with newly outdated chroots that he

+         hasn't reviewed yet?

+         """

+         projects = ComplexLogic.get_coprs_permissible_by_user(user)

+         projects_ids = [p.id for p in projects]

+         period = app.config["EOL_CHROOTS_NOTIFICATION_PERIOD"]

+         now = datetime.now()

+         soon = now + timedelta(days=period)

+ 

+         reviewed = [x.copr_chroot_id for x in cls.get_all_reviews(user).all()]

+         return bool((models.CoprChroot.query

+                      .filter(models.CoprChroot.copr_id.in_(projects_ids))

+                      .filter(models.CoprChroot.delete_after != None)

+                      .filter(models.CoprChroot.delete_after <= soon)

+                      .filter(models.CoprChroot.delete_after > now)

+                      .filter(models.CoprChroot.id.notin_(reviewed))

+                      .first()))

+ 

+     @classmethod

+     def get_all_reviews(cls, user):

+         """

+         Query all outdated chroots that a user has already seen

+         """

+         return (models.ReviewedOutdatedChroot.query

+                 .filter(models.ReviewedOutdatedChroot.user_id == user.id))

+ 

+     @classmethod

+     def make_review(cls, user):

+         """

+         A `user` declares that he has seen and reviewed all outdated chroots in

+         all of his projects (i.e. this method creates `ReviewedOutdatedChroot`

+         results for all of them)

+         """

+         reviews = {x.copr_chroot_id for x in cls.get_all_reviews(user)}

+         for copr in ComplexLogic.get_coprs_permissible_by_user(user):

+             for chroot in copr.outdated_chroots:

+                 if chroot.id in reviews:

+                     continue

+ 

+                 period = app.config["EOL_CHROOTS_NOTIFICATION_PERIOD"]

+                 if chroot.delete_after_days > period:

+                     continue

+ 

+                 review = models.ReviewedOutdatedChroot(

+                     user_id=user.id,

+                     copr_chroot_id=chroot.id,

+                 )

+                 db.session.add(review)

+ 

+     @classmethod

+     def extend(cls, copr_chroot):

+         """

+         A `user` decided to extend the preservation period for some EOL chroot

+         """

+         delete_after_days = app.config["DELETE_EOL_CHROOTS_AFTER"] + 1

+         cls._update_copr_chroot(copr_chroot, delete_after_days)

+         (models.ReviewedOutdatedChroot.query

+          .filter(models.ReviewedOutdatedChroot.copr_chroot_id

+                  == copr_chroot.id)).delete()

+ 

+     @classmethod

+     def expire(cls, copr_chroot):

+         """

+         A `user` decided to expire some EOL chroot,

+         i.e. its data should be deleted ASAP

+         """

+         delete_after_days = 0

+         cls._update_copr_chroot(copr_chroot, delete_after_days)

+ 

+     @classmethod

+     def _update_copr_chroot(cls, copr_chroot, delete_after_days):

+         delete_after_timestamp = (

+             datetime.now()

+             + timedelta(days=delete_after_days)

+         )

+         CoprChrootsLogic.update_chroot(flask.g.user, copr_chroot,

+                                        delete_after=delete_after_timestamp)

@@ -2047,6 +2047,31 @@ 

      what = db.Column(db.String(100), nullable=False, primary_key=True)

  

  

+ class ReviewedOutdatedChroot(db.Model):

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

+ 

+     user_id = db.Column(

+         db.Integer,

+         db.ForeignKey("user.id"),

+         nullable=False,

+         index=True,

+     )

+     copr_chroot_id = db.Column(

+         db.Integer,

+         db.ForeignKey("copr_chroot.id", ondelete="CASCADE"),

+         nullable=False,

+     )

+ 

+     user = db.relationship(

+         "User",

+         backref=db.backref("reviewed_outdated_chroots"),

+     )

+     copr_chroot = db.relationship(

+         "CoprChroot",

+         backref=db.backref("reviewed_outdated_chroots")

+     )

+ 

+ 

  @listens_for(DistGitInstance.__table__, 'after_create')

  def insert_fedora_distgit(*args, **kwargs):

      db.session.add(DistGitInstance(

@@ -214,7 +214,7 @@ 

        <span class="pficon pficon-close"></span>

      </button>

      <span class="pficon {{ alert_icon_map.get(type, 'pficon-info') }}"></span>

-   {{ message }}

+   {{ message |safe }}

    </div>

  {% endmacro %}

  

@@ -0,0 +1,85 @@ 

+ {% extends "coprs/show.html" %}

+ {% from "_helpers.html" import copr_url, render_form_errors %}

+ {% block title %}Outdated repositories{% endblock %}

+ {% block header %}Outdated repositories{% endblock %}

+ {% block breadcrumbs %}

+ <ol class="breadcrumb">

+     <li>

+         <a href="{{ url_for('coprs_ns.coprs_show') }}">Home</a>

+     </li>

+     <li>

+         {% if owner.at_name is defined %}

+         <a href="{{ url_for('groups_ns.list_projects_by_group', group_name=owner.name) }}">{{ owner.at_name }}</a>

+         {% else %}

+         <a href="{{ url_for('coprs_ns.coprs_by_user', username=owner.name) }}">{{ owner.name }}</a>

+         {% endif %}

+     </li>

+     <li class="active">

+         {{ owner.name }} repositories

+     </li>

+ </ol>

+ {% endblock %}

+ 

+ 

+ {% block content %}

+ <h1 style="margin-bottom:22px;margin-top:22px">Outdated repositories</h1>

+ <p>

+     These projects have available repositories for at least some outdated chroots.

+     Unless you periodically take an action and extend the time for they should be preserved,

+     they are going to be removed in the future. Please see

+     <a href="#">Outdated repos removal policy</a>

+     in the Copr Documentation.

+ </p>

+ 

+ {% if form %}

+ {{ render_form_errors(form=form) }}

+ {% endif %}

+ 

+ {% for project in projects %}

+ {% if project.outdated_chroots %}

+ <form action="" method="POST">

+     <h3><a href="{{ copr_url('coprs_ns.copr_detail', project) }}">{{ project.full_name }}</a></h3>

+     <table class="table table-bordered">

+         <thead>

+             <tr>

+                 <th class="col-md-2">Release</th>

+                 <th class="col-md-2">Architecture</th>

+                 <th>Remaining time</th>

+                 <th class="col-md-3">Action</th>

+             </tr>

+         </thead>

+         <tbody>

+             {% for chroot in project.outdated_chroots %}

+             <tr>

+                 <td>{{ chroot.mock_chroot.os.capitalize() }}</td>

+                 <td>{{ chroot.mock_chroot.arch }}</td>

+                 <td>

+                     {% if not chroot.delete_after_days %}

+                     To be removed in next cleanup

+                     {% else %}

+                     {% set color = 'danger' if chroot.delete_after_days < 20 else 'secondary' %}

+                     <span class="text-{{ color }}">

+                         {{ chroot.delete_after_days }} days

+                     </span>

+                     {% endif %}

+                 </td>

+                 <td>

+                     <button name="extend" class="btn btn-primary" type="submit" value="{{ chroot.mock_chroot.name }}">

+                         Extend

+                     </button>

+                     <button name="expire" class="btn btn-danger" type="submit" value="{{ chroot.mock_chroot.name }}"

+                         {% if not chroot.delete_after_days %} disabled="disabled" {% endif %}>Expire now

+                     </button>

+                 </td>

+             </tr>

+             {% endfor %}

+         </tbody>

+     </table>

+ 

+     <input type="hidden" name="ownername" value="{{ project.owner_name }}">

+     <input type="hidden" name="projectname" value="{{ project.name }}">

+ </form>

+ {% endif %}

+ {% endfor %}

+ 

+ {% endblock %}

@@ -1,6 +1,22 @@ 

  # coding: utf-8

  

  import flask

+ from coprs.logic.outdated_chroots_logic import OutdatedChrootsLogic

+ 

+ 

+ def flash_outdated_chroots_warning():

+     if not flask.g.user:

+         return

+ 

+     if not OutdatedChrootsLogic.has_not_reviewed(flask.g.user):

+         return

+ 

+     url = flask.url_for("user_ns.repositories", _external=True)

+     flask.flash("Some of the chroots you maintain are <b>newly marked EOL</b>, "

+                 " and will be removed in the future. Please review "

+                 "<a href='{0}'>{0}</a> to hide this warning."

+                 .format(url), "warning")

  

  

  coprs_ns = flask.Blueprint("coprs_ns", __name__, url_prefix="/coprs")

+ coprs_ns.before_request(flash_outdated_chroots_warning)

@@ -35,6 +35,7 @@ 

  from coprs.mail import send_mail, LegalFlagMessage, PermissionRequestMessage, PermissionChangeMessage

  

  from coprs.logic.complex_logic import ComplexLogic

+ from coprs.logic.outdated_chroots_logic import OutdatedChrootsLogic

  

  from coprs.views.misc import (login_required, page_not_found, req_with_copr,

                                generic_error, req_with_copr_dir)
@@ -674,18 +675,28 @@ 

  @login_required

  @req_with_copr

  def copr_repositories_post(copr):

+     return process_copr_repositories(copr, render_copr_repositories)

+ 

+ 

+ def process_copr_repositories(copr, on_success):

+     form = forms.CoprChrootExtend()

+     if not copr and not (form.ownername.data or form.projectname.data):

+         raise ValidationError("Ambiguous to what project the chroot belongs")

+ 

+     if not copr:

+         copr = ComplexLogic.get_copr_by_owner_safe(form.ownername.data,

+                                                    form.projectname.data)

      if not flask.g.user.can_edit(copr):

          flask.flash("You don't have access to this page.", "error")

          return flask.redirect(url_for_copr_details(copr))

  

-     form = forms.CoprChrootExtend()

      if form.extend.data:

-         delete_after_days = app.config["DELETE_EOL_CHROOTS_AFTER"] + 1

+         update_fun = OutdatedChrootsLogic.extend

          chroot_name = form.extend.data

          flask.flash("Repository for {} will be preserved for another {} days from now"

                      .format(chroot_name, app.config["DELETE_EOL_CHROOTS_AFTER"]))

      elif form.expire.data:

-         delete_after_days = 0

+         update_fun = OutdatedChrootsLogic.expire

          chroot_name = form.expire.data

          flask.flash("Repository for {} is scheduled to be removed."

                      "If you changed your mind, click 'Extend` to revert your decision."
@@ -694,11 +705,9 @@ 

          raise ValidationError("Copr chroot needs to be either extended or expired")

  

      copr_chroot = coprs_logic.CoprChrootsLogic.get_by_name(copr, chroot_name, active_only=False).one()

-     delete_after_timestamp = datetime.datetime.now() + datetime.timedelta(days=delete_after_days)

-     coprs_logic.CoprChrootsLogic.update_chroot(flask.g.user, copr_chroot,

-                                                delete_after=delete_after_timestamp)

+     update_fun(copr_chroot)

      db.session.commit()

-     return render_copr_repositories(copr)

+     return on_success(copr)

  

  

  @coprs_ns.route("/id/<copr_id>/createrepo/", methods=["POST"])

@@ -1,12 +1,14 @@ 

  import flask

- from . import user_ns

  from coprs import app, db, models, helpers

  from coprs.forms import PinnedCoprsForm

  from coprs.views.misc import login_required

  from coprs.logic.users_logic import UsersLogic, UserDataDumper

  from coprs.logic.builds_logic import BuildsLogic

  from coprs.logic.complex_logic import ComplexLogic

- from coprs.logic.coprs_logic import CoprsLogic, PinnedCoprsLogic

+ from coprs.logic.coprs_logic import PinnedCoprsLogic

+ from coprs.logic.outdated_chroots_logic import OutdatedChrootsLogic

+ from coprs.views.coprs_ns.coprs_general import process_copr_repositories

+ from . import user_ns

  

  

  def render_user_info(user):
@@ -93,3 +95,28 @@ 

      db.session.commit()

  

      return flask.redirect(url_on_success)

+ 

+ 

+ @user_ns.route("/repositories/")

+ @login_required

+ def repositories():

+     return render_repositories()

+ 

+ 

+ def render_repositories(*_args, **_kwargs):

+     owner = flask.g.user

+     projects = ComplexLogic.get_coprs_permissible_by_user(owner)

+     projects = sorted(projects, key=lambda p: p.full_name)

+     OutdatedChrootsLogic.make_review(owner)

+     db.session.commit()

+     return flask.render_template("repositories.html",

+                                  tasks_info=ComplexLogic.get_queue_sizes(),

+                                  graph=BuildsLogic.get_small_graph_data('30min'),

+                                  owner=owner,

+                                  projects=projects)

+ 

+ 

+ @user_ns.route("/repositories/", methods=["POST"])

+ @login_required

+ def repositories_post():

+     return process_copr_repositories(copr=None, on_success=render_repositories)

@@ -0,0 +1,131 @@ 

+ import flask

+ import pytest

+ from datetime import datetime, timedelta

+ from tests.coprs_test_case import CoprsTestCase, new_app_context

+ from coprs.logic.outdated_chroots_logic import OutdatedChrootsLogic

+ from coprs.logic.complex_logic import ComplexLogic

+ 

+ 

+ class TestOutdatedChrootsLogic(CoprsTestCase):

+ 

+     @new_app_context

+     @pytest.mark.usefixtures("f_users", "f_coprs", "f_mock_chroots", "f_db")

+     def test_outdated_chroots_simple(self):

+         # Make sure, that there are no unreviewed outdated chroots yet

+         assert not OutdatedChrootsLogic.has_not_reviewed(self.u2)

+ 

+         # Once a chroot is EOLed, we should see that a user has something unreviewed

+         self.c2.copr_chroots[0].delete_after = datetime.now() + timedelta(days=10)

+         assert OutdatedChrootsLogic.has_not_reviewed(self.u2)

+ 

+         # User just reviewed his outdated chroots

+         # (e.g. by visiting the /repositories page)

+         OutdatedChrootsLogic.make_review(self.u2)

+         assert not OutdatedChrootsLogic.has_not_reviewed(self.u2)

+ 

+     @new_app_context

+     @pytest.mark.usefixtures("f_users", "f_groups", "f_group_copr", "f_db")

+     def test_outdated_chroots_group(self):

+         # Make sure that a user is a part of a group

+         self.u3.openid_groups = {"fas_groups": [self.g1.fas_name]}

+         assert self.u3.can_build_in_group(self.g1)

+ 

+         # Make sure a project is owned by a group but not by our user himself

+         permissible = ComplexLogic.get_coprs_permissible_by_user(self.u3)

+         assert self.gc2 in permissible

+         assert self.gc2.user != self.u3

+ 

+         # Make sure, that there are no unreviewed outdated chroots yet

+         assert not OutdatedChrootsLogic.has_not_reviewed(self.u3)

+ 

+         # Once a chroot is EOLed, we should see that a user has something unreviewed

+         self.gc2.copr_chroots[0].delete_after = datetime.now() + timedelta(days=10)

+         assert OutdatedChrootsLogic.has_not_reviewed(self.u3)

+ 

+         # User just reviewed his outdated chroots

+         # (e.g. by visiting the /repositories page)

+         OutdatedChrootsLogic.make_review(self.u3)

+         assert not OutdatedChrootsLogic.has_not_reviewed(self.u3)

+ 

+         # Only a `self.u3` did the review, other group members still has

+         # unreviewed chroots

+         self.u2.openid_groups = {"fas_groups": [self.g1.fas_name]}

+         assert OutdatedChrootsLogic.has_not_reviewed(self.u2)

+ 

+     @new_app_context

+     @pytest.mark.usefixtures("f_users", "f_coprs", "f_mock_chroots", "f_db")

+     def test_outdated_chroots_flash_not_immediately(self):

+         # Make sure, that there are no unreviewed outdated chroots yet

+         assert not OutdatedChrootsLogic.has_not_reviewed(self.u2)

+ 

+         # A chroot was just marked as EOL, we don't want to see any warning just yet

+         self.c2.copr_chroots[0].delete_after = datetime.now() + timedelta(days=180)

+         assert not OutdatedChrootsLogic.has_not_reviewed(self.u2)

+ 

+         # Once around half of the preservation period for an EOLed chroot

+         # runned out, we want to start showing some notification

+         self.c2.copr_chroots[0].delete_after = datetime.now() + timedelta(days=80)

+         assert OutdatedChrootsLogic.has_not_reviewed(self.u2)

+ 

+     @new_app_context

+     @pytest.mark.usefixtures("f_users", "f_coprs", "f_mock_chroots", "f_db")

+     def test_outdated_chroots_flash_not_expired(self):

+         # A preservation period is gone and the chroot is scheduled to be

+         # deleted ASAP. At this point, user has no chance to extend it anymore,

+         # so make sure we don't notify him about such chroots

+         self.c2.copr_chroots[0].delete_after = datetime.now() - timedelta(days=1)

+         assert not OutdatedChrootsLogic.has_not_reviewed(self.u2)

+ 

+     @new_app_context

+     @pytest.mark.usefixtures("f_users", "f_coprs", "f_mock_chroots", "f_db")

+     def test_outdated_chroots_review_only_after_some_time(self):

+         # Make sure that `self.u2` hasn't reviewed anything yet

+         assert not OutdatedChrootsLogic.get_all_reviews(self.u2).all()

+ 

+         # Some chroots are going to be deleted, with various times remaining

+         self.c2.copr_chroots[0].delete_after = datetime.now() + timedelta(days=35)

+         self.c2.copr_chroots[1].delete_after = datetime.now() + timedelta(days=160)

+         self.c3.copr_chroots[0].delete_after = datetime.now() + timedelta(days=80)

+         self.c3.copr_chroots[1].delete_after = datetime.now() + timedelta(days=50)

+ 

+         # User just reviewed his outdated chroots

+         # (e.g. by visiting the /repositories page)

+         OutdatedChrootsLogic.make_review(self.u2)

+         reviews = OutdatedChrootsLogic.get_all_reviews(self.u2).all()

+ 

+         # Make sure that not all EOL chroots have been reviewed. We want to

+         # review only those with a significant portion of the preservation

+         # period already run out.

+         assert len(reviews) == 3

+         assert self.c2.copr_chroots[1] not in [x.copr_chroot for x in reviews]

+         assert {x.copr_chroot for x in reviews} == {

+             self.c2.copr_chroots[0],

+             self.c3.copr_chroots[0],

+             self.c3.copr_chroots[1],

+         }

+ 

+     @new_app_context

+     @pytest.mark.usefixtures("f_users", "f_coprs", "f_mock_chroots", "f_db")

+     def test_outdated_chroots_extend_or_expire(self):

+         # Make sure that `self.u2` hasn't reviewed anything yet

+         assert len(OutdatedChrootsLogic.get_all_reviews(self.u2).all()) == 0

+ 

+         # Let's have some outdated chroot

+         self.c2.copr_chroots[0].delete_after = datetime.now() + timedelta(days=35)

+ 

+         # Make sure a user reviewed it

+         OutdatedChrootsLogic.make_review(self.u2)

+         assert len(OutdatedChrootsLogic.get_all_reviews(self.u2).all()) == 1

+ 

+         # Extend should properly extend the preservation period

+         # and drop the review

+         flask.g.user = self.u2

+         OutdatedChrootsLogic.extend(self.c2.copr_chroots[0])

+         assert (self.c2.copr_chroots[0].delete_after

+                 > datetime.now() + timedelta(days=35))

+         assert len(OutdatedChrootsLogic.get_all_reviews(self.u2).all()) == 0

+ 

+         # User changed his mind and expired the chroot instead

+         OutdatedChrootsLogic.expire(self.c2.copr_chroots[0])

+         assert (self.c2.copr_chroots[0].delete_after.date()

+                 == datetime.now().date())

See commit messages for a more detailed description.

Metadata Update from @frostyx:
- Pull-request tagged with: wip

3 years ago

I am marking this WIP because flash_outdated_chroots_warning() function is not finished yet. Everything else is from my POV finished and ready for review.

I am trying to figure out how to implement it though. It is necessary for this function to be optimized and as fast as possible because it is called for every page. This is the behavior I propose

show a warning that user should visit their EOL repositories page
This warning will be displayed indefinitely, on all coprs_ns
pages, until a user visits the outdated repositories page.
It will re-appear everytime some chroot is marked as EOL and affects
any of the projects listed there. It will also re-appear everytime a
chroot gets close to expiring (the last 14 days).

To cover it, we can easily add a models.User.last_visited_repositories datetime column, or even models.User.has_visited_repositores boolean column. With the exception to

chroot gets close to expiring (the last 14 days).

Because for this, I cannot see any other way than comparing the remaining time of each chroot to the last_visited_repositores value, which I don't know if it is good enough. Do you have any idea how to do this better?

It is necessary for this function to be optimized

I think that these problems are typically solved by caches.

If we speak about time periods like 14 days, do the 24h caching (I'd
actually prefer something 30 days at least), ... by visiting the
/repositories page you invalidate the cache entry and calculate a new one.

Do you have any idea how to do this better?

This is just an idea, not a requirement...

The flash should inform about something like:

Some of the chroots you maintain are **newly marked EOL**, and will be
removed in XX days, please review the list to hide this warning.

The flash should be shown if there exists at least one CoprChroot which
newly crossed the XX days period. So I think that we could e.g. store
User/CoprChroot pairs into DB as a separate reviewed_chroot relation.

When the CoprChroot is "prolonged", drop the entry from
reviewed_chroots. So once it crosses the border again, it triggers the
flash again.

We could then show the warning only if there was a CoprChroot that is about to
go through the build garbage collection in the next XX days -- and still
isn't stored in reviewed_chroots relation.

The User.last_visited_repositores can not work on per-chroot basis.

rebased onto 807a617f7ba7a4c8208c9843b5a79cb85748be7d

3 years ago

Metadata Update from @frostyx:
- Pull-request untagged with: wip

3 years ago

2 new commits added

  • frontend: show a warning that user should visit their EOL repositories page
  • frontend: add EOL repositories page for user (in opposite to project)
3 years ago

I finally finished(?) the PR according to your last comment.

The flash should inform about something like:

I modified it a bit because it would complicate the implementation even more and I wasn't sure if it was worth it. If that was meant to be the most important part of the message, we can rewrite the OutdatedChrootsLogic.has_not_reviewed method.

Does the overall solution make sense to you? I rewrote it so many times, that I am not even sure anymore if I missed something.

Metadata Update from @praiskup:
- Request assigned

3 years ago

id say outer left join on user-id and copr id would be more obvious, and reusable

I initially thought that we don't need the reviewed field here ... the fact that the corresponding ReviewedOutdatedChroot item exists should be enough to claim that the CoprChroot is reviewed, and we never complain about it again ....
When user hits the "Extend" button, we just have to drop the ReviewedOutdatedChroot item.

When user hits the "Extend" button, we just have to drop the ReviewedOutdatedChroot item.

drop all the related ReviewedOutdatedChroot items (for all affected users, e.g. in group projects)

Notable benefit is that we don't have to update all the user's ReviewedOutdatedChroot records for each /repositories/ visit.

The logic methods really deserve doc-strings; they are not trivial to understand. I'd also prefer to have a test-case ... one project, one group project, one foreign admin project ... all EOLed, and a simple visit of the page should create the new rows in the new table; while hitting the Extend button should remove the row in the table, and start complaining again after some time.

7 new commits added

  • add tests for reviweing outdated chroots
  • frontend: not access flask.g.user, user parameter instead
  • document logic functions
  • drop reviewed field
  • small refactor
  • properly notify of group projects
  • remove double route
3 years ago

Thank you for the review @praiskup, you found a lot of things that I missed out. I tried to address everything except for

id say outer left join on user-id and copr id would be more obvious, and reusable

Because I didn't understand your point here. However, I removed the join completely, so it doesn't matter anymore?

When user hits the "Extend" button, we just have to drop the ReviewedOutdatedChroot item.
drop all the related ReviewedOutdatedChroot items (for all affected users, e.g. in group projects)
while hitting the Extend button should remove the row in the table, and start complaining again after some time.

Since we got rid of the reviewed timestamp, I am not exactly sure about the "after some time" part ... Once we remove those rows, the complaining will start immediately.

Metadata Update from @praiskup:
- Pull-request tagged with: needs-work

3 years ago

4 new commits added

  • not show flash message for already expired chroots
  • remove review results when clicking to extend button
  • review only those chroots where half of the period is gone
  • show notification flash only after some time
3 years ago

After our last brainstorm, I implemented the following functionality:

  1. Flash message will start to appear only once some of the time already expired, not immediately.
  2. If I open the /user/repositories/ page, it will mark only those chroots from 1. ^^ as reviewed.
  3. If I click on the extend button, the result from the reviewed_outdated_chroot table is removed

There is one minor thing that doesn't have any effect on the functionality, and I don't have whether we have any preference here - When I open the /user/repositories page, result in the reviewed_outdated_chroot table is created even for already expired chroots that are going to be deleted on next Cron run. It's easy to change if we want to ...

I haven't rebased the, so you can easily review those changes. The commit history is a mess though and should be squashed together or fixed commit names, ...

Shouldn't we auto-delete this transitively?

So you eventually did not pick DELETE_EOL_CHROOTS_AFTER/2 ..? OK.

I mean, it is completely fine with me and sounds like a good idea.

I'm just not 100% confident that get_all_reviews(user) is guaranteed to be a superset of the outdated set.
We use datetime.now() twice in this function, which is (a bit only, right) racy, it's a nit.

1 new commit added

  • datetime.now only once
3 years ago

I'm just not 100% confident that get_all_reviews(user) is guaranteed to be a superset of the outdated set.

How can we do this better? Instead .count() getting .all() for outdated and then filtering if there is some of those missing in cls.get_all_reviews(user)?

We use datetime.now() twice in this function, which is (a bit only, right) racy, it's a nit.

Done

Shouldn't we auto-delete this transitively?

We probably should. I am going to fix it.

2 new commits added

  • drop forgotten index, it doesn't exist anymore
  • ondelete cascade
3 years ago

2 new commits added

  • small refactor
  • don't compare outdated > reviewed
3 years ago

I'm just not 100% confident that get_all_reviews(user) is guaranteed to be a superset of the outdated set.

How can we do this better? Instead .count() getting .all() for outdated and then filtering if there is some of those missing in cls.get_all_reviews(user)?

I did it a bit differently, but I made the change, so we don't compare counts of outdated > reviewed anymore.

PTAL

Nice, I like this variant. But I'd still probably prefer to reasonably squash the commit storm. Up to you, +1.

Sure, I will squash, give me a minute. I didn't do any rebase so you can easily see the new changes and don't have to read the whole diff again and again ... :-)

rebased onto 580bddde764f886ef40ea3bb99d8ce96e0d3235c

3 years ago

Metadata Update from @frostyx:
- Pull-request untagged with: needs-work

3 years ago

Can you please rebase on top of master?

rebased onto 2259d89bb6e3aa050e470c37519c39d74e70963d

3 years ago

Can you please rebase on top of master?

Done

3 new commits added

  • frontend: show a warning that user should visit their EOL repositories page
  • frontend: add EOL repositories page for user (in opposite to project)
  • frontend: not access flask.g.user, user parameter instead
3 years ago

There was a conflict in alembic migrations, fixed.

rebased onto 6c78d2c

3 years ago

Pull-Request has been merged by praiskup

3 years ago
Metadata