Skip to content

Commit

Permalink
Merge pull request #1221 from riginoommen/jira-plugin-enhancements
Browse files Browse the repository at this point in the history
issue#1084 - Included the JIRA Issues VIew with Jira Plugin
  • Loading branch information
Xantier authored Jan 17, 2024
2 parents aee4af6 + ab90aa6 commit 130e992
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 52 deletions.
5 changes: 5 additions & 0 deletions .changeset/swift-badgers-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@roadiehq/backstage-plugin-jira': minor
---

Included the Issues view for the JIRA Plugin
2 changes: 2 additions & 0 deletions plugins/frontend/backstage-plugin-jira/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Jira Plugin for Backstage

![a Jira plugin for Backstage](./docs/jira-plugin.gif).
![a Jira plugin Issues View for Backstage](./docs/jira-plugin-issues-view.png).

## Features

- Show project details and tasks
- View JIRA issues of the Project
- Activity Stream

## How to add Jira project dependency to Backstage app
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 36 additions & 14 deletions plugins/frontend/backstage-plugin-jira/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ import {
IdentityApi,
} from '@backstage/core-plugin-api';
import {
IssueCountElement,
IssueCountResult,
IssueCountSearchParams,
IssuesCounter,
IssueType,
Project,
Status,
Ticket,
} from '../types';
import fetch from 'cross-fetch';

Expand Down Expand Up @@ -96,7 +96,16 @@ export class JiraAPI {
const data = {
jql,
maxResults: -1,
fields: ['key', 'issuetype'],
fields: [
'key',
'issuetype',
'summary',
'status',
'assignee',
'priority',
'created',
'updated',
],
startAt,
};

Expand Down Expand Up @@ -146,7 +155,7 @@ export class JiraAPI {
`;

let startAt: number | undefined = 0;
const issues: IssueCountElement[] = [];
const issues: Ticket[] = [];

while (startAt !== undefined) {
const res: IssueCountResult = await this.pagedIssueCountRequest(
Expand Down Expand Up @@ -240,7 +249,14 @@ export class JiraAPI {
// to fetch also the issue-keys of all the tasks for that component.
let issuesCounter: IssuesCounter[] = [];
let ticketIds: string[] = [];

let tickets: Ticket[] = [];
const foundIssues = await this.getIssueCountPaged({
apiUrl,
projectKey,
component,
label,
statusesNames,
});
if (!component && !label) {
// Generate counters for each issue type
const issuesTypes = project.issueTypes.map((status: IssueType) => ({
Expand Down Expand Up @@ -275,17 +291,9 @@ export class JiraAPI {
total: 0,
}),
);
const foundIssues = await this.getIssueCountPaged({
apiUrl,
projectKey,
component,
label,
statusesNames,
});

issuesCounter = foundIssues
.reduce((prev, curr) => {
const name = curr.fields.issuetype.name;
const name = curr.fields?.issuetype.name;
const idx = issuesTypes.findIndex(i => i.name === name);
if (idx !== -1) {
issuesTypes[idx].total++;
Expand All @@ -296,7 +304,20 @@ export class JiraAPI {

ticketIds = foundIssues.map(i => i.key);
}

tickets = foundIssues.map(index => {
return {
key: index.key,
summary: index?.fields?.summary,
assignee: {
displayName: index?.fields?.assignee?.displayName,
avatarUrl: index?.fields?.assignee?.avatarUrls['48x48'],
},
status: index?.fields?.status?.name,
priority: index?.fields?.priority,
created: index?.fields?.created,
updated: index?.fields?.updated,
};
});
return {
project: {
name: project.name,
Expand All @@ -311,6 +332,7 @@ export class JiraAPI {
}))
: [],
ticketIds: ticketIds,
tickets,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ describe('JiraCard', () => {
expect.stringContaining('mocked_icon_filename.gif'),
);
expect(
await rendered.findByText(/filter issue status/),
await rendered.findByText(/Filter issue status/),
).toBeInTheDocument();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import {
Avatar,
Box,
Expand All @@ -28,6 +28,14 @@ import {
MenuItem,
Checkbox,
Divider,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TablePagination,
Button,
} from '@material-ui/core';
import Alert from '@material-ui/lab/Alert';
import { InfoCard, Progress } from '@backstage/core-components';
Expand All @@ -39,6 +47,7 @@ import { ActivityStream } from './components/ActivityStream';
import { Selectors } from './components/Selectors';
import { useEmptyIssueTypeFilter } from '../../hooks/useEmptyIssueTypeFilter';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import ArrowForwardIcon from '@material-ui/icons/ArrowForward';

const useStyles = makeStyles((theme: Theme) =>
createStyles({
Expand All @@ -55,6 +64,14 @@ const useStyles = makeStyles((theme: Theme) =>
marginTop: theme.spacing(1),
},
},
ticketLink: {
color: theme.palette.link,
textDecoration: 'none',
transition: 'color 0.3s',
'&:hover': {
color: theme.palette.linkHover,
},
},
}),
);

Expand Down Expand Up @@ -87,6 +104,7 @@ export const JiraCard = (props: EntityProps & JiraCardOptionalProps) => {
const {
project,
issues,
tickets,
ticketIds,
projectLoading,
projectError,
Expand All @@ -99,6 +117,12 @@ export const JiraCard = (props: EntityProps & JiraCardOptionalProps) => {
} = useEmptyIssueTypeFilter(issues);

const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(5);

useEffect(() => {
setPage(0);
}, [tickets]);

const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
Expand All @@ -114,41 +138,52 @@ export const JiraCard = (props: EntityProps & JiraCardOptionalProps) => {
title="Jira"
subheader={
project && (
<Box>
<CardProjectDetails project={project} component={component} />
<Box display="inline-flex" pl={1}>
<IconButton
aria-label="more"
aria-controls="long-menu"
aria-haspopup="true"
onClick={handleClick}
>
<MoreVertIcon />
</IconButton>
<Menu
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
<Box
display="flex"
justifyContent="space-between"
width="100%"
alignItems="center"
>
<Box>
<CardProjectDetails project={project} component={component} />
<Box display="inline-flex" pl={1}>
<IconButton
aria-label="more"
aria-controls="long-menu"
aria-haspopup="true"
onClick={handleClick}
>
<MoreVertIcon />
</IconButton>
<Menu
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem onClick={changeType}>
<Checkbox checked={type === 'all'} />
<>Show empty issue types</>
</MenuItem>
</Menu>
</Box>
</Box>
<Box>
<Button
variant="outlined"
color="primary"
size="medium"
endIcon={<ArrowForwardIcon />}
href={`${project?.url}/browse/${projectKey}`}
target="_blank"
>
<MenuItem onClick={changeType}>
<Checkbox checked={type === 'all'} />
<>Show empty issue types</>
</MenuItem>
</Menu>
Open in JIRA
</Button>
</Box>
</Box>
)
}
deepLink={{
link: `${project?.url}/browse/${projectKey}`,
title: 'Go to project',
onClick: e => {
e.preventDefault();
window.open(`${project?.url}/browse/${projectKey}`);
},
}}
>
{projectLoading && !(project && issues) ? <Progress /> : null}
{projectError ? (
Expand Down Expand Up @@ -183,6 +218,94 @@ export const JiraCard = (props: EntityProps & JiraCardOptionalProps) => {
))}
</Grid>
<Divider />
<TableContainer>
<Table className={classes.infoCard} aria-label="tickets-table">
<TableHead>
<TableRow>
<TableCell>Key</TableCell>
<TableCell align="left">Summary</TableCell>
<TableCell align="left">Priority</TableCell>
<TableCell align="left">Status</TableCell>
<TableCell align="left">Created</TableCell>
<TableCell align="left">Updated</TableCell>
<TableCell align="left">Assignee</TableCell>
</TableRow>
</TableHead>
<TableBody>
{tickets && tickets.length > 0 ? (
tickets
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((ticket: any) => (
<TableRow key={ticket?.key}>
<TableCell component="th">
<a
href={`${project?.url}/browse/${ticket?.key}`}
target="_blank"
rel="noopener noreferrer"
className={classes.ticketLink}
>
{ticket?.key}
</a>
</TableCell>
<TableCell align="left">{ticket?.summary}</TableCell>
<TableCell align="left">
<img
src={ticket?.priority?.iconUrl}
alt={ticket?.priority?.name}
title={ticket?.priority?.name}
width="20px"
/>
</TableCell>
<TableCell align="left">{ticket?.status}</TableCell>
<TableCell align="left">
{new Date(ticket?.created).toLocaleDateString()}
</TableCell>
<TableCell align="left">
{new Date(ticket?.updated).toLocaleDateString()}
</TableCell>
<TableCell
align="left"
style={{ display: 'flex', alignItems: 'center' }}
>
{ticket?.assignee?.displayName ? (
<>
<img
src={ticket?.assignee?.avatarUrl}
alt={ticket?.assignee?.displayName}
title={ticket?.assignee?.displayName}
style={{ marginRight: '10px' }}
width="30px"
/>
{ticket?.assignee?.displayName}
</>
) : (
<span>Not Assigned</span>
)}
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={7} align="center">
No Jira tickets available.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
<TablePagination
rowsPerPageOptions={[5, 10, 20]}
component="div"
count={tickets?.length || 0}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={(_event, newPage) => setPage(newPage)}
onRowsPerPageChange={event => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
}}
/>
</TableContainer>
<ActivityStream
projectKey={projectKey}
tokenType={tokenType}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ export const Selectors = ({
!statusesError &&
statuses &&
statuses.length >= 2 ? (
<Box display="flex" justifyContent="flex-end">
<Box display="flex" justifyContent="flex-start">
<FormControl className={classes.formControl}>
<InputLabel id="select-multiple-projects-statuses">
filter issue status
Filter issue status
</InputLabel>
<Select
labelId="select-statuses-label"
Expand Down
Loading

0 comments on commit 130e992

Please sign in to comment.