diff --git a/src/components/LineChart.jsx b/src/components/LineChart.jsx index 46160d610..65306e80d 100644 --- a/src/components/LineChart.jsx +++ b/src/components/LineChart.jsx @@ -3,23 +3,8 @@ import PropTypes from 'prop-types'; import {Line} from 'react-chartjs-2'; import moment from 'moment'; -const calculateHeight = (total) => { - if (total > 100000) return 275; - if (total > 50000) return 250; - if (total > 25000) return 225; - if (total > 10000) return 200; - if (total > 5000) return 175; - return 150; -}; - -const calculateMinMax = (data) => { - const maxValue = Math.max(...data); - // calculate a dynamic value to center the graph - const scaleDifference = Math.pow(10, maxValue.toString().length-1); - return { - min: 0, - max: parseInt((maxValue/scaleDifference)+1)*scaleDifference // plus 1 to add more space in the top - }; +const calculateMax = (data) => { + return 1.2 * Math.max(...data); }; const chartData = (labels, data) => { @@ -39,7 +24,6 @@ const chartData = (labels, data) => { }; const options = (data) => { - const {min, max} = calculateMinMax(data); return { responsive: true, maintainAspectRatio: false, @@ -61,9 +45,10 @@ const options = (data) => { } }], yAxes: [{ + display: false, ticks: { - min, - max, + min: 0, + max: calculateMax(data), fontSize: 9, } }] @@ -77,13 +62,13 @@ const styles = { } }; -function LineChart({installations, total}) { +function LineChart({installations}) { if (!installations) { return null; } const labels = []; const data = []; - const height = calculateHeight(total); + const height = 80; const length = installations.length; installations.slice(length > 12 ? length - 12 : 0, length).forEach((installation) => { labels.push(moment.utc(installation.timestamp).format('MMM')); @@ -101,7 +86,6 @@ function LineChart({installations, total}) { } LineChart.propTypes = { - total: PropTypes.number.isRequired, installations: PropTypes.arrayOf(PropTypes.shape({ timestamp: PropTypes.number, total: PropTypes.number diff --git a/src/components/PluginLastReleased.jsx b/src/components/PluginLastReleased.jsx index ca704fd2e..a4b247489 100644 --- a/src/components/PluginLastReleased.jsx +++ b/src/components/PluginLastReleased.jsx @@ -18,7 +18,7 @@ function PluginLastReleased({plugin}) { const time = getTime(plugin); return ( <div> - {'Last released: '} + {'Released: '} <TimeAgo date={time} formatter={formatter}/> </div> ); diff --git a/src/components/PluginReadableInstalls.jsx b/src/components/PluginReadableInstalls.jsx index 6260e8d5f..4a8e86b7c 100644 --- a/src/components/PluginReadableInstalls.jsx +++ b/src/components/PluginReadableInstalls.jsx @@ -5,7 +5,7 @@ function PluginReadableInstalls({currentInstalls}) { if (!currentInstalls) { return <>No usage data available</>; } - return <>{currentInstalls}</>; + return <>{new Intl.NumberFormat('en-US').format(currentInstalls)}</>; } PluginReadableInstalls.propTypes = { diff --git a/src/styles/base.css b/src/styles/base.css index 674516c37..0d337611d 100644 --- a/src/styles/base.css +++ b/src/styles/base.css @@ -66,20 +66,21 @@ body a:active {color:#c00} .NoLabels > .Categories label > span {margin-left:0 !important; color:#0275d8; cursor:pointer} h1.title { - margin-top: 1rem; + margin-right: 2rem; } body #grid-box .NoLabels {padding: 2rem 0; background:#e5f8ff} -body #grid-box .NoLabels .Cols3 {font-size:.85rem} +body #grid-box .NoLabels .Cols3 {font-size:.875rem} body #grid-box .NoLabels .Cols3 label > span {margin-left:.5rem} -body #grid-box .NoLabels .Entry-box {display:block; padding: 0.25rem 0; - line-height: 1rem; font-size:.85rem} +body #grid-box .NoLabels .Entry-box { + display: block; + padding: 0.25rem 0; + line-height: 1rem; + font-size:.875rem +} body #grid-box .NoLabels .Entry-box a {cursor:pointer} body #grid-box .NoLabels legend {font-size:1.1rem; } -.update-link {margin:4rem 0;} -.update-link h6 {margin-bottom:.25rem} - #cb-item-finder-grid-box > .grid > .no-results {padding:.5rem; color:rgba(0,0,0,.667); display:block;} .not-found-box {text-align:center;} .not-found {margin:6rem 0; padding:1.5rem 0 1.5rem 8rem; position:relative; display:inline-block; text-align:left;} @@ -222,19 +223,27 @@ transform: scale(-1, 1); .maintainers > .maintainer {display:block;} .container-fluid.padded {padding:3rem 4rem} -#pluginPage #grid-box {font-size:.85rem;} -#pluginPage #grid-box .gutter {background:#eee; padding:2rem } +#pluginPage #grid-box {font-size:.875rem;} +#pluginPage #grid-box .gutter {padding:2rem } #pluginPage #grid-box .gutter > .btn { display:block; text-align:left; padding:.5rem 1rem .75rem 3.5rem; position:relative; margin-bottom:1rem} #pluginPage #grid-box .gutter > .btn i {font-size:2.25rem; position:absolute; left:.75rem; top:.5rem} #pluginPage #grid-box .gutter > .btn > span {font-size:1.1rem; line-height:1.3rem; font-weight:500} #pluginPage #grid-box .gutter > .btn > .v {display:block; font-weight:200; font-size:.75rem; white-space:normal} #pluginPage #grid-box .gutter .lbl {display:inline-block; margin-right:.5rem} ##pluginPage #grid-box .gutter .label-link a{display:block; line-height: 1.5rem; padding: 4px 0;} -#pluginPage #grid-box .gutter .chart {margin:1rem 0 2rem} +#pluginPage #grid-box .gutter .chart {margin:1rem 0 0} + +.gutter .sidebarSection { + margin-top: 1rem; + padding-top:.5rem; + border-top: 1px #AAA solid; +} + #pluginPage #grid-box { margin-left: 15px; margin-right: 15px; } + #pluginPage #grid-box .download {margin:2rem 0 2rem -.5rem;} #pluginPage #grid-box .alert > .alert-icon { background-repeat: no-repeat; @@ -249,6 +258,11 @@ transform: scale(-1, 1); opacity: .54; } +#grid-box .pluginContainer { + max-width: 72rem; + margin: auto; +} + .alert-warning .alert-icon .main-path { fill: #856404 } @@ -274,8 +288,18 @@ transform: scale(-1, 1); } .row.flex {display:flex} -div.title-details .sub {display:block; font-weight:normal; font-size:.85rem; color:#999; margin-top:.25rem} -div.title-details .version {display:block; font-weight:normal; font-size:.85rem; color:#999; margin-top:.25rem} +.title-wrapper { + display:flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + margin-top: 1rem; +} + +.plugin-id { + opacity: .7; + margin-bottom: .5rem; +} .markdown-body .anchor {display:none} .markdown-body h1 {font-size:2rem; margin:4rem 0 1rem;} @@ -315,7 +339,7 @@ body .item-finder.Tiles a.item, .Tile { display: block; border: .1rem solid #ccc; - font-size: .85rem; + font-size: .875rem; height: 16.5rem; width:13rem; box-sizing: border-box; @@ -553,11 +577,11 @@ body .item-finder.Table .Entry-box:nth-child(odd) > .Entry > div.Excerpt{box-sha */ body .item-finder.Table a.Entry > div, .tbody > .Row > .Tile > div {display:table-cell; max-height:3.3rem; height:auto; padding:.25rem; border-bottom:1px solid #eee} -body .item-finder.Table a.Entry > div.Score {margin:0; font-size:.85rem; position:static} +body .item-finder.Table a.Entry > div.Score {margin:0; font-size:.875rem; position:static} body .item-finder.Table a.Entry > div.Wiki {display:none} body .item-finder.Table a.Entry > div.Title, .tbody > .Row > .Tile > div.Title{width:20%} -body .item-finder.Table a.Entry > div.Title > h4 {font-size:.85rem; color:#369; font-weight:bold;} +body .item-finder.Table a.Entry > div.Title > h4 {font-size:.875rem; color:#369; font-weight:bold;} body .item-finder.Table a.Entry > div.Icon, .tbody > .Row > .Tile > div.Icon {display:none} body .item-finder.Table a.Entry > div.Authors > div, @@ -620,7 +644,7 @@ nav .nav-item.active {box-shadow:inset #D33833 0 -2px} color:#999; padding:1px 2px; - font-size:.85rem; + font-size:.875rem; display:block; width:auto; position:relative; @@ -884,6 +908,7 @@ a.card:hover { /* From atlassian batch.css - https://wiki.jenkins.io/s/f68dfafb2b4588f7b31742327b4469ae-CDN/en_GB/6441/82994790ee2f720a5ec8daf4850ac5b7b34d2194/be65c4ed0984ca532b26983f5fc1813e/_/download/contextbatch/css/_super/batch.css?atlassian.aui.raphael.disabled=true */ .confluenceTable{border-collapse:collapse}.confluenceTh,.confluenceTd{border:1px solid #c1c7d0;padding:7px 10px;vertical-align:top;text-align:left;min-width:8px}.confluenceTable ol,.confluenceTable ul{margin-left:0;padding-left:22px}.confluenceTable,.table-wrap{margin:10px 0 0 0;overflow-x:auto}.confluenceTable:first-child,.table-wrap:first-child{margin-top:0}table.confluenceTable th.confluenceTh,table.confluenceTable th.confluenceTh>p,table.confluenceTable td.confluenceTd.highlight,table.confluenceTable td.confluenceTd.highlight>p,table.confluenceTable th.confluenceTh.highlight-grey,table.confluenceTable th.confluenceTh.highlight-grey>p,table.confluenceTable td.confluenceTd.highlight-grey,table.confluenceTable td.confluenceTd.highlight-grey>p{background-color:#f4f5f7}table.confluenceTable th.confluenceTh.info,table.confluenceTable th.confluenceTh.info>p,table.confluenceTable td.confluenceTd.highlight.info,table.confluenceTable td.confluenceTd.highlight.info>p,table.confluenceTable th.confluenceTh.highlight-blue,table.confluenceTable th.confluenceTh.highlight-blue>p,table.confluenceTable td.confluenceTd.highlight-blue,table.confluenceTable td.confluenceTd.highlight-blue>p{background-color:#deebff}table.confluenceTable th.confluenceTh.success,table.confluenceTable th.confluenceTh.success>p,table.confluenceTable td.confluenceTd.highlight.success,table.confluenceTable td.confluenceTd.highlight.success>p,table.confluenceTable th.confluenceTh.highlight-green,table.confluenceTable th.confluenceTh.highlight-green>p,table.confluenceTable td.confluenceTd.highlight-green,table.confluenceTable td.confluenceTd.highlight-green>p{background-color:#e3fcef}table.confluenceTable th.confluenceTh.warning,table.confluenceTable th.confluenceTh.warning>p,table.confluenceTable td.confluenceTd.highlight.warning,table.confluenceTable td.confluenceTd.highlight.warning>p,table.confluenceTable th.confluenceTh.highlight-red,table.confluenceTable th.confluenceTh.highlight-red>p,table.confluenceTable td.confluenceTd.highlight-red,table.confluenceTable td.confluenceTd.highlight-red>p{background-color:#ffebe5}table.confluenceTable th.confluenceTh.note,table.confluenceTable th.confluenceTh.note>p,table.confluenceTable td.confluenceTd.highlight.note,table.confluenceTable td.confluenceTd.highlight.note>p,table.confluenceTable th.confluenceTh.highlight-yellow,table.confluenceTable th.confluenceTh.highlight-yellow>p,table.confluenceTable td.confluenceTd.highlight-yellow,table.confluenceTable td.confluenceTd.highlight-yellow>p{background-color:#fffae5}table.confluenceTable th.confluenceTh,table.confluenceTable th.confluenceTh>p{font-weight:bold}table.confluenceTable th.confluenceTh.nohighlight,table.confluenceTable th.confluenceTh.nohighlight>p{font-weight:normal;background-color:transparent}table.confluenceTable td.confluenceTd img,table.confluenceTable td.confluenceTd .confluence-embedded-file-wrapper img,table.confluenceTable th.confluenceTh .confluence-embedded-file-wrapper img{max-width:none}table.confluenceTable td.numberingColumn{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default} + #grid-box .content { word-break: break-word; } diff --git a/src/templates/plugin.jsx b/src/templates/plugin.jsx index e7939e331..c080b812b 100644 --- a/src/templates/plugin.jsx +++ b/src/templates/plugin.jsx @@ -49,11 +49,17 @@ function PluginPage({data: {jenkinsPlugin: plugin, reverseDependencies: reverseD <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="row flex pluginContainer"> <div className="col-md-9 main"> - <h1 className="title"> - {cleanTitle(plugin.title)} - </h1> + <div className="title-wrapper"> + <h1 className="title"> + {cleanTitle(plugin.title)} + </h1> + <div className="plugin-id"> + {'ID: '} + {plugin.name} + </div> + </div> <PluginActiveWarnings securityWarnings={plugin.securityWarnings} /> <PluginGovernanceStatus plugin={plugin} /> <ul className="nav nav-tabs"> @@ -65,35 +71,6 @@ function PluginPage({data: {jenkinsPlugin: plugin, reverseDependencies: reverseD </ul> <div className="padded"> {state.selectedTab === 'documentation' && (<> - <div className="title-details"> - <span className="version"> - {'Version: '} - {plugin.version} - </span> - <span className="sub"> - {'Minimum Jenkins requirement: '} - {plugin.requiredCore} - </span> - <span className="sub"> - {'ID: '} - {plugin.name} - </span> - </div> - <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> - {plugin.wiki.content && <div className="content" dangerouslySetInnerHTML={{__html: plugin.wiki.content}} />} </>)} {state.selectedTab === 'releases' && <PluginReleases pluginId={plugin.id} />} @@ -108,23 +85,39 @@ function PluginPage({data: {jenkinsPlugin: plugin, reverseDependencies: reverseD <span>Archives</span> <span className="v">Get past versions</span> </a> - <div className="chart"> - <LineChart - total={plugin.stats.currentInstalls} - installations={plugin.stats.installations} - /> + <h5>{`Version: ${plugin.version}`}</h5> + <PluginLastReleased plugin={plugin} /> + <div> + {'Requires Jenkins '} + {plugin.requiredCore} + </div> + <div className="sidebarSection"> + {plugin.stats && <h5> + {'Installs: '} + <PluginReadableInstalls currentInstalls={plugin.stats.currentInstalls} /> + </h5>} + <div className="chart"> + <LineChart + installations={plugin.stats.installations} + /> + </div> + </div> + <div className="sidebarSection"> + <h5>Links</h5> + {plugin.scm && plugin.scm.link && <div className="label-link"><a href={plugin.scm.link}>GitHub</a></div>} + <div className="label-link"><a href={`https://javadoc.jenkins.io/plugin/${plugin.name}`}>Javadoc</a></div> + </div> + <div className="sidebarSection"> + <h5>Labels</h5> + <PluginLabels labels={plugin.labels} /> + </div> + <div className="sidebarSection"> + <h5>Maintainers</h5> + <PluginMaintainers maintainers={plugin.maintainers} /> </div> - - <h5>Links</h5> - {plugin.scm && plugin.scm.link && <div className="label-link"><a href={plugin.scm.link}>GitHub</a></div>} - <div className="label-link"><a href={`https://javadoc.jenkins.io/plugin/${plugin.name}`}>Javadoc</a></div> - - <h5>Labels</h5> - <PluginLabels labels={plugin.labels} /> - <br/> {shouldShowWikiUrl(plugin.wiki) && - <div className="update-link"> - <h5>Help us to improve this page!</h5> + <div className="sidebarSection"> + <h5>Help us improve this page!</h5> {'This content is served from the '} <a href={plugin.wiki.url} target="_wiki">Jenkins Wiki</a> {' the '} @@ -135,17 +128,18 @@ function PluginPage({data: {jenkinsPlugin: plugin, reverseDependencies: reverseD </div> } {shouldShowGitHubUrl(plugin.wiki) && - <div className="update-link"> - <h5>Help us to improve this page!</h5> + <div className="sidebarSection"> + <h5>Help us improve this page!</h5> {'To propose a change submit a pull request to '} <a href={plugin.wiki.url} rel="noopener noreferrer" target="_blank">the plugin page</a> - {' on GitHub. Read more about GitHub support on the plugin site in the '} - <a href="https://jenkins.io/doc/developer/publishing/documentation/" rel="noopener noreferrer" target="_blank">Jenkins developer documentation</a> - {'.'} + {' on GitHub.'} + </div> + } + {plugin.securityWarnings && + <div className="sidebarSection"> + <PluginInactiveWarnings securityWarnings={plugin.securityWarnings} /> </div> } - - <PluginInactiveWarnings securityWarnings={plugin.securityWarnings} /> </div> </div> </Layout>