diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..d49d608 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,40 @@ +repos: + # Using this mirror lets us use mypyc-compiled black, which is about 2x faster + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 23.11.0 + hooks: + - id: black + + # isort + - repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + args: [--profile=black] + + # flake8 + - repo: https://github.com/pycqa/flake8 + rev: 7.0.0 + hooks: + - id: flake8 + args: [--config=.github/linters/.flake8] + + # gitleaks + - repo: https://github.com/gitleaks/gitleaks + rev: v8.18.2 + hooks: + - id: gitleaks + + # yamllint + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.35.1 + hooks: + - id: yamllint + args: [-c=.github/linters/.yaml-lint.yml] + + # prettier + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v3.1.0 + hooks: + - id: prettier + types: [javascript] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1e8c88e..c0af0aa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,13 @@ -# Some Common Ways to Contribute +# Contribution Guide -## Add a new page +## Pre-Commit + +We have a `.pre-commit-config.yaml` file in our repository that will help you avoid common linting/formatting mistakes when you commit your changes. You can get set up by following +pre-commit's [quick start guide](https://pre-commit.com/#quick-start). + +## Some Common Ways to Contribute + +### Add a new page The official Flask documentation contains a useful [walkthrough](https://flask.palletsprojects.com/en/3.0.x/quickstart/#rendering-templates) of how to add/modify a new web view. @@ -48,7 +55,7 @@ You will be able to see your page by running your Flask server and visiting ``) and rebuild the image. + +Please see our [CONTRIBUTING.md](https://github.com/RHsyseng/t5g-field-support-team-utils/blob/main/CONTRIBUTING.md) for further development help. ## CI This project has a CI that is triggered on every push. diff --git a/dashboard/src/t5gweb/cache.py b/dashboard/src/t5gweb/cache.py index 95d9ed0..9ef2ca2 100644 --- a/dashboard/src/t5gweb/cache.py +++ b/dashboard/src/t5gweb/cache.py @@ -134,7 +134,7 @@ def get_cards(cfg, self=None, background=False): }, ) issue = jira_conn.issue(card) - comments = jira_conn.comments(issue) + comments = issue.fields.comment.comments card_comments = [] for comment in comments: body = comment.body diff --git a/dashboard/src/t5gweb/libtelco5g.py b/dashboard/src/t5gweb/libtelco5g.py index 91394dd..2d15af9 100755 --- a/dashboard/src/t5gweb/libtelco5g.py +++ b/dashboard/src/t5gweb/libtelco5g.py @@ -279,9 +279,11 @@ def create_cards(cfg, new_cases, action="none"): "priority": {"name": priority}, "labels": cfg["labels"], "summary": summary[:253] + ".." if len(summary) > 253 else summary, - "description": full_description[:253] + ".." - if len(full_description) > 253 - else full_description, + "description": ( + full_description[:253] + ".." + if len(full_description) > 253 + else full_description + ), } if assignee: @@ -411,7 +413,7 @@ def get_case_from_link(jira_conn, card): return None -def generate_stats(account=None): +def generate_stats(account=None, engineer=None): """generate some stats""" logging.warning("generating stats") @@ -426,6 +428,23 @@ def generate_stats(account=None): logging.warning("filtering cases for {}".format(account)) cards = {c: d for (c, d) in cards.items() if d["account"] == account} cases = {c: d for (c, d) in cases.items() if d["account"] == account} + if engineer is not None: + logging.warning("filtering cases for {}".format(engineer)) + cards = { + c: d for (c, d) in cards.items() if d["assignee"]["displayName"] == engineer + } + + # get case number and assignee from cards so that we can determine which cases + # belong to the engineer + temp_cases = {} + for case, details in cases.items(): + for card in cards: + if ( + case == cards[card]["case_number"] + and cards[card]["assignee"]["displayName"] == engineer + ): + temp_cases[case] = details + cases = temp_cases today = datetime.date.today() @@ -602,7 +621,7 @@ def plot_stats(): return x_values, y_values -def generate_histogram_stats(account=None): +def generate_histogram_stats(account=None, engineer=None): """ Calculates histogram statistics for resolved and relief times of cards. @@ -654,6 +673,12 @@ def generate_histogram_stats(account=None): logging.warning(f"filtering cards for {account}") cards = {c: d for (c, d) in cards.items() if d["account"] == account} + if engineer is not None: + logging.warning(f"filtering cards for {engineer}") + cards = { + c: d for (c, d) in cards.items() if d["assignee"]["displayName"] == engineer + } + histogram_data = { "Resolved": base_dictionary, "Relief": base_dictionary, diff --git a/dashboard/src/t5gweb/static/js/table.js b/dashboard/src/t5gweb/static/js/table.js index 88fefe3..d84549a 100644 --- a/dashboard/src/t5gweb/static/js/table.js +++ b/dashboard/src/t5gweb/static/js/table.js @@ -35,6 +35,15 @@ function format(data) { result += '

JIRA Issues:

'; for (let issue = 0; issue < data.issues.length; issue++) { + const telcoPriority = + data.issues[issue].private_keywords != null + ? data.issues[issue].private_keywords.find((str) => + str.includes("Priority"), + ) + : null; + const priorityNum = telcoPriority + ? telcoPriority.charAt(telcoPriority.length - 1) + : 0; result += ' {% if new_comments[account][status][card]['escalated'] %} {% if new_comments[account][status][card]['escalated_link'] %} - {% else %} - + {% endif %} {% elif new_comments[account][status][card]['potential_escalation'] %} - + {% else %} {% endif %} {% if new_comments[account][status][card]['watched'] %} - + {% else %} {% endif %} {% if new_comments[account][status][card]['crit_sit'] %} - + {% else %} {% endif %} @@ -138,7 +138,7 @@

Statistics

#SummaryPrioritySeverityTelco PriorityTarget ReleaseAssigneeQA ContactLast UpdatedStatus
" + + `` + (data.issues[issue].private_keywords != null ? data.issues[issue].private_keywords.find((str) => str.includes("Priority"), @@ -142,7 +151,7 @@ $(document).ready(function () { label: "Cases on Prio-list or Watchlist or Crit Sit", value: function (rowData, rowIdx) { return ( - rowData[3] === "Yes" || + rowData[3].includes("Yes") || rowData[4] === "Yes" || rowData[5] === "Yes" ); diff --git a/dashboard/src/t5gweb/static/style.css b/dashboard/src/t5gweb/static/style.css index 6462f74..d629bf4 100644 --- a/dashboard/src/t5gweb/static/style.css +++ b/dashboard/src/t5gweb/static/style.css @@ -208,4 +208,21 @@ span.anchor { /* Makes sure Navbar doesn't cover content */ .new-cases { padding-top: 70px; -} \ No newline at end of file +} + +/* Telco priority */ +.telco-priority-1 { + color: red !important; +} + +.telco-priority-2 { + color: orange !important; +} + +.telco-priority-3 { + color: blue !important; +} + +.telco-priority-4 { + color: black !important; +} diff --git a/dashboard/src/t5gweb/t5gweb.py b/dashboard/src/t5gweb/t5gweb.py index fe2cfef..cd53a26 100644 --- a/dashboard/src/t5gweb/t5gweb.py +++ b/dashboard/src/t5gweb/t5gweb.py @@ -36,11 +36,14 @@ def get_new_cases(): return new_cases -def get_new_comments(new_comments_only=True, account=None): +def get_new_comments(cards, new_comments_only=True, account=None, engineer=None): # fetch cards from redis cache - cards = libtelco5g.redis_get("cards") if account is not None: cards = {c: d for (c, d) in cards.items() if d["account"] == account} + if engineer is not None: + cards = { + c: d for (c, d) in cards.items() if d["assignee"]["displayName"] == engineer + } logging.warning("found %d JIRA cards" % (len(cards))) time_now = datetime.now(timezone.utc) @@ -78,9 +81,8 @@ def get_new_comments(new_comments_only=True, account=None): return accounts -def get_trending_cards(): +def get_trending_cards(cards): # fetch cards from redis cache - cards = libtelco5g.redis_get("cards") # get a list of trending cards trending_cards = [card for card in cards if "Trends" in cards[card]["labels"]] diff --git a/dashboard/src/t5gweb/templates/macros/macros.html b/dashboard/src/t5gweb/templates/macros/macros.html index 2c1f49f..3201033 100644 --- a/dashboard/src/t5gweb/templates/macros/macros.html +++ b/dashboard/src/t5gweb/templates/macros/macros.html @@ -109,24 +109,24 @@

Statistics

+ Yes YesYesPotentiallyPotentiallyNoYesYesNoYesYesNo {{ new_comments[account][status][card]['case_status'] }} {{ new_comments[account][status][card]['card_status'] }} - {{ new_comments[account][status][card]['assignee']['displayName'] }} + {{ new_comments[account][status][card]['assignee']['displayName'] }} {% if new_comments[account][status][card]['contributor'] | length %}

diff --git a/dashboard/src/t5gweb/templates/ui/account.html b/dashboard/src/t5gweb/templates/ui/account.html index 858467e..3fbee77 100644 --- a/dashboard/src/t5gweb/templates/ui/account.html +++ b/dashboard/src/t5gweb/templates/ui/account.html @@ -31,7 +31,11 @@ charset="utf8" src="{{ url_for('static', filename='node_modules/plotly.js-cartesian-dist-min/plotly-cartesian.min.js') }}">
+ {% if engineer_view == True %} +

Engineer Stats: {{ account }}

+ {% else %}

Account Stats: {{ account }}

+ {% endif %}
Account Stats: {{ account }}
+ {% if engineer_view != True %}

Bugs and JIRA Issues:

Bugs and JIRA Issues:
+ {% endif %}

Cases:

{{ macros.cases_table(new_comments, jira_server) }} diff --git a/dashboard/src/t5gweb/ui.py b/dashboard/src/t5gweb/ui.py index c381fc6..ec41b7a 100644 --- a/dashboard/src/t5gweb/ui.py +++ b/dashboard/src/t5gweb/ui.py @@ -29,7 +29,7 @@ ) from t5gweb.t5gweb import get_new_cases, get_new_comments, get_trending_cards, plots from t5gweb.taskmgr import refresh_background -from t5gweb.utils import set_cfg +from t5gweb.utils import make_pie_dict, set_cfg BP = Blueprint("ui", __name__, url_prefix="/") login_manager = LoginManager() @@ -80,20 +80,6 @@ def prepare_flask_request(request): } -def load_data(): - """Load data for dashboard""" - - cfg = set_cfg() - load_data.new_cases = get_new_cases() - plot_data = plots() - load_data.y = list(plot_data.values()) - load_data.accounts = get_new_comments() - load_data.accounts_all = get_new_comments(new_comments_only=False) - load_data.trending_cards = get_trending_cards() - load_data.now = redis_get("timestamp") - load_data.jira_server = cfg["server"] - - @BP.route("/", methods=["GET", "POST"]) def login(): """Handles redirects back and forth from SAML Provider and user creation in Redis""" @@ -202,12 +188,12 @@ def login(): @login_required def index(): """list new cases""" - load_data() + plot_data = plots() return render_template( "ui/index.html", - new_cases=load_data.new_cases, - values=load_data.y, - now=load_data.now, + new_cases=get_new_cases(), + values=list(plot_data.values()), + now=redis_get("timestamp"), ) @@ -268,7 +254,6 @@ def refresh_status(task_id): def refresh(): """Forces an update to the dashboard""" task = refresh_background.delay() - load_data() return jsonify({}), 202, {"Location": url_for("ui.refresh_status", task_id=task.id)} @@ -276,12 +261,13 @@ def refresh(): @login_required def report_view(): """Retrieves cards that have been updated within the last week and creates report""" - load_data() + cfg = set_cfg() + cards = redis_get("cards") return render_template( "ui/updates.html", - now=load_data.now, - new_comments=load_data.accounts, - jira_server=load_data.jira_server, + now=redis_get("timestamp"), + new_comments=get_new_comments(cards), + jira_server=cfg["server"], page_title="recent updates", ) @@ -290,12 +276,13 @@ def report_view(): @login_required def report_view_all(): """Retrieves all cards and creates report""" - load_data() + cfg = set_cfg() + cards = redis_get("cards") return render_template( "ui/updates.html", - now=load_data.now, - new_comments=load_data.accounts_all, - jira_server=load_data.jira_server, + now=redis_get("timestamp"), + new_comments=get_new_comments(cards=cards, new_comments_only=False), + jira_server=cfg["server"], page_title="all cards", ) @@ -306,12 +293,13 @@ def trends(): """Retrieves cards that have been labeled with 'Trends' within the previous quarter and creates report """ - load_data() + cfg = set_cfg() + cards = redis_get("cards") return render_template( "ui/updates.html", - now=load_data.now, - new_comments=load_data.trending_cards, - jira_server=load_data.jira_server, + now=redis_get("timestamp"), + new_comments=get_trending_cards(cards), + jira_server=cfg["server"], page_title="trends", ) @@ -320,12 +308,13 @@ def trends(): @login_required def table_view(): """Sorts new cards by severity and creates table""" - load_data() + cfg = set_cfg() + cards = redis_get("cards") return render_template( "ui/table.html", - now=load_data.now, - new_comments=load_data.accounts, - jira_server=load_data.jira_server, + now=redis_get("timestamp"), + new_comments=get_new_comments(cards), + jira_server=cfg["server"], page_title="severity", ) @@ -334,12 +323,13 @@ def table_view(): @login_required def table_view_all(): """Sorts all cards by severity and creates table""" - load_data() + cfg = set_cfg() + cards = redis_get("cards") return render_template( "ui/table.html", - now=load_data.now, - new_comments=load_data.accounts_all, - jira_server=load_data.jira_server, + now=redis_get("timestamp"), + new_comments=get_new_comments(cards=cards, new_comments_only=False), + jira_server=cfg["server"], page_title="all-severity", ) @@ -350,12 +340,13 @@ def weekly_updates(): """Retrieves cards and displays them plainly for easy copy/pasting and distribution """ - load_data() + cfg = set_cfg() + cards = redis_get("cards") return render_template( "ui/weekly_report.html", - now=load_data.now, - new_comments=load_data.accounts, - jira_server=load_data.jira_server, + now=redis_get("timestamp"), + new_comments=get_new_comments(cards), + jira_server=cfg["server"], page_title="weekly-update", ) @@ -364,13 +355,12 @@ def weekly_updates(): @login_required def get_stats(): """generate some stats""" - load_data() stats = generate_stats() x_values, y_values = plot_stats() histogram_stats = generate_histogram_stats() return render_template( "ui/stats.html", - now=load_data.now, + now=redis_get("timestamp"), stats=stats, x_values=x_values, y_values=y_values, @@ -383,30 +373,44 @@ def get_stats(): @login_required def get_account(account): """show bugs, cases and stats by for a given account""" - load_data() + cfg = set_cfg() stats = generate_stats(account) - comments = get_new_comments(new_comments_only=False, account=account) - - pie_stats = { - "by_severity": ( - list(stats["by_severity"].keys()), - list(stats["by_severity"].values()), - ), - "by_status": ( - list(stats["by_status"].keys()), - list(stats["by_status"].values()), - ), - } - + cards = redis_get("cards") + comments = get_new_comments(cards=cards, new_comments_only=False, account=account) + pie_stats = make_pie_dict(stats) histogram_stats = generate_histogram_stats(account) return render_template( "ui/account.html", page_title=account, account=account, - now=load_data.now, + now=redis_get("timestamp"), + stats=stats, + new_comments=comments, + jira_server=cfg["server"], + pie_stats=pie_stats, + histogram_stats=histogram_stats, + ) + + +@BP.route("/engineer/") +@login_required +def get_engineer(engineer): + """show bugs, cases and stats by for a given engineer""" + cfg = set_cfg() + cards = redis_get("cards") + stats = generate_stats(engineer=engineer) + comments = get_new_comments(cards=cards, new_comments_only=False, engineer=engineer) + pie_stats = make_pie_dict(stats) + histogram_stats = generate_histogram_stats(engineer=engineer) + return render_template( + "ui/account.html", + page_title=engineer, + account=engineer, + now=redis_get("timestamp"), stats=stats, new_comments=comments, - jira_server=load_data.jira_server, + jira_server=cfg["server"], pie_stats=pie_stats, histogram_stats=histogram_stats, + engineer_view=True, ) diff --git a/dashboard/src/t5gweb/utils.py b/dashboard/src/t5gweb/utils.py index 826bcfa..56a5184 100755 --- a/dashboard/src/t5gweb/utils.py +++ b/dashboard/src/t5gweb/utils.py @@ -276,3 +276,17 @@ def slack_notify(ini, blist): ) except SlackApiError as slack_error: logging.warning("failed to post to slack: %s", slack_error) + + +def make_pie_dict(stats): + """get the code simplified""" + return { + "by_severity": ( + list(stats["by_severity"].keys()), + list(stats["by_severity"].values()), + ), + "by_status": ( + list(stats["by_status"].keys()), + list(stats["by_status"].values()), + ), + }