From aae9b73450cabbee0a5c5fee071381f367ae79c6 Mon Sep 17 00:00:00 2001 From: Logan Bibby Date: Tue, 28 Feb 2023 09:04:01 -0600 Subject: [PATCH] Refactor of display settings and display; UI tweaks --- README.md | 2 + jira_organizer/app.py | 24 +-- jira_organizer/jira/models.py | 22 +++ jira_organizer/options.py | 161 ++++++++++++++----- jira_organizer/templates/organizer.html | 2 + jira_organizer/templates/partials/issue.html | 89 ++++------ jira_organizer/views.py | 4 +- 7 files changed, 182 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index 91865c5..96e7ae1 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 diff --git a/jira_organizer/app.py b/jira_organizer/app.py index 6fcb3f2..11eec4f 100644 --- a/jira_organizer/app.py +++ b/jira_organizer/app.py @@ -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 @@ -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 diff --git a/jira_organizer/jira/models.py b/jira_organizer/jira/models.py index 2186d9b..b8faa74 100644 --- a/jira_organizer/jira/models.py +++ b/jira_organizer/jira/models.py @@ -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 "" + + 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) diff --git a/jira_organizer/options.py b/jira_organizer/options.py index cc24f19..a99ab2e 100644 --- a/jira_organizer/options.py +++ b/jira_organizer/options.py @@ -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}") diff --git a/jira_organizer/templates/organizer.html b/jira_organizer/templates/organizer.html index cf175b2..27e6830 100644 --- a/jira_organizer/templates/organizer.html +++ b/jira_organizer/templates/organizer.html @@ -177,6 +177,7 @@ }, 1000); } + {% if view["allow_sorting"]|default(True) %} issuesContainer.sortable({ cursor: "move", containment: "parent", @@ -184,6 +185,7 @@ updateOrder(); } }); + {% endif %} $("#show-hidden-link").click(function(){ hiddenContainer.toggle(); diff --git a/jira_organizer/templates/partials/issue.html b/jira_organizer/templates/partials/issue.html index 23dcf49..ba7c10d 100644 --- a/jira_organizer/templates/partials/issue.html +++ b/jira_organizer/templates/partials/issue.html @@ -1,14 +1,17 @@ -{% macro show_issue_meta(icon=None, class=None, sm=False, tooltip=None) %} -
  • +{% 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)) -%} +
  • {% if icon %}{% endif %} {{ caller() }}
  • + {%- endif -%} {% endmacro %} -{% if sm %}{% set list_me = 2 %} -{% else %}{% set list_me = 4 %}{% endif %} - -
    +
    {% if issue.is_new %}
    @@ -21,64 +24,28 @@
      - {% call show_issue_meta(sm=sm, tooltip="Jira Issue ID") %} + {% call show_issue_meta('issue_id') %} {{ issue.id }} {% 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 %}