Available: | Jira 3.0 and later. |
Changed: | Jira 5.0 – added the |
| Jira 5.0 – added the IssueTabPanel2 API. |
| Jira 6.0 – added the IssueTabPanel3 API. |
| Jira 9.0 – added the PaginatedIssueTabPanel API. |
The issue tab panel plugin module allows you to add new tab panels to the View Issue screen.
You can add a new tab with a plugin displaying information about a single issue (most likely pulled from an external source).
Here is an example descriptor:
1 2<issue-tabpanel key="custom-issue-tabpanel" name="Custom Tab Panel" class="com.atlassian.plugins.tutorial.IssueTabCustom"> <description>Show a custom panel.</description> <label>Custom panel</label> <supports-ajax-load>true</supports-ajax-load> </issue-tabpanel>
For more information about the atlassian-plugin.xml
file, see the
Configuring the app descriptor page.
Here is a simple Java implementation:
1 2public class IssueTabCustom extends AbstractIssueTabPanel3 { @Override public boolean showPanel(ShowPanelRequest showPanelRequest) { return true; } @Override public List<IssueAction> getActions(GetActionsRequest getActionsRequest) { return Lists.newArrayList(new GenericMessageAction("first"), new GenericMessageAction( this.descriptor.getI18nBean().getText("com.atlassian.plugins.tutorial.custom.issue.tab.panel.example"))); } }
The module class specified in the class="..."
attribute must implement the IssueTabPanel3
interface.
To customize the look of your items, implement your own IssueAction.
For more details, see Loading Issue Tab Panels with AJAX.
If you are working on Jira 9.0 and later versions, you can implement progressive pagination
within your activity tab using PaginatedIssueTabPanel API.
The interface requires that all actions in your activity tab
are returning dates from IssueAction#getTimePerformed
.
Only tabs that support pagination (PaginatedIssueTabPanel#paginationSupported
returns true
)
will be displayed on the "All" Tab. Therefore, actions from tabs implementing
IssueTabPanel,
IssueTabPanel2
and IssueTabPanel3
will not be shown on "All" tab panel.
Similarly to how it's done in previous versions, you can add such tab with a plugin:
1 2<issue-tabpanel key="custom-issue-tabpanel" name="Custom Tab Panel" class="com.atlassian.plugins.tutorial.PaginatedIssueTabCustom"> <description>Show a custom panel.</description> <label>Custom panel</label> <supports-ajax-load>true</supports-ajax-load> <show-newer-expander-label>Load newer custom events</show-newer-expander-label> <show-older-expander-label>Load older custom events</show-older-expander-label> <show-all-newer-expander-label>load all newer custom events</show-all-newer-expander-label> <show-all-older-expander-label>load all older custom events</show-all-older-expander-label> </issue-tabpanel>
The <...-label>
tags contain labels for buttons in different variants.
As for the class itself, here is an example Java implementation:
1 2public class PaginatedIssueTabCustom implements PaginatedIssueTabPanel { IssueTabPanelModuleDescriptor descriptor; private static final Instant START_TIME = LocalDate.of(2022, 1, 1).atStartOfDay().toInstant(ZoneOffset.UTC); private static final List<IssueAction> ALL_ACTIONS = generateActions(START_TIME, 100); @Override public void init(IssueTabPanelModuleDescriptor descriptor) { this.descriptor = descriptor; } @Override public boolean showPanel(ShowPanelRequest request) { return true; } @Override public Page<IssueAction> getActions(GetActionsRequest request) { final Window<IssueAction> issueActions = searchActions(request); return new Page<IssueAction>() { @Override public boolean isFirstPage() { return !issueActions.hasElementsBefore(); } @Override public boolean isLastPage() { return !issueActions.hasElementsAfter(); } @Override public List<IssueAction> getPageContents() { return issueActions.get(); } }; } private Window<IssueAction> searchActions(GetActionsRequest request) { Optional<Integer> limit = request.isShowAll() ? Optional.empty() : Optional.of(request.getBatch().getShowMax()); if (request.getBatch().getFetchMode() == FROM_OLDEST) { return searchFromOldest(limit, ALL_ACTIONS); } else if (request.getBatch().getFetchMode() == FROM_NEWEST) { return searchFromNewest(limit, ALL_ACTIONS); } else if (request.getBatch().getFetchMode() == NEWER_THAN_DATE) { return searchNewerThanDate(limit, request.getBatch().getFromDate(), ALL_ACTIONS); } else if (request.getBatch().getFetchMode() == OLDER_THAN_DATE) { return searchOlderThanDate(limit, request.getBatch().getFromDate(), ALL_ACTIONS); } else { throw new IllegalArgumentException(); } } private static <T> Window<T> searchFromOldest(Optional<Integer> limit, List<T> fullList) { if (limit.isPresent()) { return Window.of(fullList).shrinkFromEnd(limit.get()); } else { return Window.of(fullList); } } private static <T> Window<T> searchFromNewest(Optional<Integer> limit, List<T> fullList) { if (limit.isPresent()) { return Window.of(fullList).shrinkFromStart(limit.get()); } else { return Window.of(fullList); } } private static <T extends IssueAction> Window<T> searchNewerThanDate(Optional<Integer> limit, Date from, List<T> fullList) { final Window<T> filtered = Window.of(fullList).dropUntil(elem -> elem.getTimePerformed().after(from)); return limit.map(filtered::shrinkFromEnd).orElse(filtered); } private static <T extends IssueAction> Window<T> searchOlderThanDate(Optional<Integer> limit, Date from, List<T> fullList) { final Window<T> filtered = Window.of(fullList).keepUntil(elem -> elem.getTimePerformed().before(from)); return limit.map(filtered::shrinkFromStart).orElse(filtered); } private static List<IssueAction> generateActions(Instant from, int howMany) { return IntStream.range(0, howMany) .mapToObj(i -> from.plus(i, ChronoUnit.MINUTES)) .map(Action::new) .collect(Collectors.toList()); } private static class Action implements IssueAction { private final Instant time; public Action(Instant time) { this.time = time; } @Override public String getHtml() { return String.format("<div class=\"issue-data-block\">Issue action at %s</div>", time.toString()); } @Override public Date getTimePerformed() { return Date.from(time); } @Override public boolean isDisplayActionAllTab() { return false; } } }
As for the front-end, you need to register your tab using jira/activity-tabs/items-lazy-loader
dependency.
1 2const WRMRequire = require("wrm/require"); WRMRequire('wr!com.atlassian.jira.jira-frontend-plugin:entrypoint-activityTabs').then(function() { require('jira/activity-tabs/items-lazy-loader').then(function(itemsLazyLoader) { itemsLazyLoader.registerTab('paginatedreference-tabpanel', { version: 1 }); }); });
The version
argument here is to prevent unexpected behavior from happening once we add more features to the interface.
For this interface to work, you also must include .issue-data-block
class in all your action items HTML.
Rate this page: