Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/sonarqube.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ on:
push:
branches:
- develop
- master
pull_request:


jobs:
Expand Down
24 changes: 17 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,16 @@ You can either develop using the dev database, or use the local database provide

Using the local database is detailed below, but both options will require the dev database password, so you will have to ask an RTP for this too

#### Forcing evals/rtp or anything else
All of the role checking is done in `conditional/utils/user_dict.py`, and you can change the various functions to `return True` for debugging

### Run (Without Docker)

To run the application without using containers, you must have the latest version of [Python 3](https://www.python.org/downloads/) and [virtualenv](https://virtualenv.pypa.io/en/stable/installation/) installed. Once you have those installed, create a new virtualenv and install the Python dependencies:

```sh
virtualenv .conditionalenv -p `which python3`
source .conditionalenv/bin/activate
virtualenv .venv
source .venv/bin/activate
pip install -r requirements.txt
```

Expand Down Expand Up @@ -89,7 +92,9 @@ Which can be restarted every time changes are made

To add new dependencies, add them to `requirements.in` and then run `pip-compile requirements.in` to produce a new locked `requirements.txt`. Do not edit `requirements.txt` directly as it will be overwritten by future PRs.

### Local database
### Database Stuff

#### Local database

You can run the database locally using the docker compose

Expand All @@ -106,18 +111,23 @@ To run migration commands in the local database, you can run the commands inside
podman exec conditional flask db upgrade
```

### Database Migrations
#### Database Migrations

If the database schema is changed after initializing the database, you must migrate it to the new schema by running:

```
```sh
flask db upgrade
# or, to run it inside the container for use with local databases (DO THIS
podman exec conditional flask db upgrade
```


At the same time, if you change the database schema, you must generate a new migration by running:

```
```sh
flask db migrate
# or, to run it inside the container for use with local databases (DO THIS
podman exec conditional flask db migrate
```

The new migration script in `migrations/versions` should be verified before being committed, as Alembic may not detect every change you make to the models.
Expand All @@ -128,7 +138,7 @@ For more information, refer to the [Flask-Migrate](https://flask-migrate.readthe

Conditional includes a utility to facilitate data migrations from the old Evals DB. This isn't necessary to run Conditional. To perform this migration, run the following commands before starting the application:

```
```sh
pip install pymysql
flask zoo
```
4 changes: 2 additions & 2 deletions conditional/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ def database_processor(logger, log_method, event_dict): # pylint: disable=unuse
from .blueprints.intro_evals_form import intro_evals_form_bp
from .blueprints.housing import housing_bp
from .blueprints.spring_evals import spring_evals_bp
from .blueprints.gatekeep import gatekeep_bp
from .blueprints.conditional import conditionals_bp
from .blueprints.member_management import member_management_bp
from .blueprints.slideshow import slideshow_bp
Expand All @@ -132,15 +133,14 @@ def database_processor(logger, log_method, event_dict): # pylint: disable=unuse
app.register_blueprint(intro_evals_form_bp)
app.register_blueprint(housing_bp)
app.register_blueprint(spring_evals_bp)
app.register_blueprint(gatekeep_bp)
app.register_blueprint(conditionals_bp)
app.register_blueprint(member_management_bp)
app.register_blueprint(slideshow_bp)
app.register_blueprint(cache_bp)
app.register_blueprint(co_op_bp)
app.register_blueprint(log_bp)

from .util.ldap import ldap_get_member


@app.route('/<path:path>')
def static_proxy(path):
Expand Down
17 changes: 16 additions & 1 deletion conditional/blueprints/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
from conditional.util.auth import get_user
from conditional.util.flask import render_template
from conditional.util.housing import get_queue_position
from conditional.util.member import get_active_members, get_freshman_data, get_voting_members, get_cm, get_hm, req_cm
from conditional.util.member import gatekeep_values, get_active_members, get_freshman_data, get_voting_members, \
get_cm, get_hm, is_gatekeep_active, req_cm
from conditional.util.user_dict import user_dict_is_active, user_dict_is_bad_standing, user_dict_is_intromember, \
user_dict_is_onfloor

Expand Down Expand Up @@ -146,4 +147,18 @@ def display_dashboard(user_dict=None):
data['hm_attendance'] = hm_attendance
data['hm_attendance_len'] = len(hm_attendance)

gatekeep_info = gatekeep_values(uid)
gatekeep_result = 'disenfranchised'

if gatekeep_info['result']:
gatekeep_result = 'passing'

data['gatekeep_active'] = is_gatekeep_active()
data['gatekeep'] = {
'status': gatekeep_result,
'committee_meetings': gatekeep_info['c_meetings'],
'technical_seminars': gatekeep_info['t_seminars'],
'hm_missed': gatekeep_info['h_meetings_missed']
}

return render_template('dashboard.html', **data)
131 changes: 131 additions & 0 deletions conditional/blueprints/gatekeep.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import structlog
from flask import Blueprint, request
from sqlalchemy import func

from conditional import start_of_year, auth
from conditional.models.models import CommitteeMeeting, HouseMeeting, MemberCommitteeAttendance, \
MemberSeminarAttendance, TechnicalSeminar
from conditional.models.models import MemberHouseMeetingAttendance
from conditional.util.auth import get_user
from conditional.util.flask import render_template
from conditional.util.ldap import ldap_get_active_members
from conditional.util.member import get_semester_info, is_gatekeep_active

gatekeep_bp = Blueprint('gatekeep_bp', __name__)

logger = structlog.get_logger()

@gatekeep_bp.route('/gatekeep_status/')
@auth.oidc_auth("default")
@get_user
def display_spring_evals(internal=False, user_dict=None):
log = logger.new(request=request, auth_dict=user_dict)
log.info('Display Gatekeep Status Listing')

_, semester_start = get_semester_info()
active_members = ldap_get_active_members()

cm_count = {row[0]: row[1] for row in MemberCommitteeAttendance.query.join(
CommitteeMeeting,
MemberCommitteeAttendance.meeting_id == CommitteeMeeting.id
).with_entities(
MemberCommitteeAttendance.uid,
CommitteeMeeting.timestamp,
CommitteeMeeting.approved,
).filter(
CommitteeMeeting.approved,
CommitteeMeeting.timestamp >= semester_start
).with_entities(
MemberCommitteeAttendance.uid,
func.count(MemberCommitteeAttendance.uid) #pylint: disable=not-callable
).group_by(
MemberCommitteeAttendance.uid
).all()}

ts_count = {row[0]: row[1] for row in MemberSeminarAttendance.query.join(
TechnicalSeminar,
MemberSeminarAttendance.seminar_id == TechnicalSeminar.id
).with_entities(
MemberSeminarAttendance.uid,
TechnicalSeminar.timestamp,
TechnicalSeminar.approved,
).filter(
TechnicalSeminar.approved,
TechnicalSeminar.timestamp >= semester_start
).with_entities(
MemberSeminarAttendance.uid,
func.count(MemberSeminarAttendance.uid) #pylint: disable=not-callable
).group_by(
MemberSeminarAttendance.uid
).all()}

hm_missed = {row[0]: row[1] for row in MemberHouseMeetingAttendance.query.join(
HouseMeeting,
MemberHouseMeetingAttendance.meeting_id == HouseMeeting.id
).filter(
HouseMeeting.date >= semester_start,
MemberHouseMeetingAttendance.attendance_status == 'Absent'
).with_entities(
MemberHouseMeetingAttendance.uid,
func.count(MemberHouseMeetingAttendance.uid) #pylint: disable=not-callable
).group_by(
MemberHouseMeetingAttendance.uid
).all()}

gk_members = []
for account in active_members:
uid = account.uid
name = account.cn

member_missed_hms = []

if hm_missed.get(uid, 0) != 0:
member_missed_hms = MemberHouseMeetingAttendance.query.join(
HouseMeeting,
MemberHouseMeetingAttendance.meeting_id == HouseMeeting.id
).filter(
HouseMeeting.date >= start_of_year(),
MemberHouseMeetingAttendance.attendance_status == 'Absent',
MemberHouseMeetingAttendance.uid == uid,
).with_entities(
func.array_agg(HouseMeeting.date)
).scalar()

cm_attended_count = cm_count.get(uid, 0)
ts_attended_count = ts_count.get(uid, 0)

passing = len(member_missed_hms) <= 1 and cm_attended_count >= 6 and ts_attended_count >= 2

status = 'disenfranchised'

if passing:
status = 'passed'

member = {
'name': name,
'uid': uid,
'status': status,
'committee_meetings': cm_attended_count,
'technical_seminars': ts_attended_count,
'req_meetings': 6,
'req_seminars': 2,
'house_meetings_missed': member_missed_hms,
}

gk_members.append(member)

gk_members.sort(key=lambda x: x['committee_meetings'], reverse=True)
gk_members.sort(key=lambda x: x['technical_seminars'], reverse=True)
gk_members.sort(key=lambda x: len(x['house_meetings_missed']))
# return names in 'first last (username)' format
if internal:
return gk_members

gatekeep_active = is_gatekeep_active()

return render_template('gatekeep.html',
username=user_dict['username'],
members=gk_members,
gatekeep_active=gatekeep_active,
req_meetings=6,
req_seminars=2)
72 changes: 71 additions & 1 deletion conditional/templates/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,6 @@ <h3 class="panel-title">Member Statistics</h3>
</div>
</div>


{% if major_projects_count == 0 and not active%}
<div class="alert alert-warning" role="alert"> <i class="bi bi-exclamation-circle white" style="padding-right:5px"> </i> You have no major projects.</div>
{% elif major_projects_count > 0 %}
Expand Down Expand Up @@ -318,6 +317,77 @@ <h3 class="panel-title">Housing Status</h3>
</div>
</div>

{% if active %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Gatekeep
{% if gatekeep['status'] == "passing" %}
<span class="pull-right"><i class="bi bi-check-circle-fill green"></i> Ok! </span>
{% elif gatekeep['status'] == "disenfranchised" and gatekeep_active%}
<span class="pull-right"><i class="bi bi-x-circle-fill red"></i> Disenfranchised</span>
{% elif active %}
<span class="pull-right"><i class="bi bi-hourglass-split yellow"></i> Pending</span>
{% endif %}
</h3>
</div>
<div class="panel-body table-fill">
<table class="table table-striped table-responsive no-bottom-margin">
<tbody>
{% if not gatekeep_active %}
<tr>
<td class="title">Status</td>
<td>
<span class="pull-right">
Gatekeep Inactive Until 6 Weeks
</span>
</td>
</tr>
{% endif %}
<tr>
<td class="title">Technical Seminars</td>
<td>
<span class="pull-right">
{% if gatekeep['technical_seminars'] >= 2 %}
<i class="bi bi-check-circle-fill green"></i>
{% else %}
<i class="bi bi-x-circle-fill red"></i>
{% endif %}
{{ gatekeep['technical_seminars'] }} / 2
</span>
</td>
</tr>
<tr>
<td class="title">Directorship Meetings</td>
<td>
<span class="pull-right">
{% if gatekeep['committee_meetings'] >= 6 %}
<i class="bi bi-check-circle-fill green"></i>
{% else %}
<i class="bi bi-x-circle-fill red"></i>
{% endif %}
{{ gatekeep['committee_meetings']}} / 6
</span>
</td>
</tr>
<tr>
<td class="title">House Meetings Missed</td>
<td><span class="pull-right">
{% if gatekeep['hm_missed'] == 0 %}
<i class="bi bi-check-circle-fill green"></i> None
{% elif gatekeep['hm_missed'] <= 1 %}
<i class="bi bi-check-circle-fill green"></i> {{ gatekeep['hm_missed'] }} Missed
{% else %}
<i class="bi bi-x-circle-fill red"></i> {{gatekeep['hm_missed']}}</span>
{% endif %}
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
{% endif %}

{% if hm_attendance_len == 0 and active%}
<div class="alert alert-success">
<i class="bi bi-check-circlce white" style="padding-right:5px"> </i> You haven't missed any house meetings.
Expand Down
Loading