From e987e6a5ee9fa0f3b93feea1f55f292bbc2e947e Mon Sep 17 00:00:00 2001
From: Ben Surgison
Date: Tue, 3 Oct 2023 15:49:35 +0100
Subject: [PATCH] Build the basic plugin details page
---
CHANGELOG.md | 4 +
cypress/e2e/plugins/plugin-utils.js | 35 ++--
lib/assets/sass/manage-prototype.scss | 19 ++
lib/manage-prototype-handlers.js | 54 +++++-
lib/manage-prototype-handlers.test.js | 30 ++-
lib/manage-prototype-routes.js | 6 +-
lib/nunjucks/views/manage-prototype/index.njk | 2 +-
.../views/manage-prototype/plugin-details.njk | 89 +++++++++
.../views/manage-prototype/plugin-header.njk | 9 +
.../views/manage-prototype/plugins.njk | 180 ++++++++----------
lib/plugins/packages.js | 8 +-
lib/plugins/packages.spec.js | 9 +
12 files changed, 321 insertions(+), 124 deletions(-)
create mode 100644 lib/nunjucks/views/manage-prototype/plugin-details.njk
create mode 100644 lib/nunjucks/views/manage-prototype/plugin-header.njk
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 68647c8251..db9a252eed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+### New features
+
+- [#2351: Build the basic plugin details page](https://github.com/alphagov/govuk-prototype-kit/pull/2351)
+
## 13.13.6
### Fixes
diff --git a/cypress/e2e/plugins/plugin-utils.js b/cypress/e2e/plugins/plugin-utils.js
index 8a8416ab16..0ab6d16406 100644
--- a/cypress/e2e/plugins/plugin-utils.js
+++ b/cypress/e2e/plugins/plugin-utils.js
@@ -42,22 +42,26 @@ function provePluginTemplatesUninstalled (plugin) {
}
function initiatePluginAction (action, plugin, pluginName, options = {}) {
- if (action === 'install') {
- cy.visit(managePluginsPagePath)
- } else {
- cy.visit(manageInstalledPluginsPagePath)
- }
+ cy.visit(managePluginsPagePath)
if (pluginName) {
cy.get(`[data-plugin-package-name="${plugin}"]`)
.scrollIntoView()
- .find('h4')
+ .find('a')
.contains(pluginName)
}
cy.get(`[data-plugin-package-name="${plugin}"]`)
.scrollIntoView()
- .find('button')
+ .find('a')
+ .click()
+
+ if (action === 'update') {
+ cy.get('a')
+ .contains('Latest version:').click()
+ }
+
+ cy.get('button')
.contains(capitalize(action))
.click()
@@ -73,7 +77,7 @@ function provePluginInstalled (plugin, pluginName) {
if (pluginName) {
cy.get(`[data-plugin-package-name="${plugin}"]`)
.scrollIntoView()
- .find('h4')
+ .find('a')
.contains(pluginName)
}
@@ -91,7 +95,7 @@ function provePluginUninstalled (plugin, pluginName) {
if (pluginName) {
cy.get(`[data-plugin-package-name="${plugin}"]`)
.scrollIntoView()
- .find('h4')
+ .find('a')
.contains(pluginName)
}
@@ -108,7 +112,13 @@ function provePluginUpdated (plugin, pluginName) {
provePluginInstalled(plugin, pluginName)
cy.get(`[data-plugin-package-name="${plugin}"]`)
.scrollIntoView()
- .find('button')
+ .find('a')
+ .click()
+
+ cy.get('a')
+ .contains('Latest version:').should('not.exist')
+
+ cy.get('button')
.contains(capitalize('update')).should('not.exist')
}
@@ -117,10 +127,13 @@ function provePluginInstalledOldVersion (plugin, pluginName) {
if (pluginName) {
cy.get(`[data-plugin-package-name="${plugin}"]`)
.scrollIntoView()
- .find('h4')
+ .find('a')
.contains(pluginName)
}
+ cy.get(`[data-plugin-package-name="${plugin}"] strong.govuk-tag`)
+ .contains('Update available')
+
cy.get('#installed-plugins-link').click()
cy.get(`[data-plugin-package-name="${plugin}"]`)
diff --git a/lib/assets/sass/manage-prototype.scss b/lib/assets/sass/manage-prototype.scss
index 977587e8a3..855e33017f 100644
--- a/lib/assets/sass/manage-prototype.scss
+++ b/lib/assets/sass/manage-prototype.scss
@@ -440,6 +440,15 @@ body .govuk-prototype-kit-manage-prototype-govuk-tag {
padding-left: 0 !important;
}
+.govuk-prototype-kit-manage-prototype-plugin-heading {
+ margin-bottom: 0;
+}
+
+.govuk-prototype-kit-manage-prototype-plugin-sub-heading {
+ color: #505a5f;
+ margin-bottom: 15px;
+}
+
.govuk-prototype-kit-manage-prototype-plugin-list-plugin-list {
border-top: 1px solid #b1b4b6;
margin-bottom: 2em;
@@ -524,6 +533,9 @@ body .govuk-prototype-kit-manage-prototype-govuk-tag {
@media(min-width: 40.0525em) {
.govuk-prototype-kit-manage-prototype-plugin-list-plugin-list__item-buttons {
+ .govuk-button-group {
+ margin-bottom: 0;
+ }
.govuk-button, .govuk-link--no-visited-state {
margin: 0 15px 0 0;
}
@@ -574,3 +586,10 @@ body .govuk-prototype-kit-manage-prototype-govuk-tag {
}
}
}
+
+.govuk-prototype-kit-manage-prototype-plugin-details-links {
+ padding-top: 20px;
+ margin-bottom: 20px;
+ border-top: 2px solid #b1b4b6;
+ border-bottom: 2px solid #b1b4b6;
+}
diff --git a/lib/manage-prototype-handlers.js b/lib/manage-prototype-handlers.js
index 7adedd3719..f6d7997944 100644
--- a/lib/manage-prototype-handlers.js
+++ b/lib/manage-prototype-handlers.js
@@ -404,7 +404,7 @@ function getTemplatesPostInstallHandler (req, res) {
}))
}
-function buildPluginData (pluginData) {
+function buildPluginData (pluginData, isLatest) {
if (pluginData === undefined) {
return
}
@@ -417,22 +417,25 @@ function buildPluginData (pluginData) {
installedVersion,
required,
localVersion,
- pluginConfig = {}
+ pluginConfig = {},
+ latestPluginConfig
} = pluginData
+ const meta = isLatest ? latestPluginConfig?.meta || pluginConfig?.meta : pluginConfig?.meta
const preparedPackageNameForDisplay = plugins.preparePackageNameForDisplay(packageName)
return {
...preparedPackageNameForDisplay,
- ...pluginConfig.meta,
+ ...meta,
packageName,
latestVersion,
installedLocally,
- installLink: `${contextPath}/plugins/install?package=${encodeURIComponent(packageName)}`,
+ installLink: installedVersion ? undefined : `${contextPath}/plugins/install?package=${encodeURIComponent(packageName)}`,
installCommand: `npm install ${packageName}`,
updateLink: updateAvailable ? `${contextPath}/plugins/update?package=${encodeURIComponent(packageName)}` : undefined,
updateCommand: latestVersion && `npm install ${packageName}@${latestVersion}`,
uninstallLink: installed && !required ? `${contextPath}/plugins/uninstall?package=${encodeURIComponent(packageName)}${installedLocally ? `&version=${encodeURIComponent(localVersion)}` : ''}` : undefined,
uninstallCommand: `npm uninstall ${packageName}`,
- installedVersion
+ installedVersion,
+ inThisPlugin: getInThisPluginDetails(isLatest ? latestPluginConfig || pluginConfig : pluginConfig)
}
}
@@ -451,7 +454,7 @@ async function prepareForPluginPage (isInstalledPage, search) {
status: isInstalledPage ? 'installed' : 'search',
plugins: plugins.map(buildPluginData),
found: plugins.length,
- updates: installedPlugins.filter(plugin => plugin.updateAvailable).length
+ updates: installedPlugins.filter(plugin => !plugin.installedLocally && plugin.updateAvailable).length
}
}
@@ -583,6 +586,24 @@ async function getPluginForRequest (req) {
return chosenPlugin
}
+function getInThisPluginDetails (pluginConfig) {
+ const { nunjucksMacros = [], templates = [] } = pluginConfig || {}
+ const list = []
+ if (nunjucksMacros?.length) {
+ list.push({
+ title: 'Components',
+ items: nunjucksMacros.map(({ macroName }) => macroName)
+ })
+ }
+ if (templates?.length) {
+ list.push({
+ title: 'Templates',
+ items: templates.map(({ name }) => name)
+ })
+ }
+ return list
+}
+
function modeIsComplete (mode, { installedVersion, latestVersion, version, installedLocally }) {
switch (mode) {
case 'update':
@@ -743,6 +764,24 @@ async function postPluginsModeHandler (req, res) {
res.json({ status })
}
+async function getPluginDetailsHandler (req, res) {
+ const packageName = req.query.package
+ const isLatest = req.route.path.split('/').pop() === 'latest'
+ const latestLink = isLatest ? '' : req.originalUrl.replace('?', '/latest?')
+ const installedLink = isLatest ? req.originalUrl.replace('/latest?', '?') : ''
+ const plugin = await lookupPackageInfo(packageName)
+ const { name, scope, installedVersion, latestVersion, ...pluginData } = buildPluginData(plugin, isLatest)
+ const viewData = {
+ ...pluginData,
+ installedVersion,
+ latestVersion,
+ latestLink,
+ installedLink,
+ plugin: { name, scope, version: latestLink ? installedVersion || latestVersion : latestVersion }
+ }
+ res.send(nunjucksManagementEnv.render(getManagementView('plugin-details.njk'), viewData))
+}
+
module.exports = {
contextPath,
setKitRestarted,
@@ -766,5 +805,6 @@ module.exports = {
getPluginsModeHandler,
postPluginsStatusHandler,
postPluginsModeMiddleware,
- postPluginsModeHandler
+ postPluginsModeHandler,
+ getPluginDetailsHandler
}
diff --git a/lib/manage-prototype-handlers.test.js b/lib/manage-prototype-handlers.test.js
index a719a834ef..cf5832c86a 100644
--- a/lib/manage-prototype-handlers.test.js
+++ b/lib/manage-prototype-handlers.test.js
@@ -40,6 +40,7 @@ const {
postTemplatesInstallHandler,
getTemplatesPostInstallHandler,
getPluginsHandler,
+ getPluginDetailsHandler,
postPluginsStatusHandler,
postPluginsModeMiddleware,
getPluginsModeHandler,
@@ -433,7 +434,8 @@ describe('manage-prototype-handlers', () => {
name: pluginDisplayName.name,
packageName,
uninstallCommand: `npm uninstall ${packageName}`,
- updateCommand: `npm install ${packageName}@${latestVersion}`
+ updateCommand: `npm install ${packageName}@${latestVersion}`,
+ inThisPlugin: []
}
beforeEach(() => {
@@ -501,6 +503,32 @@ describe('manage-prototype-handlers', () => {
expect(res.redirect).toHaveBeenCalledWith(fullPath + '?search=' + search)
})
+ describe('getPluginDetailsHandler', () => {
+ it('plugins installed', async () => {
+ fse.readJsonSync.mockReturnValue(undefined)
+ req.route.path = 'plugins-installed'
+ await getPluginDetailsHandler(req, res)
+ expect(mockNunjucksRender).toHaveBeenCalledWith(
+ 'views/manage-prototype/plugin-details.njk',
+ expect.objectContaining({
+ packageName: 'test-package',
+ installLink: '/manage-prototype/plugins/install?package=test-package',
+ installCommand: 'npm install test-package',
+ updateCommand: 'npm install test-package@2.0.0',
+ uninstallCommand: 'npm uninstall test-package',
+ inThisPlugin: [],
+ latestVersion: '2.0.0',
+ latestLink: '/current-url',
+ installedLink: '',
+ plugin: {
+ name: 'Test Package',
+ version: '2.0.0'
+ }
+ })
+ )
+ })
+ })
+
it('getPluginsModeHandler', async () => {
req.params.mode = 'install'
req.query.package = packageName
diff --git a/lib/manage-prototype-routes.js b/lib/manage-prototype-routes.js
index 42c824c03e..5841933aee 100644
--- a/lib/manage-prototype-routes.js
+++ b/lib/manage-prototype-routes.js
@@ -24,7 +24,8 @@ const {
postPluginsModeHandler,
postPluginsStatusHandler,
pluginCacheMiddleware,
- postPluginsHandler
+ postPluginsHandler,
+ getPluginDetailsHandler
} = require('./manage-prototype-handlers')
const { packageDir, projectDir } = require('./utils/paths')
const { govukFrontendPaths } = require('./govukFrontendPaths')
@@ -79,6 +80,9 @@ router.post('/plugins/:mode', postPluginsModeMiddleware)
router.post('/plugins/:mode', csrfProtection, postPluginsModeHandler)
+router.get('/plugin-details/latest', getPluginDetailsHandler)
+router.get('/plugin-details', getPluginDetailsHandler)
+
// Find GOV.UK Frontend (via internal package, project fallback)
router.use('/dependencies/govuk-frontend', express.static(
govukFrontendPaths([packageDir, projectDir]).baseDir)
diff --git a/lib/nunjucks/views/manage-prototype/index.njk b/lib/nunjucks/views/manage-prototype/index.njk
index ece33f1f15..5ff43596d9 100644
--- a/lib/nunjucks/views/manage-prototype/index.njk
+++ b/lib/nunjucks/views/manage-prototype/index.njk
@@ -13,7 +13,7 @@
{% if kitUpdateAvailable %}
-
+
New version available: {{ latestAvailableKit }}
diff --git a/lib/nunjucks/views/manage-prototype/plugin-details.njk b/lib/nunjucks/views/manage-prototype/plugin-details.njk
new file mode 100644
index 0000000000..c9cad76965
--- /dev/null
+++ b/lib/nunjucks/views/manage-prototype/plugin-details.njk
@@ -0,0 +1,89 @@
+{% extends "views/manage-prototype/layout.njk" %}
+{% from "govuk/components/button/macro.njk" import govukButton %}
+
+{% block beforeContent %}
+ {{ super() }}
+ Back to plugins
+{% endblock %}
+
+{% block content %}
+
+{% endblock %}
diff --git a/lib/nunjucks/views/manage-prototype/plugin-header.njk b/lib/nunjucks/views/manage-prototype/plugin-header.njk
new file mode 100644
index 0000000000..89466e5701
--- /dev/null
+++ b/lib/nunjucks/views/manage-prototype/plugin-header.njk
@@ -0,0 +1,9 @@
+
+
+
{{ plugin.name }}
+
v{{ plugin.version }}
+ {% if plugin.scope %}
+
By {{ plugin.scope }}
+ {% endif %}
+
+
diff --git a/lib/nunjucks/views/manage-prototype/plugins.njk b/lib/nunjucks/views/manage-prototype/plugins.njk
index afc0f36d9e..f5ccef1b4f 100644
--- a/lib/nunjucks/views/manage-prototype/plugins.njk
+++ b/lib/nunjucks/views/manage-prototype/plugins.njk
@@ -5,47 +5,47 @@
{% from "govuk/components/tag/macro.njk" import govukTag %}
{% block content %}
-