diff --git a/plugins/gatsby-source-jenkinsplugins/__mocks__/plugin-health-score.json b/plugins/gatsby-source-jenkinsplugins/__mocks__/plugin-health-score.json new file mode 100644 index 000000000..2e3e4e739 --- /dev/null +++ b/plugins/gatsby-source-jenkinsplugins/__mocks__/plugin-health-score.json @@ -0,0 +1,22 @@ +{ + "aws-java-sdk-sns": { + "value": 96, + "version": "1.12.406-370.v8f993c987059", + "details": [ + { + "key": "update-center-plugin-publication", + "value": 1, + "coefficient": 1 + }, + { "key": "security", "value": 1, "coefficient": 1 }, + { + "key": "repository-configuration", + "value": 0.7, + "coefficient": 0.5 + }, + { "key": "adoption", "value": 1, "coefficient": 0.8 }, + { "key": "deprecation", "value": 1, "coefficient": 0.8 } + ], + "timestamp": "2023-03-24T15:15:33.022244Z" + } +} diff --git a/plugins/gatsby-source-jenkinsplugins/__snapshots__/utils.test.js.snap b/plugins/gatsby-source-jenkinsplugins/__snapshots__/utils.test.js.snap index e2c3d7f8a..b74fad135 100644 --- a/plugins/gatsby-source-jenkinsplugins/__snapshots__/utils.test.js.snap +++ b/plugins/gatsby-source-jenkinsplugins/__snapshots__/utils.test.js.snap @@ -224,3 +224,47 @@ Array [ }, ] `; + +exports[`utils get plugin healthScore data 1`] = ` +Array [ + Object { + "children": Array [], + "details": Array [ + Object { + "coefficient": 1, + "key": "update-center-plugin-publication", + "value": 1, + }, + Object { + "coefficient": 1, + "key": "security", + "value": 1, + }, + Object { + "coefficient": 0.5, + "key": "repository-configuration", + "value": 0.7, + }, + Object { + "coefficient": 0.8, + "key": "adoption", + "value": 1, + }, + Object { + "coefficient": 0.8, + "key": "deprecation", + "value": 1, + }, + ], + "id": "aws-java-sdk-sns", + "internal": Object { + "contentDigest": "f789c60332b91b320525aea3639a7c73", + "type": "JenkinsPluginHealthScore", + }, + "parent": null, + "timestamp": "2023-03-24T15:15:33.022244Z", + "value": 96, + "version": "1.12.406-370.v8f993c987059", + }, +] +`; diff --git a/plugins/gatsby-source-jenkinsplugins/gatsby-node.js b/plugins/gatsby-source-jenkinsplugins/gatsby-node.js index e35efd273..dba58862b 100644 --- a/plugins/gatsby-source-jenkinsplugins/gatsby-node.js +++ b/plugins/gatsby-source-jenkinsplugins/gatsby-node.js @@ -2,6 +2,7 @@ const { fetchSiteInfo, fetchPluginData, fetchPluginVersions, + fetchPluginHealthScore, processCategoryData, fetchLabelData, fetchStats, @@ -21,6 +22,7 @@ exports.sourceNodes = async ( processCategoryData({createNode, createNodeField, createContentDigest, createNodeId, createRemoteFileNode, reporter}), fetchLabelData({createNode, createNodeField, createContentDigest, createNodeId, createRemoteFileNode, reporter}), fetchPluginVersions({createNode, createNodeField, createContentDigest, createNodeId, createRemoteFileNode, reporter, firstReleases}), + fetchPluginHealthScore({createNode, createNodeField, createContentDigest, createNodeId, createRemoteFileNode, reporter}), ]).then(() => fetchPluginData({createNode, createNodeField, createContentDigest, createNodeId, createRemoteFileNode, reporter, firstReleases, stats})); } catch (err) { reporter.panic( @@ -36,6 +38,7 @@ exports.createSchemaCustomization = ({actions}) => { type JenkinsPlugin implements Node { wiki: JenkinsPluginWiki @link(from: "name", by: "name") releases: [JenkinsPluginVersion] @link(from: "name", by: "name") + healthScore: JenkinsPluginHealthScore @link(from: "name", by: "id") buildDate: Date @dateformat previousTimestamp: Date @dateformat releaseTimestamp: Date @dateformat diff --git a/plugins/gatsby-source-jenkinsplugins/utils.js b/plugins/gatsby-source-jenkinsplugins/utils.js index 427753792..10d9bdcf7 100644 --- a/plugins/gatsby-source-jenkinsplugins/utils.js +++ b/plugins/gatsby-source-jenkinsplugins/utils.js @@ -498,12 +498,37 @@ const fetchPluginVersions = async ({createNode, reporter, firstReleases}) => { sectionActivity.end(); }; +const fetchPluginHealthScore = async ({createNode, reporter}) => { + const sectionActivity = reporter.activityTimer('fetch plugin health score'); + sectionActivity.start(); + const url = 'https://plugin-health.jenkins.io/api/scores'; + const json = await requestGET({url, reporter}); + for (const pluginName of Object.keys(json)) { + const data = json[pluginName]; + createNode({ + ...data, + id: pluginName, + parent: null, + children: [], + internal: { + type: 'JenkinsPluginHealthScore', + contentDigest: crypto + .createHash('md5') + .update(`pluginHealthScore_${pluginName}`) + .digest('hex') + } + }); + } + sectionActivity.end(); +}; + module.exports = { fetchSiteInfo, fetchLabelData, processCategoryData, fetchPluginData, fetchPluginVersions, + fetchPluginHealthScore, fixGitHubUrl, fetchStats, getPluginContent, diff --git a/plugins/gatsby-source-jenkinsplugins/utils.test.js b/plugins/gatsby-source-jenkinsplugins/utils.test.js index a9a7836ca..5f8072033 100644 --- a/plugins/gatsby-source-jenkinsplugins/utils.test.js +++ b/plugins/gatsby-source-jenkinsplugins/utils.test.js @@ -94,4 +94,13 @@ describe('utils', () => { await utils.fetchPluginData({createNode, createNodeId, createContentDigest, reporter: _reporter, firstReleases, labelToCategory, stats}); expect(createNode.mock.calls.filter(call => call[0].name === 'ios-device-connector').map(args => args[0])).toMatchSnapshot(); }); + it('get plugin healthScore data', async () => { + nock('https://plugin-health.jenkins.io') + .get('/api/scores') + .reply(200, JSON.parse(await readText('plugin-health-score.json'))); + const createNode = jest.fn().mockResolvedValue(); + + await utils.fetchPluginHealthScore({createNode, reporter: _reporter}); + expect(createNode.mock.calls.filter(call => call[0].id === 'aws-java-sdk-sns').map(args => args[0])).toMatchSnapshot(); + }); }); diff --git a/plugins/plugin-site/src/components/Plugin.jsx b/plugins/plugin-site/src/components/Plugin.jsx index 3cac1ab70..bc6b357ef 100644 --- a/plugins/plugin-site/src/components/Plugin.jsx +++ b/plugins/plugin-site/src/components/Plugin.jsx @@ -1,13 +1,13 @@ import PropTypes from 'prop-types'; import React from 'react'; - import {navigate} from 'gatsby'; - import {cleanTitle} from '../commons/helper'; import Icon from '../components/Icon'; import PluginLabels from '../components/PluginLabels'; import PluginLastReleased from '../components/PluginLastReleased'; import PluginDevelopers from '../components/PluginDevelopers'; +import PluginHealthScore from '../components/PluginHealthScore'; + function Developers({developers}) { return ( @@ -24,7 +24,8 @@ function Developers({developers}) { Developers.propTypes = PluginDevelopers.propTypes; -function Plugin({plugin: {name, title, stats, labels, excerpt, developers, buildDate, releaseTimestamp}}) { +function Plugin({plugin: {name, title, stats, labels, excerpt, developers, buildDate, releaseTimestamp, healthScore}}) { + return (
navigate(`/${name}/`)} className="Plugin--PluginContainer">
@@ -49,6 +50,9 @@ function Plugin({plugin: {name, title, stats, labels, excerpt, developers, build
+
+ +
); } @@ -63,6 +67,9 @@ Plugin.propTypes = { id: PropTypes.string, name: PropTypes.string })), + healthScore: PropTypes.shape({ + value: PropTypes.number, + }), name: PropTypes.string.isRequired, requiredCore: PropTypes.string, sha1: PropTypes.string, diff --git a/plugins/plugin-site/src/components/PluginHealthScore.jsx b/plugins/plugin-site/src/components/PluginHealthScore.jsx new file mode 100644 index 000000000..6ca850bea --- /dev/null +++ b/plugins/plugin-site/src/components/PluginHealthScore.jsx @@ -0,0 +1,29 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {Progress} from 'reactstrap'; + +function PluginHealthScore({healthScore}) { + const score = healthScore.value || 0; + const color = + score > 80 ? 'success' : score > 60 ? 'warning' : 'danger'; + return ( + <> +
+ Health Score +
+ {score} + /100 +
+
+ + + ); +} + +PluginHealthScore.propTypes = { + healthScore: PropTypes.shape({ + value: PropTypes.number, + }), +}; + +export default PluginHealthScore; diff --git a/plugins/plugin-site/src/components/SearchResults.css b/plugins/plugin-site/src/components/SearchResults.css index d954c2488..0974a91e6 100644 --- a/plugins/plugin-site/src/components/SearchResults.css +++ b/plugins/plugin-site/src/components/SearchResults.css @@ -21,7 +21,7 @@ display: block; flex: 0 0 auto; font-size: 0.85rem; - height: 16.5rem; + height: 18.5rem; margin: 0.25rem; min-height: 6rem; opacity: 0.9; @@ -56,7 +56,7 @@ } } .Plugin--IconContainer { - bottom: 0.25rem; + bottom: 3rem; display: block; grid-area: icon; opacity: 0.75; @@ -148,7 +148,7 @@ word-wrap: break-word; } .Plugin--AuthorsContainer { - bottom: 1rem; + bottom: 3rem; grid-area: authors; max-width: 8rem; overflow: hidden; @@ -161,6 +161,20 @@ margin-right: 0.5rem; } } + +.Plugin--HealthScoreContainer{ + bottom: 0rem; + position: absolute; + width: 100%; + & div{ + display: flex; + justify-content: space-between; + font-size: 1rem; + font-weight: bold; + row-gap: 0.5rem; + } + +} .SearchResults--List { & .SearchResults--ItemBox { height: initial; @@ -187,6 +201,10 @@ height: initial; margin-bottom: 1rem; } + & .Plugin--HealthScoreContainer { + position: initial; + margin-top: 10px; + } } .Pagination--Container { display: flex; diff --git a/plugins/plugin-site/src/templates/search.jsx b/plugins/plugin-site/src/templates/search.jsx index 2a779420f..25835a700 100644 --- a/plugins/plugin-site/src/templates/search.jsx +++ b/plugins/plugin-site/src/templates/search.jsx @@ -93,6 +93,7 @@ function SearchPage({location}) { } } `); + const categoriesMap = groupBy(graphqlData.categories.edges.map(edge => edge.node), 'id'); const suspendedPlugins = graphqlData.suspendedPlugins.edges.map(edge => edge.node.id); const { diff --git a/plugins/plugin-site/src/utils/algolia-queries.js b/plugins/plugin-site/src/utils/algolia-queries.js index 5978a8d1b..dab0c462e 100644 --- a/plugins/plugin-site/src/utils/algolia-queries.js +++ b/plugins/plugin-site/src/utils/algolia-queries.js @@ -12,6 +12,9 @@ function pluginQueries() { currentInstalls trend } + healthScore { + value + } developers { name id @@ -38,16 +41,12 @@ function pluginQueries() { { type: 'synonym', synonyms: ['perforce', 'p4'], - objectID: 'syn-1617250859718-18' - } + objectID: 'syn-1617250859718-18', + }, ], settings: { paginationLimitedTo: 2000, // they recommend 1000, to keep speed up and prevent people from scraping, but both are fine to us - optionalWords: [ - 'jenkins', - 'plugin', - 'plugins' - ], + optionalWords: ['jenkins', 'plugin', 'plugins'], ranking: [ 'typo', 'geo', @@ -56,26 +55,20 @@ function pluginQueries() { 'proximity', 'attribute', 'exact', - 'custom' - ], - customRanking: [ - 'desc(stats.currentInstalls)' - ], - attributesForFaceting: [ - 'labels' + 'custom', ], + customRanking: ['desc(stats.currentInstalls)'], + attributesForFaceting: ['labels'], attributesToIndex: [ 'name', 'title', 'developers.name', 'developers.id', - 'excerpt' + 'excerpt', ], }, }; } -const queries = [ - pluginQueries(), -]; +const queries = [pluginQueries()]; module.exports = queries;