From 631c41a1ffa38dda34bbdc0bcc3a9580cd8c56be Mon Sep 17 00:00:00 2001 From: Julien <32807437+julien-me@users.noreply.github.com> Date: Wed, 8 Feb 2023 22:16:09 +0800 Subject: [PATCH] Desktop: Resolves #6143: Show installed plugins in Help - About Joplin (#7711) --- .eslintignore | 1 + .gitignore | 1 + packages/app-cli/app/command-version.js | 2 +- packages/app-desktop/gui/ErrorBoundary.tsx | 11 +- packages/app-desktop/gui/MenuBar.tsx | 4 +- packages/lib/versionInfo.test.ts | 132 +++++++++++++++++++++ packages/lib/versionInfo.ts | 44 ++++++- 7 files changed, 186 insertions(+), 9 deletions(-) create mode 100644 packages/lib/versionInfo.test.ts diff --git a/.eslintignore b/.eslintignore index 0b0bf0a13..d6e70bf8f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -756,6 +756,7 @@ packages/lib/utils/credentialFiles.js packages/lib/utils/joplinCloud.js packages/lib/uuid.js packages/lib/versionInfo.js +packages/lib/versionInfo.test.js packages/pdf-viewer/FullViewer.js packages/pdf-viewer/Page.js packages/pdf-viewer/PdfDocument.js diff --git a/.gitignore b/.gitignore index e52a312aa..d4b3df803 100644 --- a/.gitignore +++ b/.gitignore @@ -744,6 +744,7 @@ packages/lib/utils/credentialFiles.js packages/lib/utils/joplinCloud.js packages/lib/uuid.js packages/lib/versionInfo.js +packages/lib/versionInfo.test.js packages/pdf-viewer/FullViewer.js packages/pdf-viewer/Page.js packages/pdf-viewer/PdfDocument.js diff --git a/packages/app-cli/app/command-version.js b/packages/app-cli/app/command-version.js index 66f304698..29cc13d99 100644 --- a/packages/app-cli/app/command-version.js +++ b/packages/app-cli/app/command-version.js @@ -12,7 +12,7 @@ class Command extends BaseCommand { } async action() { - this.stdout(versionInfo(require('./package.json')).message); + this.stdout(versionInfo(require('./package.json'), {}).message); } } diff --git a/packages/app-desktop/gui/ErrorBoundary.tsx b/packages/app-desktop/gui/ErrorBoundary.tsx index 5b2d1dbc3..2df158504 100644 --- a/packages/app-desktop/gui/ErrorBoundary.tsx +++ b/packages/app-desktop/gui/ErrorBoundary.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import versionInfo from '@joplin/lib/versionInfo'; -import PluginService from '@joplin/lib/services/plugins/PluginService'; +import PluginService, { Plugins } from '@joplin/lib/services/plugins/PluginService'; import Setting from '@joplin/lib/models/Setting'; import restart from '../services/restart'; const packageInfo = require('../packageInfo.js'); @@ -21,6 +21,7 @@ interface State { error: Error; errorInfo: ErrorInfo; pluginInfos: PluginInfo[]; + plugins: Plugins; } interface Props { @@ -29,14 +30,16 @@ interface Props { export default class ErrorBoundary extends React.Component { - public state: State = { error: null, errorInfo: null, pluginInfos: [] }; + public state: State = { error: null, errorInfo: null, pluginInfos: [], plugins: {} }; componentDidCatch(error: any, errorInfo: ErrorInfo) { if (typeof error === 'string') error = { message: error }; const pluginInfos: PluginInfo[] = []; + let plugins: Plugins = {}; try { const service = PluginService.instance(); + plugins = service.plugins; const pluginSettings = service.unserializePluginSettings(Setting.value('plugins.states')); for (const pluginId in pluginSettings) { const plugin = PluginService.instance().pluginById(pluginId); @@ -52,7 +55,7 @@ export default class ErrorBoundary extends React.Component { console.error('Could not get plugin info:', error); } - this.setState({ error, errorInfo, pluginInfos }); + this.setState({ error, errorInfo, pluginInfos, plugins }); } componentDidMount() { @@ -91,7 +94,7 @@ export default class ErrorBoundary extends React.Component { output.push(

Version info

-
{versionInfo(packageInfo).message}
+
{versionInfo(packageInfo, this.state.plugins).message}
); diff --git a/packages/app-desktop/gui/MenuBar.tsx b/packages/app-desktop/gui/MenuBar.tsx index ea8171e4e..b99bb3af4 100644 --- a/packages/app-desktop/gui/MenuBar.tsx +++ b/packages/app-desktop/gui/MenuBar.tsx @@ -21,6 +21,7 @@ import checkForUpdates from '../checkForUpdates'; const { connect } = require('react-redux'); import { reg } from '@joplin/lib/registry'; import { ProfileConfig } from '@joplin/lib/services/profileConfig/types'; +import PluginService from '@joplin/lib/services/plugins/PluginService'; const packageInfo = require('../packageInfo.js'); const { clipboard } = require('electron'); const Menu = bridge().Menu; @@ -485,7 +486,8 @@ function useMenu(props: Props) { } function _showAbout() { - const v = versionInfo(packageInfo); + const v = versionInfo(packageInfo, PluginService.instance().plugins); + const copyToClipboard = bridge().showMessageBox(v.message, { icon: `${bridge().electronApp().buildDir()}/icons/128x128.png`, diff --git a/packages/lib/versionInfo.test.ts b/packages/lib/versionInfo.test.ts new file mode 100644 index 000000000..ada67daf4 --- /dev/null +++ b/packages/lib/versionInfo.test.ts @@ -0,0 +1,132 @@ +import versionInfo from './versionInfo'; +import { reg } from './registry'; +import { Plugins } from './services/plugins/PluginService'; +import Plugin from './services/plugins/Plugin'; + +jest.mock('./registry'); + +const info = jest.spyOn(console, 'info').mockImplementation(() => {}); + +const mockedVersion = jest.fn(() => 'test'); +const mockedDb = { version: mockedVersion }; + +const packageInfo = { + 'name': 'Joplin', + 'version': '2.10.5', + 'description': 'Joplin for Desktop', + 'repository': { + 'type': 'git', + 'url': 'git+https://github.com/laurent22/joplin.git', + }, + 'author': 'Laurent Cozic', + 'license': 'AGPL-3.0-or-later', + 'bugs': { + 'url': 'https://github.com/laurent22/joplin/issues', + }, + 'homepage': 'https://github.com/laurent22/joplin#readme', + 'build': { + 'appId': 'net.cozic.joplin-desktop', + }, + 'git': { + 'branch': 'dev', + 'hash': '1b527f2bb', + }, +}; + +describe('getPluginLists', function() { + + beforeAll(() => { + (reg.db as jest.Mock).mockReturnValue(mockedDb); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should not list any plugin when no plugin is installed', () => { + const v = versionInfo(packageInfo, {}); + expect(v.body).toMatch(/Revision:\s[a-z0-9]{3,}\s\([a-zA-Z0-9-_/.]{1,}\)$/); + expect(v.message).toMatch(/Revision:\s[a-z0-9]{3,}\s\([a-zA-Z0-9-_/.]{1,}\)$/); + }); + + it('should list one plugin', () => { + const plugin: Plugin = new Plugin( + '', + { + manifest_version: 1, + id: '1', + name: 'Plugin1', + version: '1', + app_min_version: '1' }, + '', + () => {}, + '' + ); + + const plugins: Plugins = {}; + plugins[plugin.manifest.id] = plugin; + + const v = versionInfo(packageInfo, plugins); + expect(v.body).toMatch(/\n\nPlugin1: 1/); + expect(v.message).toMatch(/\n\nPlugin1: 1/); + }); + + it('should show a list of three plugins', () => { + const plugins: Plugins = {}; + for (let i = 1; i <= 3; i++) { + const plugin: Plugin = new Plugin( + '', + { + manifest_version: i, + id: i.toString(), + name: `Plugin${i}`, + version: '1', + app_min_version: '1' }, + '', + () => {}, + '' + ); + plugins[plugin.manifest.id] = plugin; + } + const v = versionInfo(packageInfo, plugins); + + expect(v.body).toMatch(/\n\nPlugin1: 1\nPlugin2: 1\nPlugin3: 1/); + expect(v.message).toMatch(/\n\nPlugin1: 1\nPlugin2: 1\nPlugin3: 1/); + }); + + it('should show an abridged list of plugins in message and the full list in body', () => { + const plugins: Plugins = {}; + for (let i = 1; i <= 21; i++) { + const plugin: Plugin = new Plugin( + '', + { + manifest_version: i, + id: i.toString(), + name: `Plugin${i}`, + version: '1', + app_min_version: '1' }, + '', + () => {}, + '' + ); + + plugins[plugin.manifest.id] = plugin; + } + const v = versionInfo(packageInfo, plugins); + + const body = '\n'; + for (let i = 1; i <= 21; i++) { + body.concat(`\nPlugin${i}: 1`); + } + expect(v.body).toMatch(new RegExp(body)); + + const message = '\n'; + for (let i = 1; i <= 20; i++) { + message.concat(`\nPlugin${i}: 1`); + } + message.concat('\n...'); + expect(v.message).toMatch(new RegExp(message)); + }); + + info.mockReset(); +}); diff --git a/packages/lib/versionInfo.ts b/packages/lib/versionInfo.ts index 2cb08b479..457c95b22 100644 --- a/packages/lib/versionInfo.ts +++ b/packages/lib/versionInfo.ts @@ -1,8 +1,44 @@ import { _ } from './locale'; import Setting from './models/Setting'; import { reg } from './registry'; +import { Plugins } from './services/plugins/PluginService'; -export default function versionInfo(packageInfo: any) { +interface PluginList { + completeList: string; + summary: string; +} + +function getPluginLists(plugins: Plugins): PluginList { + const pluginList = []; + if (Object.keys(plugins).length > 0) { + for (const pluginId in plugins) { + pluginList.push(`${plugins[pluginId].manifest.name}: ${plugins[pluginId].manifest.version}`); + } + } + + let completeList = ''; + let summary = ''; + if (pluginList.length > 0) { + completeList = ['\n', ...pluginList].join('\n'); + + if (pluginList.length > 20) { + summary = [ + '\n', + ...[...pluginList].filter((_, index) => index < 20), + '...', + ].join('\n'); + } else { + summary = completeList; + } + } + + return { + completeList, + summary, + }; +} + +export default function versionInfo(packageInfo: any, plugins: Plugins) { const p = packageInfo; let gitInfo = ''; if ('git' in p) { @@ -32,9 +68,11 @@ export default function versionInfo(packageInfo: any) { console.info(gitInfo); } + const pluginList = getPluginLists(plugins); + return { header: header.join('\n'), - body: body.join('\n'), - message: header.concat(body).join('\n'), + body: body.join('\n').concat(pluginList.completeList), + message: header.concat(body).join('\n').concat(pluginList.summary), }; }