Skip to content

Commit

Permalink
Merge pull request #250 from halkeye/add-issues-tab
Browse files Browse the repository at this point in the history
Add Releases and Issues Tabs to plugin site.
  • Loading branch information
halkeye committed Jun 9, 2020
2 parents 1d12652 + fca6b43 commit 487ad7a
Show file tree
Hide file tree
Showing 11 changed files with 308 additions and 46 deletions.
2 changes: 1 addition & 1 deletion gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module.exports = {
},
proxy: {
prefix: '/api',
url: 'https://plugins.jenkins.io',
url: process.env.DEV_OVERRIDE_API_PROXY || 'https://plugins.jenkins.io',
},
plugins: [
'gatsby-transformer-sharp',
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"dev": "gatsby develop -p 3000 -H 0.0.0.0",
"build": "gatsby build",
"lint": "eslint --ext .js --ext .jsx .",
"lint": "eslint --ext .js,.jsx .",
"stylelint": "stylelint --fix 'src/**/*.js'",
"test": "jest",
"test:watch": "yarn test -- --watch",
Expand Down Expand Up @@ -54,7 +54,7 @@
"bugs": {
"url": "https://issues.jenkins-ci.org/secure/RapidBoard.jspa?rapidView=1&projectKey=WEBSITE&view=detail"
},
"homepage": "http://plugins.jenkins-ci.org/",
"homepage": "https://plugins.jenkins.io/",
"dependencies": {
"@babel/core": "^7.9.0",
"chart.js": "^2.9.3",
Expand Down Expand Up @@ -87,6 +87,7 @@
"react-chartjs-2": "^2.9.0",
"react-helmet": "^6.0.0",
"react-radio-group": "^3.0.3",
"react-time-ago": "^5.0.8",
"reactstrap": "^8.4.1"
},
"devDependencies": {
Expand Down
3 changes: 3 additions & 0 deletions plugins/gatsby-source-jenkinsplugins/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ function getContentFromConfluencePage(url, content) {
// remove jira issue table
$('.jira-table.conf-macro.output-block').remove();

// remove jira issue list
$('.jira-issues').remove();

// Replace href/src with the wiki url
$('[href]').each((idx, elm) => {
$(elm).attr('href', URL.resolve(url, $(elm).attr('href')));
Expand Down
70 changes: 70 additions & 0 deletions src/components/PluginIssues.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, {useState, useEffect} from 'react';
import PropTypes from 'prop-types';
import axios from 'axios';

function PluginIssues({pluginId}) {
const [isLoading, setIsLoading] = useState(false);
const [issues, setIssues] = useState([]);

useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
const result = await axios(`/api/plugin/${pluginId}/issues/open`);
setIssues(result.data.issues || []);
setIsLoading(false);
};
fetchData();
return;
}, []);

if (isLoading) {
return (<div className="spinner-border" role="status">
<span className="sr-only">Loading...</span>
</div>);
}

return (
<div>
<div className="table-responsive">
<table className="table">
<caption>List of issues</caption>
<thead>
<tr>
<th scope="col">Key</th>
<th scope="col">Summary</th>
<th scope="col">Assignee</th>
<th scope="col">Reporter</th>
<th scope="col">Priority</th>
<th scope="col">Status</th>
<th scope="col">Resolution</th>
<th scope="col">Created</th>
<th scope="col">Updated</th>
</tr>
</thead>
<tbody>
{issues && issues.map(issue => {
return (
<tr key={issue.key}>
<th scope="row"><a href={issue.url}>{issue.key}</a></th>
<td>{issue.summary}</td>
<td>{issue.assignee}</td>
<td>{issue.reporter}</td>
<td>{issue.priority}</td>
<td>{issue.status}</td>
<td>{issue.resolution}</td>
<td>{issue.created}</td>
<td>{issue.updated}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
}

PluginIssues.propTypes = {
pluginId: PropTypes.string.isRequired
};
export default PluginIssues;
7 changes: 3 additions & 4 deletions src/components/PluginLastReleased.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import ReactTimeAgo from 'react-time-ago/tooltip';

const getTime = (plugin) => {
if (plugin.releaseTimestamp !== null) {
Expand All @@ -16,10 +17,8 @@ function PluginLastReleased({plugin}) {
const time = getTime(plugin);
return (
<div>
Last released:
<span title={time.format('dddd, MMMM Do YYYY')}>
{time.fromNow()}
</span>
{'Last released: '}
<ReactTimeAgo date={time} />
</div>
);
}
Expand Down
7 changes: 7 additions & 0 deletions src/components/PluginReleases.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pluginReleases--container {
margin-top: 10px;
}

#pluginReleases--container .item {
margin-bottom: 10px;
}
58 changes: 58 additions & 0 deletions src/components/PluginReleases.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, {useState, useEffect} from 'react';
import PropTypes from 'prop-types';
import axios from 'axios';
import ReactTimeAgo from 'react-time-ago/tooltip';
import './PluginReleases.css';

function PluginIssues({pluginId}) {
const [isLoading, setIsLoading] = useState(false);
const [releases, setReleases] = useState([]);

useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
const result = await axios(`/api/plugin/${pluginId}/releases`);
setReleases(result.data.releases || []);
setIsLoading(false);
};
fetchData();
return;
}, []);

if (isLoading) {
return (<div className="spinner-border" role="status">
<span className="sr-only">Loading...</span>
</div>);
}

return (
<div id="pluginReleases--container" className="container">
{releases && releases.map(release => {
return (
<div key={release.tag_name} className="item card">
<div className="card-header">
<h5 className="card-title d-flex justify-content-between">
<div>{release.name || release.tag_name}</div>
<div>
{'Released: '}
<ReactTimeAgo date={new Date(release.published_at)} />
</div>
</h5>
</div>
<div className="card-body">
<p
className="card-text"
dangerouslySetInnerHTML={{__html: release.bodyHTML}}
/>
</div>
</div>
);
})}
</div>
);
}

PluginIssues.propTypes = {
pluginId: PropTypes.string.isRequired
};
export default PluginIssues;
8 changes: 8 additions & 0 deletions src/styles/tooltip.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
:root {
--rrui-tooltip-text-color: white;
--rrui-tooltip-background-color: grey;
--rrui-tooltip-border-radius: 25px;
--rrui-tooltip-hidden-distance: 10px;
--rrui-tooltip-visible-distance: 0.5px;
--rrui-tooltip-animation-duration: 0.5s;
}
96 changes: 62 additions & 34 deletions src/templates/plugin.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {graphql} from 'gatsby';
import React from 'react';
import React, {useState} from 'react';
import PropTypes from 'prop-types';

import {cleanTitle} from '../commons/helper';
Expand All @@ -15,7 +15,8 @@ import PluginInactiveWarnings from '../components/PluginInactiveWarnings';
import PluginGovernanceStatus from '../components/PluginGovernanceStatus';
import PluginMaintainers from '../components/PluginMaintainers';
import PluginReadableInstalls from '../components/PluginReadableInstalls';

import PluginIssues from '../components/PluginIssues';
import PluginReleases from '../components/PluginReleases';

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

const tabs = [
{id: 'documentation', label: 'Documentation'},
{id: 'releases', label: 'Releases'},
{id: 'issues', label: 'Issues'},
];

function getDefaultTab() {
const tabName = (typeof window !== 'undefined' && window.location.hash.replace('#', '')) || tabs[0].id;
if (tabs.find(tab => tab.id === tabName)) {
return tabName;
}
return tabs[0].id;
}

function PluginPage({data: {jenkinsPlugin: plugin}}) {
const [state, setState] = useState({selectedTab: getDefaultTab()});
const pluginPage = 'templates/plugin.jsx';

return (
<Layout id="pluginPage" reportProblemRelativeSourcePath={pluginPage} reportProblemUrl={`/${plugin.name}`} reportProblemTitle={plugin.title}>
<SEO title={cleanTitle(plugin.title)} description={plugin.excerpt} pathname={`/${plugin.id}`}/>

<div className="row flex">
<div className="col-md-9 main">
<ul className="nav nav-tabs">
{tabs.map(tab => (
<li className="nav-item" key={tab.id}>
<a className={`nav-link ${state.selectedTab === tab.id ? 'active' : ''}`} href={`#${tab.id}`} onClick={() => setState({selectedTab: tab.id})}>{tab.label}</a>
</li>
))}
</ul>
<div className="padded">
<h1 className="title">
{cleanTitle(plugin.title)}
<PluginActiveWarnings securityWarnings={plugin.securityWarnings} />
<span className="v">{plugin.version}</span>
<span className="sub">
{'Minimum Jenkins requirement: '}
{plugin.requiredCore}
</span>
<span className="sub">
{'ID: '}
{plugin.name}
</span>
</h1>
<div className="row flex">
<div className="col-md-4">
{plugin.stats && <div>
{'Installs: '}
<PluginReadableInstalls currentInstalls={plugin.stats.currentInstalls} />
</div>}
{plugin.scm && plugin.scm.link && <div><a href={plugin.scm.link}>GitHub →</a></div>}
<PluginLastReleased plugin={plugin} />
</div>
<div className="col-md-4 maintainers">
<h5>Maintainers</h5>
<PluginMaintainers maintainers={plugin.maintainers} />
{state.selectedTab === 'documentation' && (<>
<h1 className="title">
{cleanTitle(plugin.title)}
<PluginActiveWarnings securityWarnings={plugin.securityWarnings} />
<span className="v">{plugin.version}</span>
<span className="sub">
{'Minimum Jenkins requirement: '}
{plugin.requiredCore}
</span>
<span className="sub">
{'ID: '}
{plugin.name}
</span>
</h1>
<div className="row flex">
<div className="col-md-4">
{plugin.stats && <div>
{'Installs: '}
<PluginReadableInstalls currentInstalls={plugin.stats.currentInstalls} />
</div>}
{plugin.scm && plugin.scm.link && <div><a href={plugin.scm.link}>GitHub →</a></div>}
<PluginLastReleased plugin={plugin} />
</div>
<div className="col-md-4 maintainers">
<h5>Maintainers</h5>
<PluginMaintainers maintainers={plugin.maintainers} />
</div>
<div className="col-md-4 dependencies">
<h5>Dependencies</h5>
<PluginDependencies dependencies={plugin.dependencies} />
</div>
</div>
<div className="col-md-4 dependencies">
<h5>Dependencies</h5>
<PluginDependencies dependencies={plugin.dependencies} />
</div>
</div>

<PluginGovernanceStatus plugin={plugin} />
<PluginGovernanceStatus plugin={plugin} />

{plugin.wiki.content && <div className="content" dangerouslySetInnerHTML={{__html: plugin.wiki.content}} />}
{plugin.wiki.content && <div className="content" dangerouslySetInnerHTML={{__html: plugin.wiki.content}} />}
</>)}
{state.selectedTab === 'releases' && <PluginReleases pluginId={plugin.id} />}
{state.selectedTab === 'issues' && <PluginIssues pluginId={plugin.id} />}
</div>
</div>
<div className="col-md-3 gutter">
Expand Down
7 changes: 6 additions & 1 deletion utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ async function makeReactLayout() {
'import { Helmet } from \'react-helmet\';',
'import SiteVersion from \'./components/SiteVersion\';',
'import ReportAProblem from \'./components/ReportAProblem\';',
'import \'./layout.css\';'
'import \'./layout.css\';',
'import JavascriptTimeAgo from \'javascript-time-ago\';',
'import en from \'javascript-time-ago/locale/en\';',
'JavascriptTimeAgo.locale(en)'
];

const cssLines = [
Expand All @@ -26,6 +29,8 @@ async function makeReactLayout() {
'@import \'./styles/roboto-fonts.css\';',
'@import \'./styles/base.css\';',
'@import \'./styles/font-icons.css\';',
'@import \'./styles/tooltip.css\';',
'@import \'react-time-ago/Tooltip.css\';',
];

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

0 comments on commit 487ad7a

Please sign in to comment.