aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Meinhold <mxmeinhold@gmail.com>2021-03-20 11:46:30 -0400
committerGitHub <noreply@github.com>2021-03-20 11:46:30 -0400
commit1672ecc67e52695ca88f4fa22587d5f9d80ee91c (patch)
treede1f1fe1dceab1dccc3ad2f683023a0a1bae7c30
parente4d19a78c6bc1c029d306ebb0276f821cdbd424c (diff)
parent83f6548f36845fe906094e706cd49a9e416a1118 (diff)
Merge Version 3.5.3 (#251)v3.5.3
-rw-r--r--.devcontainer/Dockerfile9
-rw-r--r--.devcontainer/devcontainer.json58
-rw-r--r--.devcontainer/docker-compose.yaml41
-rw-r--r--.github/workflows/python-app.yml4
-rw-r--r--.pylintrc1
-rw-r--r--Dockerfile11
-rw-r--r--README.md8
-rw-r--r--config.env.py6
-rw-r--r--package.json2
-rw-r--r--packet/__init__.py7
-rw-r--r--packet/git.py49
-rw-r--r--packet/ldap.py18
-rw-r--r--packet/routes/upperclassmen.py17
-rw-r--r--packet/templates/include/head.html39
-rw-r--r--packet/templates/upperclassmen_totals.html38
-rw-r--r--packet/utils.py3
-rw-r--r--requirements.txt1
17 files changed, 277 insertions, 35 deletions
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 0000000..14700af
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,9 @@
+FROM mcr.microsoft.com/vscode/devcontainers/universal:1-linux
+
+USER root
+
+# Add LDAP and python dependency build deps
+RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && \
+ apt-get -yq --no-install-recommends install gcc curl libsasl2-dev libldap2-dev libssl-dev python3-dev
+
+USER codespace
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..ef33dc3
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,58 @@
+// Update the VARIANT arg in docker-compose.yml to pick a Python version: 3, 3.8, 3.7, 3.6
+{
+ "name": "Packet Codespace (python and postgres)",
+ "dockerComposeFile": "docker-compose.yaml",
+ "service": "app",
+
+ // Set *default* container specific settings.json values on container create.
+ "settings": {
+ "sqltools.connections": [{
+ "name": "Container database",
+ "driver": "PostgreSQL",
+ "previewLimit": 50,
+ "server": "localhost",
+ "port": 5432,
+ "database": "postgres",
+ "username": "postgres",
+ "password": "mysecretpassword"
+ }],
+ "terminal.integrated.shell.linux": "/bin/bash",
+ "python.pythonPath": "/opt/python/latest/bin/python",
+ "python.linting.enabled": true,
+ "python.linting.pylintEnabled": true,
+ "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
+ "python.formatting.blackPath": "/usr/local/py-utils/bin/black",
+ "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
+ "python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
+ "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
+ "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
+ "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
+ "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
+ "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint",
+ "python.testing.pytestPath": "/usr/local/py-utils/bin/pytest"
+ },
+ "remoteUser": "codespace",
+ "overrideCommand": false,
+ "workspaceMount": "source=${localWorkspaceFolder},target=/home/codespace/workspace,type=bind,consistency=cached",
+ "workspaceFolder": "/home/codespace/workspace",
+ "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined", "--privileged", "--init" ],
+
+ // Add the IDs of extensions you want installed when the container is created.
+ "extensions": [
+ "GitHub.vscode-pull-request-github",
+ "ms-python.python",
+ "mtxr.sqltools",
+ "mtxr.sqltools-driver-pg"
+ ],
+
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
+ // "forwardPorts": [5000, 5432],
+
+ // Use 'postCreateCommand' to run commands after the container is created.
+ // "oryx build" will automatically install your dependencies and attempt to build your project
+ "postCreateCommand": [
+ "pip install --progress-bar=off install -r requirements.txt;",
+ "yarn install && `yarn bin gulp production`;",
+ "/home/codespace/.local/bin/flask db upgrade;"
+ ]
+}
diff --git a/.devcontainer/docker-compose.yaml b/.devcontainer/docker-compose.yaml
new file mode 100644
index 0000000..974d59f
--- /dev/null
+++ b/.devcontainer/docker-compose.yaml
@@ -0,0 +1,41 @@
+version: '3'
+
+services:
+ app:
+ build:
+ context: ..
+ dockerfile: .devcontainer/Dockerfile
+ args:
+ NODE_VERSION: "10"
+
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ - ..:/workspace:cached
+
+ # Overrides default command so things don't shut down after the process ends.
+ command: sleep infinity
+
+ # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
+ network_mode: service:db
+
+ # Uncomment the next line to use a non-root user for all processes.
+ user: codespace
+
+ # Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
+ # (Adding the "ports" property to this file will not forward from a Codespace.)
+
+ db:
+ image: postgres:latest
+ restart: unless-stopped
+ volumes:
+ - postgres-data:/var/lib/postgresql/data
+ environment:
+ POSTGRES_USER: postgres
+ POSTGRES_DB: postgres
+ POSTGRES_PASSWORD: mysecretpassword
+
+ # Add "forwardPorts": ["5432"] to **devcontainer.json** to forward MongoDB locally.
+ # (Adding the "ports" property to this file will not forward from a Codespace.)
+
+volumes:
+ postgres-data:
diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml
index 71dcdd5..763c63e 100644
--- a/.github/workflows/python-app.yml
+++ b/.github/workflows/python-app.yml
@@ -15,11 +15,11 @@ jobs:
strategy:
matrix:
- python-version: [3.7, 3.8]
+ python-version: [3.9]
steps:
- name: Install ldap dependencies
- run: sudo apt-get install libldap2-dev libsasl2-dev
+ run: sudo apt-get update && sudo apt-get install libldap2-dev libsasl2-dev
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
diff --git a/.pylintrc b/.pylintrc
index a250c48..bd778b5 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -1,6 +1,7 @@
[MASTER]
ignore = ,input
persistent = yes
+load-plugins = pylint_quotes
[MESSAGES CONTROL]
disable =
diff --git a/Dockerfile b/Dockerfile
index 98ca1d5..2ca5882 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,8 +1,10 @@
-FROM python:3.7-slim-buster
+FROM python:3.9-slim-buster
MAINTAINER Devin Matte <matted@csh.rit.edu>
+ENV DD_LOGS_INJECTION=true
+
RUN apt-get -yq update && \
- apt-get -yq --no-install-recommends install gcc curl libsasl2-dev libldap2-dev libssl-dev gnupg2 && \
+ apt-get -yq --no-install-recommends install gcc curl libsasl2-dev libldap2-dev libssl-dev gnupg2 git && \
apt-get -yq clean all
RUN mkdir /opt/packet
@@ -30,4 +32,7 @@ RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - && \
RUN ln -sf /usr/share/zoneinfo/America/New_York /etc/localtime
-CMD ["gunicorn", "packet:app", "--bind=0.0.0.0:8080", "--access-logfile=-", "--timeout=600"]
+# Set version for apm
+RUN echo "export DD_VERSION=$(python3 packet/git.py)" >> /tmp/version
+
+CMD ["/bin/bash", "-c", "source /tmp/version && ddtrace-run gunicorn packet:app --bind=0.0.0.0:8080 --access-logfile=- --timeout=600"]
diff --git a/README.md b/README.md
index d9d4271..72355f3 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# CSH Web Packet
-[![Python 3.7](https://img.shields.io/badge/python-3.7-blue.svg)](https://www.python.org/downloads/release/python-370/)
+[![Python 3.9](https://img.shields.io/badge/python-3.9-blue.svg)](https://www.python.org/downloads/release/python-390/)
[![Build Status](https://travis-ci.com/ComputerScienceHouse/packet.svg?branch=develop)](https://travis-ci.com/ComputerScienceHouse/packet)
Packet is used by CSH to facilitate the freshmen packet portion of our introductory member evaluation process. This is
@@ -8,7 +8,7 @@ the second major iteration of packet on the web. The first version was
[Tal packet](https://github.com/TalCohen/CSHWebPacket).
## Setup
-**Requires Python 3.7 or newer.**
+**Requires Python 3.9 or newer.**
To get the server working you'll just need the Python dependencies and some secrets. There will be some UI issues due
to missing assets though. To solve that you'll want to set up the front end dependencies or download a copy of the
@@ -115,12 +115,12 @@ All DB commands are from the `Flask-Migrate` library and are used to configure D
docs [here](https://flask-migrate.readthedocs.io/en/latest/) for details.
## Code standards
-This project is configured to use Pylint. Commits will be pylinted by Travis CI and if the score drops your build will
+This project is configured to use Pylint. Commits will be pylinted by GitHub actions and if the score drops your build will
fail blocking you from merging. To make your life easier just run it before making a PR.
To run pylint use this command:
```bash
-pylint --load-plugins pylint_quotes packet/routes packet
+pylint packet/routes packet
```
All python files should have a top-level docstring explaining the contents of the file and complex functions should
diff --git a/config.env.py b/config.env.py
index b14e3eb..7e7b883 100644
--- a/config.env.py
+++ b/config.env.py
@@ -15,7 +15,6 @@ SECRET_KEY = environ.get("PACKET_SECRET_KEY", "PLEASE_REPLACE_ME")
# Logging config
LOG_LEVEL = environ.get("PACKET_LOG_LEVEL", "INFO")
-ANALYTICS_ID = environ.get("ANALYTICS_ID", "UA-420696-9")
# OpenID Connect SSO config
REALM = environ.get("PACKET_REALM", "csh")
@@ -68,3 +67,8 @@ SLACK_WEBHOOK_URL = environ.get("PACKET_SLACK_URL", None)
# Packet Config
PACKET_UPPER = environ.get("PACKET_UPPER", "packet.csh.rit.edu")
PACKET_INTRO = environ.get("PACKET_INTRO", "freshmen-packet.csh.rit.edu")
+
+# RUM
+RUM_APP_ID = environ.get("PACKET_RUM_APP_ID", "")
+RUM_CLIENT_TOKEN = environ.get("PACKET_RUM_CLIENT_TOKEN","")
+DD_ENV = environ.get("DD_ENV", "local-dev")
diff --git a/package.json b/package.json
index 17a3c6c..1b92a41 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"title": "CSH Packet",
"name": "csh-packet",
- "version": "3.5.2",
+ "version": "3.5.3",
"description": "A web app implementation of the CSH introductory packet.",
"bugs": {
"url": "https://github.com/ComputerScienceHouse/packet/issues",
diff --git a/packet/__init__.py b/packet/__init__.py
index 89b7728..c7fed4b 100644
--- a/packet/__init__.py
+++ b/packet/__init__.py
@@ -19,6 +19,8 @@ import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
+from .git import get_version
+
app = Flask(__name__)
gzip = Gzip(app)
@@ -31,9 +33,8 @@ _pyfile_config = os.path.join(_root_dir, 'config.py')
if os.path.exists(_pyfile_config):
app.config.from_pyfile(_pyfile_config)
-# Fetch the version number from the npm package file
-with open(os.path.join(_root_dir, 'package.json')) as package_file:
- app.config['VERSION'] = json.load(package_file)['version']
+# Fetch the version number
+app.config['VERSION'] = get_version()
# Logger configuration
logging.getLogger().setLevel(app.config['LOG_LEVEL'])
diff --git a/packet/git.py b/packet/git.py
new file mode 100644
index 0000000..00e4d65
--- /dev/null
+++ b/packet/git.py
@@ -0,0 +1,49 @@
+import json
+import os
+import subprocess
+
+def get_short_sha(commit_ish: str = 'HEAD'):
+ """
+ Get the short hash of a commit-ish
+ Returns '' if unfound
+ """
+
+ try:
+ rev_parse = subprocess.run(f'git rev-parse --short {commit_ish}'.split(), capture_output=True, check=True)
+ return rev_parse.stdout.decode('utf-8').strip()
+ except subprocess.CalledProcessError:
+ return ''
+
+def get_tag(commit_ish: str = 'HEAD'):
+ """
+ Get the name of the tag at a given commit-ish
+ Returns '' if untagged
+ """
+
+ try:
+ describe = subprocess.run(f'git describe --exact-match {commit_ish}'.split(), capture_output=True, check=True)
+ return describe.stdout.decode('utf-8').strip()
+ except subprocess.CalledProcessError:
+ return ''
+
+def get_version(commit_ish: str = 'HEAD'):
+ """
+ Get the version string of a commit-ish
+
+ If we have a commit and the commit is tagged, version is `tag (commit-sha)`
+ If we have a commit but not a tag, version is `commit-sha`
+ If we have neither, version is the version field of package.json
+ """
+
+ if sha := get_short_sha(commit_ish):
+ if tag := get_tag(commit_ish):
+ return f'{tag} ({sha})'
+ else:
+ return sha
+ else:
+ root_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+ with open(os.path.join(root_dir, 'package.json')) as package_file:
+ return json.load(package_file)['version']
+
+if __name__ == '__main__':
+ print(get_version())
diff --git a/packet/ldap.py b/packet/ldap.py
index 4fcb738..99b0367 100644
--- a/packet/ldap.py
+++ b/packet/ldap.py
@@ -67,6 +67,24 @@ class LDAPWrapper:
else:
return group in member.groups
+ def get_groups(self, member):
+ if self.ldap:
+ return list(
+ map(
+ lambda g: g[0][3:],
+ filter(
+ lambda d: d[1] == 'cn=groups',
+ map(
+ lambda group_dn: group_dn.split(','),
+ member.get('memberOf')
+ )
+ )
+ )
+ )
+ else:
+ return member.groups
+
+
# Getters
diff --git a/packet/routes/upperclassmen.py b/packet/routes/upperclassmen.py
index 11bc2b1..92d5564 100644
--- a/packet/routes/upperclassmen.py
+++ b/packet/routes/upperclassmen.py
@@ -3,12 +3,11 @@ Routes available to CSH users only
"""
import json
-from itertools import chain
from operator import itemgetter
from flask import redirect, render_template, url_for
from packet import app
-from packet.models import Packet, MiscSignature
+from packet.models import Packet
from packet.utils import before_request, packet_auth
from packet.log_utils import log_cache, log_time
from packet.stats import packet_stats
@@ -51,18 +50,20 @@ def upperclassmen_total(info=None):
# Sum up the signed packets per upperclassman
upperclassmen = dict()
+ misc = dict()
for packet in open_packets:
- for sig in chain(packet.upper_signatures, packet.misc_signatures):
+ for sig in packet.upper_signatures:
if sig.member not in upperclassmen:
upperclassmen[sig.member] = 0
- if isinstance(sig, MiscSignature):
- upperclassmen[sig.member] += 1
- elif sig.signed:
- upperclassmen[sig.member] += 1
+ if sig.signed:
+ upperclassmen[sig.member] += 1
+ for sig in packet.misc_signatures:
+ misc[sig.member] = 1 + misc.get(sig.member, 0)
return render_template('upperclassmen_totals.html', info=info, num_open_packets=len(open_packets),
- upperclassmen=sorted(upperclassmen.items(), key=itemgetter(1), reverse=True))
+ upperclassmen=sorted(upperclassmen.items(), key=itemgetter(1), reverse=True),
+ misc=sorted(misc.items(), key=itemgetter(1), reverse=True))
@app.route('/stats/packet/<packet_id>')
diff --git a/packet/templates/include/head.html b/packet/templates/include/head.html
index 12ff14c..20d0f42 100644
--- a/packet/templates/include/head.html
+++ b/packet/templates/include/head.html
@@ -34,6 +34,35 @@
<link rel="stylesheet" href="{{ url_for('static', filename='css/packet.min.css') }}">
+ <!-- RUM -->
+ <script
+ src="https://www.datadoghq-browser-agent.com/datadog-rum.js"
+ type="text/javascript">
+ </script>
+ <script>
+ window.DD_RUM && window.DD_RUM.init({
+ applicationId: '{{ config["RUM_APP_ID"] }}',
+ clientToken: '{{ config["RUM_CLIENT_TOKEN"] }}',
+ site: 'datadoghq.com',
+ service: 'Packet',
+ env: '{{ config["DD_ENV"] }}',
+ version: '{{ config["VERSION"] }}',
+ sampleRate: 100,
+ trackInteractions: true
+ });
+
+ window.DD_RUM && window.DD_RUM.setUser({
+ id: '{{ info.uid }}',
+ realm: '{{ info.realm }}',
+ });
+
+ // Add groups to global context so they get interpreted as a list
+ window.DD_RUM && window.DD_RUM.addRumGlobalContext('usr', {
+ groups: {{ info.groups|safe }},
+ });
+ </script>
+
+
<!-- Push Notifications -->
<script src="https://cdn.onesignal.com/sdks/OneSignalSDK.js" async=""></script>
<script>
@@ -66,14 +95,4 @@
});
});
</script>
-
- <!-- Analytics -->
- <script async src="https://www.googletagmanager.com/gtag/js?id={{ config['ANALYTICS_ID'] }}"></script>
- <script>
- window.dataLayer = window.dataLayer || [];
- function gtag(){dataLayer.push(arguments);}
- gtag('js', new Date());
- gtag('set', {'user_id': '{{ info.uid }}' });
- gtag('config', '{{ config['ANALYTICS_ID'] }}');
- </script>
</head>
diff --git a/packet/templates/upperclassmen_totals.html b/packet/templates/upperclassmen_totals.html
index 814062e..2b56955 100644
--- a/packet/templates/upperclassmen_totals.html
+++ b/packet/templates/upperclassmen_totals.html
@@ -11,7 +11,7 @@
<div id="eval-blocks">
{% if num_open_packets > 0 %}
<div id="eval-table">
- <div class="card">
+ <div class="card mb-2">
<div class="card-body table-fill">
<div class="table-responsive">
<table class="table table-striped no-bottom-margin" data-module="table"
@@ -19,7 +19,7 @@
data-length-changable="true" data-paginated="false">
<thead>
<tr>
- <th>Upperclassman</th>
+ <th>Active Member</th>
<th>Signatures</th>
</tr>
</thead>
@@ -45,6 +45,40 @@
</div>
</div>
</div>
+ <div class="card mb-2">
+ <div class="card-body table-fill">
+ <div class="table-responsive">
+ <table class="table table-striped no-bottom-margin" data-module="table"
+ data-searchable="true" data-sort-column="1" data-sort-order="asc"
+ data-length-changable="true" data-paginated="false">
+ <thead>
+ <tr>
+ <th>Alumni or Advisor</th>
+ <th>Signatures</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for member, signed_count in misc %}
+ <tr>
+ <td>
+ <a href="{{ url_for("upperclassman", uid=member) }}">
+ <img class="eval-user-img"
+ alt="{{ member }}"
+ src="https://profiles.csh.rit.edu/image/{{ member }}"
+ width="25"
+ height="25"/> {{ get_csh_name(member) }}
+ </a>
+ </td>
+ <td>
+ {{ signed_count }}/{{ num_open_packets }}
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
</div>
{% else %}
<div class="alert alert-info" role="alert">
diff --git a/packet/utils.py b/packet/utils.py
index 4caa673..ff2f5a6 100644
--- a/packet/utils.py
+++ b/packet/utils.py
@@ -36,7 +36,8 @@ def before_request(func):
info = {
'realm': 'csh',
'uid': uid,
- 'admin': ldap.is_evals(member)
+ 'admin': ldap.is_evals(member),
+ 'groups': ldap.get_groups(member),
}
kwargs['info'] = info
diff --git a/requirements.txt b/requirements.txt
index 01ab4db..83cb6a8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -11,3 +11,4 @@ csh_ldap~=2.3.1
onesignal-sdk~=1.0.0
pylint-quotes~=0.2.1
sentry-sdk~=0.19.5
+ddtrace