Skip to content

Commit

Permalink
Refactor of display settings and display; UI tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
loganbibby committed Feb 28, 2023
1 parent 5ce29e4 commit aae9b73
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 122 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ Dictionary of views where the key is the name of the view (no spaces) and the va
* `jql`: JQL for the view
* `title`: Title to be shown
* `display`: See `ISSUE_DEFAULT_DISPLAY_SETTINGS`
* `allow_sorting`: Allows for sorting of issues (defaults to `True`)

If no default view is specified, a view will be added for open issues of the current user.

Expand All @@ -107,6 +108,7 @@ Defaults to `default`.

## Release History

* `0.8` - Refactor of display settings and display; UI tweaks
* `0.7` - Added new component displays; refactored display settings; minor UI tweaks
* `0.6` - Added auto refresh
* `0.5` - Added caching; refactored main view to use APIs
Expand Down
24 changes: 5 additions & 19 deletions jira_organizer/app.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from flask import Flask, g
from flask_caching import Cache

from .options import Data, ComponentDisplay, DisplaySettings
from .options import Data, DisplaySettings
from .jira.client import JiraClient


Expand All @@ -28,24 +28,10 @@
for view_name in list(app.config["ISSUE_VIEWS"].keys()):
view = app.config["ISSUE_VIEWS"][view_name]

display_settings = view.get("display", {})
display_defaults = app.config["ISSUE_DEFAULT_DISPLAY_SETTINGS"]

for key in ["flags", "other_statuses"]:
if key not in display_settings:
display_settings[key] = display_defaults.get(key, [])

for key in ["statuses", "priorities", "issue_types"]:
default = display_defaults.get(key, {})

if key not in display_settings:
display_settings[key] = default
else:
for child, value in default.items():
if child not in display_settings[key]:
display_settings[key][child] = value

view["display"] = display_settings
view["display"] = DisplaySettings(
view.get("display", {}),
app.config["ISSUE_DEFAULT_DISPLAY_SETTINGS"]
)

app.config["ISSUE_VIEWS"][view_name] = view

Expand Down
22 changes: 22 additions & 0 deletions jira_organizer/jira/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,28 @@ def priority_slug(self):
def issue_type_slug(self):
return slugify(self.issue_type)

def get_slug(self, type):
slug_property = f"{type}_slug"
if hasattr(self, slug_property):
return getattr(self, slug_property)

def should_display(self, key):
return hasattr(self, key) and getattr(self, key) is not None

def get_display(self, key):
if not hasattr(self, key):
return "<Not Available>"

value = getattr(self, key)

if isinstance(value, list):
return ", ".join(value)

if isinstance(value, dict) and "summary" in value:
return value["summary"]

return str(value)

def update(self, payload=None):
super().update(payload)

Expand Down
161 changes: 120 additions & 41 deletions jira_organizer/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,61 +35,140 @@ def dump(self):
json.dump(payload, fh)


class ComponentDisplay(object):
def __init__(self, slug, display, show_in_main=True, color=None, icon=None):
self.display = display
self.slug = slug
self.show_in_main = show_in_main
self.options = {}
self.defaults = {
"color": color,
"icon": icon
class DisplaySettings(object):
def __init__(self, view_settings, default_settings):
self.data = {
"flags": [],
"other_statuses": [],
"issue_id": {
"_": {
"singular_name": "Issue ID",
"plural_name": "Issue IDs",
"show_in_small": True,
}
},
"assignee": {
"_": {
"singular_name": "Current Assignee",
"icon": "fas fa-user-ninja",
},
},
"project": {
"_": {
"singular_name": "Project",
"plural_name": "Projects",
"icon": "fas fa-bookmark"
},
},
"version": {
"_": {
"singular_name": "Version",
"plural_name": "Versions",
"icon": "fas fa-code-branch"
},
},
"parent": {
"_": {
"singular_name": "Parent",
"plural_name": "Parents",
"icon": "fas fa-sitemap"
},
},
"labels": {
"_": {
"singular_name": "Labels",
"icon": "fas fa-tags"
},
},
"reporter": {
"_": {
"singular_name": "Reporter",
"plural_name": "Reporters",
"icon": "fas fa-user",
}
},
"status": {
"_": {
"singular_name": "Status",
"plural_name": "Statuses",
"show_in_small": True,
"icon": "far fa-bell",
}
},
"priority": {
"_": {
"singular_name": "Priority",
"plural_name": "Priorities",
"icon": "fas fa-exclamation-triangle"
}
},
"issue_type": {
"_": {
"singular_name": "Issue Type",
"plural_name": "Issue Types",
"icon": "fas fa-dot-circle",
"show_in_small": True,
},
},
}

def __call__(self, slug):
if slug in self.options:
return self.options[slug]
def update_data(settings):
for key, value in settings.items():
if key not in self.data or not isinstance(value, dict):
self.data[key] = value
continue

return self.set(slug)
for child_key, child_value in value.items():
if child_key not in self.data[key]:
self.data[key][child_key] = child_value
continue

def set(self, slug, override=True, **options):
for key, value in self.defaults.items():
if key not in options:
options[key] = value
self.data[key][child_key].update(child_value)

if not override and slug in self.options:
return self.options[slug]
update_data(default_settings)
update_data(view_settings)

self.options[slug] = options
def has_flag(self, flag):
return flag in self.data["flags"]

return self.options[slug]
def show_in_other(self, slug):
return slug in self.data["other_statuses"]

def build_options(self, view, defaults):
for key, options in view.get(self.slug, {}).items():
self.set(key, **options)
def get_type_settings(self, type_name):
return self.data.get(type_name, {})

for key, options in defaults.get(self.slug, {}).items():
self.set(key, override=False, **options)
def get_type_default_settings(self, type_name):
return self.get_type_settings(type_name).get("_", {})

def get_type_default_setting(self, type_name, key, default=None):
return self.get_type_default_settings(type_name).get(key, default)

class DisplaySettings(dict):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def get_name(self, type_name):
return self.get_type_default_setting(type_name, "singular_name")

defaults = (
("flags", []),
("other_statuses", []),
def get_plural_name(self, type_name):
return self.get_type_default_setting(
type_name, "plural_name",
self.get_name(type_name)
)

for key, default in defaults:
if key not in self:
self[key] = default
def get_settings_for_slug(self, type_name, slug):
return self.get_type_settings(type_name).get(slug, {})

def get_color(self, type_name, slug):
return self.get_settings_for_slug(type_name, slug).get(
"color",
self.get_type_default_setting(type_name, "color")
)

def add_component(self, component):
self[component.slug] = component
def get_icon(self, type_name, slug):
return self.get_settings_for_slug(type_name, slug).get(
"icon",
self.get_type_default_setting(type_name, "icon")
)

def add_flag(self, flag):
self["flags"].append(flag)
def show_in_small(self, type_name):
return self.get_type_default_setting(type_name, "show_in_small", False)

def add_other_status(self, slug):
self["other_statuses"].append(slug)
def show_type(self, type_name):
return type_name == "issue_id" or self.has_flag(f"show_{type_name}")
2 changes: 2 additions & 0 deletions jira_organizer/templates/organizer.html
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,15 @@
}, 1000);
}

{% if view["allow_sorting"]|default(True) %}
issuesContainer.sortable({
cursor: "move",
containment: "parent",
update: function(){
updateOrder();
}
});
{% endif %}

$("#show-hidden-link").click(function(){
hiddenContainer.toggle();
Expand Down
89 changes: 28 additions & 61 deletions jira_organizer/templates/partials/issue.html
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
{% macro show_issue_meta(icon=None, class=None, sm=False, tooltip=None) %}
<li class="list-inline-item small me-{% if sm %}3{% else %}4{% endif %}{% if class %} {{ class }}{% endif %}"{% if tooltip %} data-bs-toggle="tooltip" data-bs-title="{{ tooltip }}"{% endif %}>
{% macro show_issue_meta(type) %}
{% set slug = issue.get_slug(type) %}
{% set color = display.get_color(type, slug) %}
{% set icon = display.get_icon(type, slug) %}
{% set tooltip = display.get_name(type) %}
{%- if display.show_type(type) and (not sm or display.show_in_small(type)) -%}
<li class="list-inline-item small me-{% if sm %}3{% else %}4{% endif %}{% if color %} text-{{ color }}{% endif %}"{% if tooltip %} data-bs-toggle="tooltip" data-bs-title="{{ tooltip }}"{% endif %}>
{% if icon %}<i class="{{ icon }} pe-1"></i>{% endif %}
{{ caller() }}
</li>
{%- endif -%}
{% endmacro %}

{% if sm %}{% set list_me = 2 %}
{% else %}{% set list_me = 4 %}{% endif %}

<div class="issue-container card shadow mb-3 border-top border-{{ display.statuses.get(issue.status_slug, {}).get("color", "secondary") }}" data-id="{{ issue.jira_id }}" style="border-top-width: 4px !important">
<div class="issue-container card shadow mb-3 border-top border-{{ display.get_color('status', issue.get_slug('status')) }}" data-id="{{ issue.jira_id }}" style="border-top-width: 4px !important">
<div class="card-body">
{% if issue.is_new %}
<div class="float-end new-issue" data-bs-toggle="tooltip" data-bs-title="New issue">
Expand All @@ -21,64 +24,28 @@
<div class="row mb-0">
<div class="col-10">
<ul class="list-inline list-unstyled mb-0">
{% call show_issue_meta(sm=sm, tooltip="Jira Issue ID") %}
{% call show_issue_meta('issue_id') %}
<a href="https://{{ config['JIRA_SUBDOMAIN'] }}.atlassian.net/browse/{{ issue.id }}" target="_blank">
{{ issue.id }}
</a>
{% endcall %}
{% if 'show_issue_type' in display.flags %}
{% call show_issue_meta(display.issue_types.get(issue.issue_type_slug, {}).get("icon", "fas fa-dot-circle"), sm=sm, tooltip="Issue Type") %}
{{ issue.issue_type }}
{% endcall %}
{% endif %}
{% if 'show_assignee' in display.flags and not sm %}
{% call show_issue_meta("fas fa-user-ninja", sm=sm, tooltip="Current Assignee") %}
{% if issue.assignee %}{{ issue.assignee }}{% else %}Unassigned{% endif %}
{% endcall %}
{% endif %}
{% if 'show_status' in display.flags %}
{% if issue.status_slug in display.statuses %}
{% set class_name="text-" + display.statuses.get(issue.status_slug).get("color") %}
{% else %}{% set class_name = None %}{% endif %}
{% call show_issue_meta("far fa-bell", sm=sm, class=class_name, tooltip="Issue Status") %}
{{ issue.status }}
{% endcall %}
{% endif %}
{% if not sm %}
{% if 'show_project' in display.flags %}
{% call show_issue_meta("fas fa-bookmark", sm=sm, tooltip="Jira Project") %}
{{ issue.project }}
{% endcall %}
{% endif %}
{% if 'show_reporter' in display.flags %}
{% call show_issue_meta("fas fa-user", sm=sm, tooltip="Reporter") %}
{{ issue.reporter }}
{% endcall %}
{% endif %}
{% if 'show_priority' in display.flags %}
{% if issue.priority_slug in display.priorities %}
{% set class_name="text-" + display.priorities.get(issue.priority_slug).get("color") %}
{% else %}{% set class_name = None %}{% endif %}
{% call show_issue_meta("fas fa-exclamation-triangle", sm=sm, class=class_name, tooltip="Priority") %}
{{ issue.priority }}
{% endcall %}
{% endif %}
{% if 'show_version' in display.flags and issue.fix_versions|length %}
{% call show_issue_meta("fas fa-code-branch", sm=sm, tooltip="Version") %}
{{ issue.fix_versions | join(', ') }}
{% endcall %}
{% endif %}
{% if 'show_labels' in display.flags and issue.labels|length %}
{% call show_issue_meta("fas fa-tags", sm=sm, tooltip="Labels") %}
{{ issue.labels | join(', ') }}
{% endcall %}
{% endif %}
{% if 'show_parent' in display.flags and issue.parent %}
{% call show_issue_meta("fas fa-sitemap", sm=sm, tooltip="Parent") %}
{{ issue.parent.summary }}
{% endcall %}
{% endif %}
{% endif %}
{% call show_issue_meta("issue_type") %}{{ issue.issue_type }}{% endcall %}
{% call show_issue_meta('assignee') %}
{% if issue.assignee %}{{ issue.assignee }}{% else %}Unassigned{% endif %}
{% endcall %}
{% call show_issue_meta('status') %}{{ issue.status }}{% endcall %}
{% call show_issue_meta('project') %}{{ issue.project }}{% endcall %}
{% call show_issue_meta('reporter') %}{{ issue.reporter }}{% endcall %}
{% call show_issue_meta('priority') %}{{ issue.priority }}{% endcall %}
{% if issue.fix_versions|length %}{% call show_issue_meta('version') %}
{{ issue.fix_versions | join(', ') }}
{% endcall %}{% endif %}
{% if issue.labels|length %}{% call show_issue_meta('labels') %}
{{ issue.labels | join(', ') }}
{% endcall %}{% endif %}
{% if issue.parent %}{% call show_issue_meta('parent') %}
{{ issue.parent.summary }}
{% endcall %}{% endif %}
</ul><!-- ul -->
</div><!-- .col -->
<div class="col-2 text-end">
Expand All @@ -88,7 +55,7 @@
<i class="{% if hidden %}far fa-eye{% else %}fas fa-eye-slash{% endif %}"></i>
</a>
</li>
{% if not sm %}
{% if not sm and view["allow_sorting"]|default(True) %}
<li class="list-inline-item">
<a href="#" class="move-to-bottom" data-bs-toggle="tooltip" data-bs-title="Move to bottom">
<i class="fas fa-angle-double-down"></i>
Expand Down
Loading

0 comments on commit aae9b73

Please sign in to comment.