diff --git a/README.md b/README.md index 53751ff..91865c5 100644 --- a/README.md +++ b/README.md @@ -42,37 +42,44 @@ A Personal Access Token (PAT) created from [here](https://id.atlassian.com/manag API endpoint for Jira. Automatically set if not defined. -### `ISSUE_DEFAULT_STATUS_COLORS` +### `ISSUE_DEFAULT_DISPLAY_SETTINGS` -Dictionary of colors for each status where the key is sluggified status name and the value is the color. +These settings are the defaults for how the organizer displays issues. This same dictionary format is used for defining a view's `display` settings. -You can override this in `ISSUE_VIEWS` with `status_colors`. +#### `flags` -### `ISSUE_DEFAULT_PRIORITY_COLORS` +List of flags for displaying components in the issue: -Dictionary of Bootstrap color classes for each priority where the key is the sluggified priority name and the class name. + * `show_status`: Shows the issue's status + * `show_issue_type`: Shows the issue type + * `show_reporter`: Shows the name of the report (only on the main column) + * `show_project`: Shows the project name (only on the main column) + * `show_assignee`: Shows the assignee name (only on the main column) + * `show_priority`: Shows the priority (only on the main column) + * `show_labels`: Shows a comma separated list of labels (only on the main column) + * `show_parent`: Shows the parent Jira information (only on the main column) -Defaults to `danger` for `high` and `very_high`, `warning` for `medium`, and `primary` for `low` and `lowest`. +#### `other_statuses` -You can override this in `ISSUE_VIEWS` with `priority_colors`. +List of sluggified status names to be shown in the Other column. -### `ISSUE_DEFAULT_OTHER_STATUSES` +#### `statuses` -List of sluggified status names to be shown in the Other column. +Dictionary of settings for each status where the key is sluggified status name and the value is a dictionary: -You can override this in `ISSUE_VIEWS` with `other_statuses`. + * `color`: Bootstrap color class -### `ISSUE_DEFAULT_DISPLAY_FLAGS` +#### `priorities` -Dictionary of display flags where the key is an option from below and the value is a boolean. +Dictionary of settings for each priority where the key is sluggified priority name and the value is a dictionary: - * `show_status`: Shows the issue's status - * `show_reporter`: Shows the name of the report (only on the main column) - * `show_project`: Shows the project name (only on the main column) - * `show_assignee`: Shows the assignee name (only on the main column) - * `show_priority`: Shows the priority (only on the main column) + * `color`: Bootstrap color class + +#### `issue_types` + +Dictionary of settings for each issue type where the key is sluggified issue type and the value is a dictionary: -You can override this in `ISSUE_VIEWS` with `display_flags`. + * `icon`: Font Awesome 5 Free icon class ### `ISSUE_VIEWS` @@ -80,10 +87,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_flags`: See `ISSUE_DEFAULT_DISPLAY_FLAGS` - * `other_statuses`: See `ISSUE_DEFAULT_OTHER_STATUSES` - * `priority_colors`: See `ISSUE_DEFAULT_PRIORITY_COLORS` - * `status_colors`: See `ISSUE_DEFAULT_STATUS_COLORS` + * `display`: See `ISSUE_DEFAULT_DISPLAY_SETTINGS` If no default view is specified, a view will be added for open issues of the current user. @@ -103,6 +107,7 @@ Defaults to `default`. ## Release History +* `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 * `0.4` - Added multiple views; various cosmetic changes diff --git a/jira_organizer/app.py b/jira_organizer/app.py index d3bbd7e..6fcb3f2 100644 --- a/jira_organizer/app.py +++ b/jira_organizer/app.py @@ -1,6 +1,7 @@ from flask import Flask, g from flask_caching import Cache -from .options import Data + +from .options import Data, ComponentDisplay, DisplaySettings from .jira.client import JiraClient @@ -27,9 +28,24 @@ for view_name in list(app.config["ISSUE_VIEWS"].keys()): view = app.config["ISSUE_VIEWS"][view_name] - for key in ["display_flags", "other_statuses", "status_colors", "priority_colors"]: - if key not in view: - view[key] = app.config[f"ISSUE_DEFAULT_{key.upper()}"] + 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 app.config["ISSUE_VIEWS"][view_name] = view diff --git a/jira_organizer/config-sample.py b/jira_organizer/config-sample.py index 5c1eb6f..e704717 100644 --- a/jira_organizer/config-sample.py +++ b/jira_organizer/config-sample.py @@ -21,25 +21,33 @@ # # Organizer -ISSUE_VIEWS = {} - -ISSUE_DEFAULT_DISPLAY_FLAGS = [ - "show_status", - "show_reporter", - "show_priority" -] +AUTO_REFRESH = 60 -ISSUE_DEFAULT_OTHER_STATUSES = [] - -# -# Display - -ISSUE_DEFAULT_STATUS_COLORS = {} +ISSUE_VIEWS = {} -ISSUE_DEFAULT_PRIORITY_COLORS = { - "high": "danger", - "very_high": "danger", - "medium": "warning", - "low": "primary", - "lowest": "primary", +ISSUE_DEFAULT_DISPLAY_SETTINGS = { + "flags": [ + "show_status", + "show_reporter", + "show_priority" + ], + "other_statuses": [], + "priorities": { + "high": { + "color": "danger", + }, + "very_high": { + "color": "danger", + }, + "medium": { + "color": "warning", + }, + "low": { + "color": "primary", + }, + "lowest": { + "color": "primary", + }, + } } + diff --git a/jira_organizer/jira/models.py b/jira_organizer/jira/models.py index e870ac8..2186d9b 100644 --- a/jira_organizer/jira/models.py +++ b/jira_organizer/jira/models.py @@ -56,6 +56,17 @@ def to_dict(self): return data +class NestedJiraIssue(JiraObject): + field_mapping = { + "jira_id": "id", + "id": "key", + "priority": "fields.priority.name", + "summary": "fields.summary", + "status": "fields.status.name", + "issue_type": "fields.issuetype.name", + } + + class JiraIssue(JiraObject): field_mapping = { "jira_id": "id", @@ -67,12 +78,31 @@ class JiraIssue(JiraObject): "issue_type": "fields.issuetype.name", "project": "fields.project.name", "assignee": "fields.assignee.displayName", + "fix_versions": "fields.fixVersions", + "labels": "fields.labels", + "parent": "fields.parent", } + @property + def status_slug(self): + return slugify(self.status) + @property def priority_slug(self): return slugify(self.priority) + @property + def issue_type_slug(self): + return slugify(self.issue_type) + + def update(self, payload=None): + super().update(payload) + + self.fix_versions = [d["name"] for d in self.fix_versions] + + if hasattr(self, "parent"): + self.parent = NestedJiraIssue(self.parent) + class JiraStatus(JiraObject): field_mapping = { diff --git a/jira_organizer/options.py b/jira_organizer/options.py index 1e0cda2..cc24f19 100644 --- a/jira_organizer/options.py +++ b/jira_organizer/options.py @@ -33,3 +33,63 @@ def dump(self): with open(self.filename, "w+", encoding="utf-8") as fh: 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 + } + + def __call__(self, slug): + if slug in self.options: + return self.options[slug] + + return self.set(slug) + + def set(self, slug, override=True, **options): + for key, value in self.defaults.items(): + if key not in options: + options[key] = value + + if not override and slug in self.options: + return self.options[slug] + + self.options[slug] = options + + return self.options[slug] + + def build_options(self, view, defaults): + for key, options in view.get(self.slug, {}).items(): + self.set(key, **options) + + for key, options in defaults.get(self.slug, {}).items(): + self.set(key, override=False, **options) + + +class DisplaySettings(dict): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + defaults = ( + ("flags", []), + ("other_statuses", []), + ) + + for key, default in defaults: + if key not in self: + self[key] = default + + def add_component(self, component): + self[component.slug] = component + + def add_flag(self, flag): + self["flags"].append(flag) + + def add_other_status(self, slug): + self["other_statuses"].append(slug) diff --git a/jira_organizer/templates/partials/issue.html b/jira_organizer/templates/partials/issue.html index de9726f..23dcf49 100644 --- a/jira_organizer/templates/partials/issue.html +++ b/jira_organizer/templates/partials/issue.html @@ -1,6 +1,6 @@ -{% macro show_issue_meta(icon, class=None, sm=False, tooltip=None) %} +{% macro show_issue_meta(icon=None, class=None, sm=False, tooltip=None) %}
  • - + {% if icon %}{% endif %} {{ caller() }}
  • {% endmacro %} @@ -8,7 +8,7 @@ {% if sm %}{% set list_me = 2 %} {% else %}{% set list_me = 4 %}{% endif %} -
    +
    {% if issue.is_new %}
    @@ -21,41 +21,63 @@
      - {% call show_issue_meta(2, sm=sm, tooltip="Jira Issue ID") %} + {% call show_issue_meta(sm=sm, tooltip="Jira Issue ID") %} {{ issue.id }} {% endcall %} - {% if 'show_assignee' in display_flags and not sm %} + {% 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 %} - {% call show_issue_meta("far fa-bell", sm=sm, tooltip="Issue Status") %} + {% 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 %} + {% 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 %} + {% 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 priority_colors %} - {% set class_name="text-" + priority_colors[issue.priority_slug] %} - {% else %}{% set class_name = None %} - {% 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 %}
    diff --git a/jira_organizer/views.py b/jira_organizer/views.py index 99a0af2..f37026b 100644 --- a/jira_organizer/views.py +++ b/jira_organizer/views.py @@ -36,7 +36,7 @@ def _get_issues(view_name, force=False): sorted_ids.append(issue.jira_id) continue - if slugify(issue.status) in app.config["ISSUE_DEFAULT_OTHER_STATUSES"]: + if slugify(issue.status) in view["display"]["other_statuses"]: columns["other"].append(issue) sorted_ids.append(issue.jira_id) continue