Skip to content

Commit

Permalink
Add the demo plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
matejak committed Jun 16, 2023
1 parent e72e942 commit 9540eda
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 40 deletions.
11 changes: 9 additions & 2 deletions estimage/inidata.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ class IniAppdata(IniStorage):
RETROSPECTIVE_PERIOD: typing.Container[datetime.datetime] = (None, None)
RETROSPECTIVE_QUARTER: str = ""
PROJECTIVE_QUARTER: str = ""
DAY_INDEX: int = 0
DATADIR: pathlib.Path = pathlib.Path(".")

@classmethod
Expand All @@ -128,17 +129,23 @@ def _get_default_projective_quarter(self):
def _get_default_retrospective_quarter(self):
raise NotImplementedError()

def save(self):
to_save = dict()
def _save_retrospective_period(self, to_save):
to_save["RETROSPECTIVE_PERIOD"] = dict(
start=self.RETROSPECTIVE_PERIOD[0],
end=self.RETROSPECTIVE_PERIOD[1],
)

def _save_quarters(self, to_save):
to_save["QUARTERS"] = dict(
projective=self.PROJECTIVE_QUARTER,
retrospective=self.RETROSPECTIVE_QUARTER,
)

def save(self):
to_save = dict()
self._save_retrospective_period(to_save)
self._save_quarters(to_save)

with self._manipulate_existing_config(self.CONFIG_FILENAME) as config:
config.update(to_save)

Expand Down
54 changes: 54 additions & 0 deletions estimage/plugins/demo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import re
import datetime
import dataclasses
import collections
import dateutil.relativedelta
import json

from ... import simpledata, data, persistence
from .. import jira


def load_data():
try:
with open("/tmp/estimage_demo.json") as f:
ret = json.loads(f.read())
except Exception:
ret = dict()
return ret


def save_data(what):
old = load_data()
old.update(what)
with open("/tmp/estimage_demo.json", "w") as f:
json.dump(old, f)


class NotToday:
@property
def DDAY_LABEL(self):
return "Week from now"

def get_date_of_dday(self):
data = load_data()
day_index = data.get("day_index", 0)
return datetime.datetime.today() + datetime.timedelta(days=day_index)


EXPORTS = dict(
MPLPointPlot="NotToday",
MPLVelocityPlot="NotToday",
)


QUARTER_TO_MONTH_NUMBER = None
PROJECT_NAME = "OPENSCAP"


TEMPLATE_OVERRIDES = {
}


def get_not_finished_targets(targets):
return [t for t in targets if t.state in (data.State.todo, data.State.in_progress)]
8 changes: 8 additions & 0 deletions estimage/plugins/demo/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from flask_wtf import FlaskForm
import wtforms


class DemoForm(FlaskForm):
issues = wtforms.RadioField('Issues')
progress = wtforms.FloatField('Progress')
submit = wtforms.SubmitField("Next Day")
27 changes: 27 additions & 0 deletions estimage/plugins/demo/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import flask
import flask_login

from ...webapp import web_utils
from . import forms
from . import get_not_finished_targets, load_data, save_data

bp = flask.Blueprint("demo", __name__, template_folder="templates")


@bp.route('/demo', methods=("GET", "POST"))
@flask_login.login_required
def next_day():
cls, loader = web_utils.get_retro_loader()
targets_by_id = loader.get_loaded_targets_by_id()
targets = get_not_finished_targets(targets_by_id.values())

form = forms.DemoForm()
choices = [(t.name, t.title) for t in targets]
form.issues.choices = choices
if form.validate_on_submit():
old_plugin_data = load_data()
old_plugin_data["day_index"] = old_plugin_data.get("day_index", 0) + 1
save_data(old_plugin_data)

return web_utils.render_template(
'demo.html', title='Demo Plugin', plugin_form=form, day_index=0)
16 changes: 16 additions & 0 deletions estimage/plugins/demo/templates/demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{% extends "base.html" %}

{% import "utils.j2" as utils %}
{% from 'bootstrap5/form.html' import render_form %}

{% block content %}
<div class="container">
<div class="row">
<h1>Execution Demo</h1>
<p>Day {{ day_index + 1 }}</p>
{{ render_form(plugin_form, action=url_for("demo.next_day")) }}
</div>
</div>
{% endblock %}


25 changes: 25 additions & 0 deletions estimage/plugins/demo/tests/test_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import pytest

from estimage import plugins, data, PluginResolver, persistence
from estimage.data import BaseTarget
import estimage.plugins.demo as tm

from tests.test_target import base_target_load_save, fill_target_instance_with_stuff, assert_targets_are_equal
from tests.test_inidata import temp_filename, targetio_inifile_cls


@pytest.fixture
def some_targets():
a = BaseTarget("a")
a.state = data.State.todo
b = BaseTarget("b")
b.state = data.State.in_progress
c = BaseTarget("c")
d = BaseTarget("d")
d.state = data.State.done
return [a, b, c, d]


def test_select_tasks_not_finished(some_targets):
assert not tm.get_not_finished_targets([])
assert len(tm.get_not_finished_targets(some_targets)) == 2
33 changes: 19 additions & 14 deletions estimage/visualize/burndown.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from ..entities.target import State
from . import utils
from .. import history
from .. import history, PluginResolver


class StatusStyle(typing.NamedTuple):
Expand All @@ -14,20 +14,25 @@ class StatusStyle(typing.NamedTuple):
label: str


@PluginResolver.class_is_extendable("MPLPointPlot")
class MPLPointPlot:
STYLES = (
StatusStyle(status=State.todo, color=(0.1, 0.1, 0.5, 1), label="To Do"),
StatusStyle(status=State.in_progress, color=(0.1, 0.1, 0.6, 0.8), label="In Progress"),
StatusStyle(status=State.review, color=(0.1, 0.2, 0.7, 0.6), label="Needs Review"),
)
DDAY_LABEL = "today"

def __init__(self, a: history.Aggregation):
self.aggregation = a
self.status_arrays = np.zeros((len(self.STYLES), a.days))
today_date = datetime.datetime.today()
self.index_of_today = history.days_between(self.aggregation.start, today_date)
dday_date = self.get_date_of_dday()
self.index_of_dday = history.days_between(self.aggregation.start, dday_date)
self.width = 1.0

def get_date_of_dday(self):
return datetime.datetime.today()

def _prepare_plots(self):
for index, style in enumerate(self.STYLES):
for r in self.aggregation.repres:
Expand All @@ -38,9 +43,9 @@ def _show_plan(self, ax):
ax.plot(self.aggregation.get_plan_array(), color="orange",
linewidth=self.width, label="burndown")

def _show_today(self, ax):
if self.aggregation.start <= datetime.datetime.today() <= self.aggregation.end:
ax.axvline(self.index_of_today, label="today", color="grey", linewidth=self.width * 2)
def _show_dday(self, ax):
if self.aggregation.start <= self.get_date_of_dday() <= self.aggregation.end:
ax.axvline(self.index_of_dday, label=self.DDAY_LABEL, color="grey", linewidth=self.width * 2)

def _plot_prepared_arrays(self, ax):
days = np.arange(self.aggregation.days)
Expand All @@ -52,12 +57,12 @@ def _plot_prepared_arrays(self, ax):

def _plot_data_with_termination(self, ax, array, bottom, style):
days = np.arange(self.aggregation.days)
if 0 <= self.index_of_today < len(days):
up_until_today = slice(0, self.index_of_today + 1)
today = self.index_of_today
array = utils.insert_element_into_array_after(array[up_until_today], today, 0)
bottom = utils.insert_element_into_array_after(bottom[up_until_today], today, 0)
days = utils.insert_element_into_array_after(days[up_until_today], today, today)
if 0 <= self.index_of_dday < len(days):
up_until_dday = slice(0, self.index_of_dday + 1)
dday = self.index_of_dday
array = utils.insert_element_into_array_after(array[up_until_dday], dday, 0)
bottom = utils.insert_element_into_array_after(bottom[up_until_dday], dday, 0)
days = utils.insert_element_into_array_after(days[up_until_dday], dday, dday)
ax.fill_between(days, array + bottom, bottom, label=style.label,
color=style.color, edgecolor="white", linewidth=self.width * 0.5)

Expand All @@ -70,7 +75,7 @@ def get_figure(self):
self._prepare_plots()
self._plot_prepared_arrays(ax)
self._show_plan(ax)
self._show_today(ax)
self._show_dday(ax)
ax.legend(loc="upper right")

utils.x_axis_weeks_and_months(ax, self.aggregation.start, self.aggregation.end)
Expand All @@ -86,7 +91,7 @@ def get_small_figure(self):
self._prepare_plots()
self._plot_prepared_arrays(ax)
self._show_plan(ax)
self._show_today(ax)
self._show_dday(ax)

ax.set_axis_off()
fig.subplots_adjust(0, 0, 1, 1)
Expand Down
15 changes: 0 additions & 15 deletions estimage/visualize/completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,6 @@ def get_figure(self):

return fig

def get_small_figure(self):
plt = utils.get_standard_pyplot()

fig, ax = plt.subplots()

self._prepare_plots()
self._plot_prepared_arrays(ax)
self._show_plan(ax)
self._show_today(ax)

ax.set_axis_off()
fig.subplots_adjust(0, 0, 1, 1)

return fig

def plot_stuff(self):
plt = utils.get_standard_pyplot()
self.get_figure()
Expand Down
13 changes: 9 additions & 4 deletions estimage/visualize/velocity.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@
import numpy as np

from . import utils
from .. import history
from .. import history, PluginResolver


class TierStyle(typing.NamedTuple):
label: str
color: tuple


@PluginResolver.class_is_extendable("MPLVelocityPlot")
class MPLVelocityPlot:
TIER_STYLES = (
TierStyle(label="", color=(0.1, 0.1, 0.6, 1)),
)
DDAY_LABEL = "today"

def __init__(self, a: typing.Iterable[history.Aggregation]):
try:
Expand Down Expand Up @@ -58,6 +60,9 @@ def _plot_velocity_tier(self, ax, data, tier):
self.days, data, color=tier_style.color,
label=f"{tier_style.label} Velocity Fit")

def get_date_of_dday(self):
return datetime.datetime.today()

def get_figure(self, cutoff_date):
plt = utils.get_standard_pyplot()

Expand All @@ -77,9 +82,9 @@ def get_figure(self, cutoff_date):
self.days, all_tiers_rolling_velocity * days_in_real_week,
color="orange", label="Rolling velocity estimate")

index_of_today = history.days_between(self.start, datetime.datetime.today())
if 0 <= index_of_today <= len(self.days):
ax.axvline(index_of_today, label="today", color="grey", linewidth=2)
index_of_dday = history.days_between(self.start, self.get_date_of_dday())
if 0 <= index_of_dday <= len(self.days):
ax.axvline(index_of_dday, label=self.DDAY_LABEL, color="grey", linewidth=2)

ax.legend(loc="upper center")
utils.x_axis_weeks_and_months(ax, self.start, self.end)
Expand Down
1 change: 0 additions & 1 deletion estimage/webapp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ def create_app(config_class=config.Config):
config_class.DATADIR = pathlib.Path(app.config["DATA_DIR"])
app.config.from_object(config.read_or_create_config(simpledata.AppData))
app.config["classes"] = app.plugin_resolver.class_dict

plugins_dict = {name: plugins.get_plugin(name) for name in app.config["PLUGINS"]}

app.set_plugins_dict(plugins_dict)
Expand Down
2 changes: 1 addition & 1 deletion estimage/webapp/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Config:
GOOGLE_DISCOVERY_URL = (
"https://accounts.google.com/.well-known/openid-configuration"
)
PLUGINS = os.environ.get("PLUGINS", "").split(",")
PLUGINS = os.environ.get("PLUGINS", "").split(",") or []


def read_or_create_config(cls):
Expand Down
9 changes: 6 additions & 3 deletions estimage/webapp/vis/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def visualize_velocity(epic_name):
all_events.load()

start, end = flask.current_app.config["RETROSPECTIVE_PERIOD"]
velocity_class = flask.current_app.config["classes"]["MPLVelocityPlot"]

if epic_name == ".":
target_tree = utilities.reduce_subsets_from_sets(list(all_targets.values()))
Expand All @@ -115,7 +116,7 @@ def visualize_velocity(epic_name):
cutoff_date = min(datetime.datetime.today(), end)

matplotlib.use("svg")
fig = velocity.MPLVelocityPlot(aggregation).get_figure(cutoff_date)
fig = velocity_class(aggregation).get_figure(cutoff_date)
fig.set_size_inches(* NORMAL_FIGURE_SIZE)
return send_figure_as_svg(fig, epic_name)

Expand Down Expand Up @@ -207,12 +208,14 @@ def output_burndown(target_tree, size):
aggregation = history.Aggregation.from_targets(target_tree, start, end)
aggregation.process_event_manager(all_events)

burndown_class = flask.current_app.config["classes"]["MPLPointPlot"]

matplotlib.use("svg")
if size == "small":
fig = burndown.MPLPointPlot(aggregation).get_small_figure()
fig = burndown_class(aggregation).get_small_figure()
fig.set_size_inches(* SMALL_FIGURE_SIZE)
else:
fig = burndown.MPLPointPlot(aggregation).get_figure()
fig = burndown_class(aggregation).get_figure()
fig.set_size_inches(* NORMAL_FIGURE_SIZE)

basename = flask.request.path.split("/")[-1]
Expand Down

0 comments on commit 9540eda

Please sign in to comment.