Skip to content

Commit 487ad7a

Browse files
authored
Merge pull request #250 from halkeye/add-issues-tab
Add Releases and Issues Tabs to plugin site.
2 parents 1d12652 + fca6b43 commit 487ad7a

File tree

11 files changed

+308
-46
lines changed

11 files changed

+308
-46
lines changed

gatsby-config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ module.exports = {
1414
},
1515
proxy: {
1616
prefix: '/api',
17-
url: 'https://plugins.jenkins.io',
17+
url: process.env.DEV_OVERRIDE_API_PROXY || 'https://plugins.jenkins.io',
1818
},
1919
plugins: [
2020
'gatsby-transformer-sharp',

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"scripts": {
77
"dev": "gatsby develop -p 3000 -H 0.0.0.0",
88
"build": "gatsby build",
9-
"lint": "eslint --ext .js --ext .jsx .",
9+
"lint": "eslint --ext .js,.jsx .",
1010
"stylelint": "stylelint --fix 'src/**/*.js'",
1111
"test": "jest",
1212
"test:watch": "yarn test -- --watch",
@@ -54,7 +54,7 @@
5454
"bugs": {
5555
"url": "https://issues.jenkins-ci.org/secure/RapidBoard.jspa?rapidView=1&projectKey=WEBSITE&view=detail"
5656
},
57-
"homepage": "http://plugins.jenkins-ci.org/",
57+
"homepage": "https://plugins.jenkins.io/",
5858
"dependencies": {
5959
"@babel/core": "^7.9.0",
6060
"chart.js": "^2.9.3",
@@ -87,6 +87,7 @@
8787
"react-chartjs-2": "^2.9.0",
8888
"react-helmet": "^6.0.0",
8989
"react-radio-group": "^3.0.3",
90+
"react-time-ago": "^5.0.8",
9091
"reactstrap": "^8.4.1"
9192
},
9293
"devDependencies": {

plugins/gatsby-source-jenkinsplugins/utils.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ function getContentFromConfluencePage(url, content) {
3434
// remove jira issue table
3535
$('.jira-table.conf-macro.output-block').remove();
3636

37+
// remove jira issue list
38+
$('.jira-issues').remove();
39+
3740
// Replace href/src with the wiki url
3841
$('[href]').each((idx, elm) => {
3942
$(elm).attr('href', URL.resolve(url, $(elm).attr('href')));

src/components/PluginIssues.jsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import React, {useState, useEffect} from 'react';
2+
import PropTypes from 'prop-types';
3+
import axios from 'axios';
4+
5+
function PluginIssues({pluginId}) {
6+
const [isLoading, setIsLoading] = useState(false);
7+
const [issues, setIssues] = useState([]);
8+
9+
useEffect(() => {
10+
const fetchData = async () => {
11+
setIsLoading(true);
12+
const result = await axios(`/api/plugin/${pluginId}/issues/open`);
13+
setIssues(result.data.issues || []);
14+
setIsLoading(false);
15+
};
16+
fetchData();
17+
return;
18+
}, []);
19+
20+
if (isLoading) {
21+
return (<div className="spinner-border" role="status">
22+
<span className="sr-only">Loading...</span>
23+
</div>);
24+
}
25+
26+
return (
27+
<div>
28+
<div className="table-responsive">
29+
<table className="table">
30+
<caption>List of issues</caption>
31+
<thead>
32+
<tr>
33+
<th scope="col">Key</th>
34+
<th scope="col">Summary</th>
35+
<th scope="col">Assignee</th>
36+
<th scope="col">Reporter</th>
37+
<th scope="col">Priority</th>
38+
<th scope="col">Status</th>
39+
<th scope="col">Resolution</th>
40+
<th scope="col">Created</th>
41+
<th scope="col">Updated</th>
42+
</tr>
43+
</thead>
44+
<tbody>
45+
{issues && issues.map(issue => {
46+
return (
47+
<tr key={issue.key}>
48+
<th scope="row"><a href={issue.url}>{issue.key}</a></th>
49+
<td>{issue.summary}</td>
50+
<td>{issue.assignee}</td>
51+
<td>{issue.reporter}</td>
52+
<td>{issue.priority}</td>
53+
<td>{issue.status}</td>
54+
<td>{issue.resolution}</td>
55+
<td>{issue.created}</td>
56+
<td>{issue.updated}</td>
57+
</tr>
58+
);
59+
})}
60+
</tbody>
61+
</table>
62+
</div>
63+
</div>
64+
);
65+
}
66+
67+
PluginIssues.propTypes = {
68+
pluginId: PropTypes.string.isRequired
69+
};
70+
export default PluginIssues;

src/components/PluginLastReleased.jsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33
import moment from 'moment';
4+
import ReactTimeAgo from 'react-time-ago/tooltip';
45

56
const getTime = (plugin) => {
67
if (plugin.releaseTimestamp !== null) {
@@ -16,10 +17,8 @@ function PluginLastReleased({plugin}) {
1617
const time = getTime(plugin);
1718
return (
1819
<div>
19-
Last released:
20-
<span title={time.format('dddd, MMMM Do YYYY')}>
21-
{time.fromNow()}
22-
</span>
20+
{'Last released: '}
21+
<ReactTimeAgo date={time} />
2322
</div>
2423
);
2524
}

src/components/PluginReleases.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#pluginReleases--container {
2+
margin-top: 10px;
3+
}
4+
5+
#pluginReleases--container .item {
6+
margin-bottom: 10px;
7+
}

src/components/PluginReleases.jsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import React, {useState, useEffect} from 'react';
2+
import PropTypes from 'prop-types';
3+
import axios from 'axios';
4+
import ReactTimeAgo from 'react-time-ago/tooltip';
5+
import './PluginReleases.css';
6+
7+
function PluginIssues({pluginId}) {
8+
const [isLoading, setIsLoading] = useState(false);
9+
const [releases, setReleases] = useState([]);
10+
11+
useEffect(() => {
12+
const fetchData = async () => {
13+
setIsLoading(true);
14+
const result = await axios(`/api/plugin/${pluginId}/releases`);
15+
setReleases(result.data.releases || []);
16+
setIsLoading(false);
17+
};
18+
fetchData();
19+
return;
20+
}, []);
21+
22+
if (isLoading) {
23+
return (<div className="spinner-border" role="status">
24+
<span className="sr-only">Loading...</span>
25+
</div>);
26+
}
27+
28+
return (
29+
<div id="pluginReleases--container" className="container">
30+
{releases && releases.map(release => {
31+
return (
32+
<div key={release.tag_name} className="item card">
33+
<div className="card-header">
34+
<h5 className="card-title d-flex justify-content-between">
35+
<div>{release.name || release.tag_name}</div>
36+
<div>
37+
{'Released: '}
38+
<ReactTimeAgo date={new Date(release.published_at)} />
39+
</div>
40+
</h5>
41+
</div>
42+
<div className="card-body">
43+
<p
44+
className="card-text"
45+
dangerouslySetInnerHTML={{__html: release.bodyHTML}}
46+
/>
47+
</div>
48+
</div>
49+
);
50+
})}
51+
</div>
52+
);
53+
}
54+
55+
PluginIssues.propTypes = {
56+
pluginId: PropTypes.string.isRequired
57+
};
58+
export default PluginIssues;

src/styles/tooltip.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
:root {
2+
--rrui-tooltip-text-color: white;
3+
--rrui-tooltip-background-color: grey;
4+
--rrui-tooltip-border-radius: 25px;
5+
--rrui-tooltip-hidden-distance: 10px;
6+
--rrui-tooltip-visible-distance: 0.5px;
7+
--rrui-tooltip-animation-duration: 0.5s;
8+
}

src/templates/plugin.jsx

Lines changed: 62 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {graphql} from 'gatsby';
2-
import React from 'react';
2+
import React, {useState} from 'react';
33
import PropTypes from 'prop-types';
44

55
import {cleanTitle} from '../commons/helper';
@@ -15,7 +15,8 @@ import PluginInactiveWarnings from '../components/PluginInactiveWarnings';
1515
import PluginGovernanceStatus from '../components/PluginGovernanceStatus';
1616
import PluginMaintainers from '../components/PluginMaintainers';
1717
import PluginReadableInstalls from '../components/PluginReadableInstalls';
18-
18+
import PluginIssues from '../components/PluginIssues';
19+
import PluginReleases from '../components/PluginReleases';
1920

2021
function shouldShowWikiUrl({url}) {
2122
return url && (url.startsWith('https://wiki.jenkins-ci.org') || url.startsWith('https://wiki.jenkins.io'));
@@ -25,50 +26,77 @@ function shouldShowGitHubUrl({url}) {
2526
return url && url.startsWith('https://github.com');
2627
}
2728

29+
const tabs = [
30+
{id: 'documentation', label: 'Documentation'},
31+
{id: 'releases', label: 'Releases'},
32+
{id: 'issues', label: 'Issues'},
33+
];
34+
35+
function getDefaultTab() {
36+
const tabName = (typeof window !== 'undefined' && window.location.hash.replace('#', '')) || tabs[0].id;
37+
if (tabs.find(tab => tab.id === tabName)) {
38+
return tabName;
39+
}
40+
return tabs[0].id;
41+
}
42+
2843
function PluginPage({data: {jenkinsPlugin: plugin}}) {
44+
const [state, setState] = useState({selectedTab: getDefaultTab()});
2945
const pluginPage = 'templates/plugin.jsx';
3046

3147
return (
3248
<Layout id="pluginPage" reportProblemRelativeSourcePath={pluginPage} reportProblemUrl={`/${plugin.name}`} reportProblemTitle={plugin.title}>
3349
<SEO title={cleanTitle(plugin.title)} description={plugin.excerpt} pathname={`/${plugin.id}`}/>
50+
3451
<div className="row flex">
3552
<div className="col-md-9 main">
53+
<ul className="nav nav-tabs">
54+
{tabs.map(tab => (
55+
<li className="nav-item" key={tab.id}>
56+
<a className={`nav-link ${state.selectedTab === tab.id ? 'active' : ''}`} href={`#${tab.id}`} onClick={() => setState({selectedTab: tab.id})}>{tab.label}</a>
57+
</li>
58+
))}
59+
</ul>
3660
<div className="padded">
37-
<h1 className="title">
38-
{cleanTitle(plugin.title)}
39-
<PluginActiveWarnings securityWarnings={plugin.securityWarnings} />
40-
<span className="v">{plugin.version}</span>
41-
<span className="sub">
42-
{'Minimum Jenkins requirement: '}
43-
{plugin.requiredCore}
44-
</span>
45-
<span className="sub">
46-
{'ID: '}
47-
{plugin.name}
48-
</span>
49-
</h1>
50-
<div className="row flex">
51-
<div className="col-md-4">
52-
{plugin.stats && <div>
53-
{'Installs: '}
54-
<PluginReadableInstalls currentInstalls={plugin.stats.currentInstalls} />
55-
</div>}
56-
{plugin.scm && plugin.scm.link && <div><a href={plugin.scm.link}>GitHub →</a></div>}
57-
<PluginLastReleased plugin={plugin} />
58-
</div>
59-
<div className="col-md-4 maintainers">
60-
<h5>Maintainers</h5>
61-
<PluginMaintainers maintainers={plugin.maintainers} />
61+
{state.selectedTab === 'documentation' && (<>
62+
<h1 className="title">
63+
{cleanTitle(plugin.title)}
64+
<PluginActiveWarnings securityWarnings={plugin.securityWarnings} />
65+
<span className="v">{plugin.version}</span>
66+
<span className="sub">
67+
{'Minimum Jenkins requirement: '}
68+
{plugin.requiredCore}
69+
</span>
70+
<span className="sub">
71+
{'ID: '}
72+
{plugin.name}
73+
</span>
74+
</h1>
75+
<div className="row flex">
76+
<div className="col-md-4">
77+
{plugin.stats && <div>
78+
{'Installs: '}
79+
<PluginReadableInstalls currentInstalls={plugin.stats.currentInstalls} />
80+
</div>}
81+
{plugin.scm && plugin.scm.link && <div><a href={plugin.scm.link}>GitHub →</a></div>}
82+
<PluginLastReleased plugin={plugin} />
83+
</div>
84+
<div className="col-md-4 maintainers">
85+
<h5>Maintainers</h5>
86+
<PluginMaintainers maintainers={plugin.maintainers} />
87+
</div>
88+
<div className="col-md-4 dependencies">
89+
<h5>Dependencies</h5>
90+
<PluginDependencies dependencies={plugin.dependencies} />
91+
</div>
6292
</div>
63-
<div className="col-md-4 dependencies">
64-
<h5>Dependencies</h5>
65-
<PluginDependencies dependencies={plugin.dependencies} />
66-
</div>
67-
</div>
6893

69-
<PluginGovernanceStatus plugin={plugin} />
94+
<PluginGovernanceStatus plugin={plugin} />
7095

71-
{plugin.wiki.content && <div className="content" dangerouslySetInnerHTML={{__html: plugin.wiki.content}} />}
96+
{plugin.wiki.content && <div className="content" dangerouslySetInnerHTML={{__html: plugin.wiki.content}} />}
97+
</>)}
98+
{state.selectedTab === 'releases' && <PluginReleases pluginId={plugin.id} />}
99+
{state.selectedTab === 'issues' && <PluginIssues pluginId={plugin.id} />}
72100
</div>
73101
</div>
74102
<div className="col-md-3 gutter">

utils.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ async function makeReactLayout() {
1717
'import { Helmet } from \'react-helmet\';',
1818
'import SiteVersion from \'./components/SiteVersion\';',
1919
'import ReportAProblem from \'./components/ReportAProblem\';',
20-
'import \'./layout.css\';'
20+
'import \'./layout.css\';',
21+
'import JavascriptTimeAgo from \'javascript-time-ago\';',
22+
'import en from \'javascript-time-ago/locale/en\';',
23+
'JavascriptTimeAgo.locale(en)'
2124
];
2225

2326
const cssLines = [
@@ -26,6 +29,8 @@ async function makeReactLayout() {
2629
'@import \'./styles/roboto-fonts.css\';',
2730
'@import \'./styles/base.css\';',
2831
'@import \'./styles/font-icons.css\';',
32+
'@import \'./styles/tooltip.css\';',
33+
'@import \'react-time-ago/Tooltip.css\';',
2934
];
3035

3136
console.info(`Downloading header file from '${headerUrl}'`);

0 commit comments

Comments
 (0)