Skip to content

Commit

Permalink
Add --rank and --dependents
Browse files Browse the repository at this point in the history
  • Loading branch information
clintandrewhall committed Jan 11, 2024
1 parent 02a46b9 commit 6ace3b1
Show file tree
Hide file tree
Showing 13 changed files with 294 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import colors from 'colors/safe';

import { ToolingLog } from '@kbn/tooling-log';

import { PluginLayer, PluginLifecycle, PluginInfo, PluginStatuses, PluginState } from './types';
import { PLUGIN_LAYERS, PLUGIN_LIFECYCLES } from './const';
import { PluginLayer, PluginLifecycle, PluginInfo, PluginStatuses, PluginState } from '../types';
import { PLUGIN_LAYERS, PLUGIN_LIFECYCLES } from '../const';
import { borders } from './table_borders';

// A lot of this logic is brute-force and ugly. It's a quick and dirty way to get the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
*/

import { ToolingLog } from '@kbn/tooling-log';
import { PluginInfo, DependencyState, PluginStatuses } from './types';
import { PluginInfo, DependencyState, PluginStatuses } from '../types';

import { PLUGIN_LAYERS, PLUGIN_LIFECYCLES } from './const';
import { PLUGIN_LAYERS, PLUGIN_LIFECYCLES } from '../const';

/**
* Prepares a summary of the plugin's dependencies, based on its manifest and plugin classes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { ClassDeclaration, MethodDeclaration, Project, SyntaxKind, TypeNode } fr
import { PluginOrPackage } from '@kbn/docs-utils/src/types';
import { ToolingLog } from '@kbn/tooling-log';

import { getPluginClasses } from './get_plugin_classes';
import { PluginInfo, PluginLifecycle, PluginLayer, Lifecycle, Dependencies } from './types';
import { getPluginClasses } from '../lib/get_plugin_classes';
import { PluginInfo, PluginLifecycle, PluginLayer, Lifecycle, Dependencies } from '../types';

/**
* Derive and return information about a plugin and its dependencies.
Expand Down
51 changes: 51 additions & 0 deletions packages/kbn-plugin-check/dependencies/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { Flags } from '@kbn/dev-cli-runner';
import { findTeamPlugins } from '@kbn/docs-utils';
import { ToolingLog } from '@kbn/tooling-log';
import { Project } from 'ts-morph';
import { getPlugin } from '../lib';
import { displayDependencyCheck } from './display_dependency_check';

export const checkDependencies = (flags: Flags, log: ToolingLog) => {
const checkPlugin = (name: string) => {
const plugin = getPlugin(name, log);

if (!plugin) {
log.error(`Cannot find plugin ${name}`);
return;
}

const project = new Project({
tsConfigFilePath: `${plugin.directory}/tsconfig.json`,
});

displayDependencyCheck(project, plugin, log);
};

const pluginName = flags.plugin && typeof flags.plugin === 'string' ? flags.plugin : null;
const teamName = flags.team && typeof flags.team === 'string' ? flags.team : null;

if ((!pluginName && !teamName) || (pluginName && teamName)) {
log.error(`Must specify plugin or team name.`);
return;
}

if (pluginName) {
checkPlugin(pluginName);
}

if (teamName) {
const plugins = findTeamPlugins(teamName);

plugins.forEach((plugin) => {
checkPlugin(plugin.manifest.id);
});
}
};
File renamed without changes.
63 changes: 63 additions & 0 deletions packages/kbn-plugin-check/dependents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { ToolingLog } from '@kbn/tooling-log';

import { getAllPlugins } from './lib';

interface Dependents {
required: readonly string[];
optional: readonly string[];
bundles?: readonly string[];
}

export const findDependents = (plugin: string, log: ToolingLog): Dependents => {
log.info(`Finding dependents for ${plugin}`);
const plugins = getAllPlugins(log);
const required: string[] = [];
const optional: string[] = [];
const bundles: string[] = [];

plugins.forEach((p) => {
const manifest = p.manifest;

if (manifest.requiredPlugins?.includes(plugin)) {
required.push(manifest.id);
}

if (manifest.optionalPlugins?.includes(plugin)) {
optional.push(manifest.id);
}

if (manifest.requiredBundles?.includes(plugin)) {
bundles.push(manifest.id);
}
});

if (required.length === 0 && optional.length === 0 && bundles.length === 0) {
log.info(`No plugins depend on ${plugin}`);
}

if (required.length > 0) {
log.info(`REQUIRED BY ${required.length}:\n${required.join('\n')}\n`);
}

if (optional.length > 0) {
log.info(`OPTIONAL FOR ${optional.length}:\n${optional.join('\n')}\n`);
}

if (bundles.length > 0) {
log.info(`BUNDLE FOR ${bundles.length}:\n${bundles.join('\n')}\n`);
}

return {
required,
optional,
bundles,
};
};
61 changes: 27 additions & 34 deletions packages/kbn-plugin-check/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,62 +7,55 @@
*/

import { run } from '@kbn/dev-cli-runner';
import { Project } from 'ts-morph';
import { findTeamPlugins } from '@kbn/docs-utils';

import { getPlugin } from './get_plugin';
import { displayDependencyCheck } from './display_dependency_check';
import { checkDependencies } from './dependencies';
import { rankDependencies } from './rank';
import { findDependents } from './dependents';

/**
* A CLI for checking the consistency of a plugin's declared and implicit dependencies.
*/
export const runPluginCheckCli = () => {
run(
async ({ log, flags }) => {
const checkPlugin = (name: string) => {
const plugin = getPlugin(name, log);
if (
(flags.dependencies && flags.rank) ||
(flags.dependencies && flags.dependents) ||
(flags.rank && flags.dependents)
) {
throw new Error('Only one of --dependencies, --rank, or --dependents may be specified.');
}

if (!plugin) {
log.error(`Cannot find plugin ${name}`);
return;
if (flags.dependencies) {
if ((!flags.plugin && !flags.team) || (flags.plugin && flags.team)) {
throw new Error(
'Either --plugin or --team must or may be specified when checking dependencies.'
);
}

const project = new Project({
tsConfigFilePath: `${plugin.directory}/tsconfig.json`,
});

displayDependencyCheck(project, plugin, log);
};

const pluginName = flags.plugin && typeof flags.plugin === 'string' ? flags.plugin : null;
const teamName = flags.team && typeof flags.team === 'string' ? flags.team : null;

if ((!pluginName && !teamName) || (pluginName && teamName)) {
log.error(`Must specify plugin or team name.`);
return;
checkDependencies(flags, log);
}

if (pluginName) {
checkPlugin(pluginName);
if (flags.rank) {
rankDependencies(log);
}

if (teamName) {
const plugins = findTeamPlugins(teamName);

plugins.forEach((plugin) => {
checkPlugin(plugin.manifest.id);
});
if (flags.dependents && typeof flags.dependents === 'string') {
findDependents(flags.dependents, log);
}
},
{
log: {
defaultLevel: 'info',
},
flags: {
string: ['plugin', 'team'],
boolean: ['dependencies', 'rank'],
string: ['plugin', 'team', 'dependents'],
help: `
--plugin The plugin to check.
--team Check all plugins owned by a given team.
--rank Display plugins as a ranked list of usage.
--dependents [plugin] Display plugins that depend on a given plugin.
--dependencies Check plugin dependencies.
--plugin [plugin] The plugin to check.
--team [team] Check all plugins owned by a given team.
`,
},
}
Expand Down
20 changes: 20 additions & 0 deletions packages/kbn-plugin-check/lib/get_all_plugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { findPlugins } from '@kbn/docs-utils';
import { ToolingLog } from '@kbn/tooling-log';

/**
* Utility method for finding and logging information about all plugins.
*/
export const getAllPlugins = (log: ToolingLog) => {
const plugins = findPlugins().filter((plugin) => plugin.isPlugin);
log.info(`Found ${plugins.length} plugins.`);
log.debug('Found plugins:', plugins);
return plugins;
};
File renamed without changes.
11 changes: 11 additions & 0 deletions packages/kbn-plugin-check/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export { getPlugin } from './get_plugin';
export { getPluginClasses } from './get_plugin_classes';
export { getAllPlugins } from './get_all_plugins';
116 changes: 116 additions & 0 deletions packages/kbn-plugin-check/rank.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { MultiBar, Presets } from 'cli-progress';

import { ToolingLog } from '@kbn/tooling-log';

import { getAllPlugins } from './lib';

interface Dependencies {
required: readonly string[];
optional: readonly string[];
bundles?: readonly string[];
}

const getSpaces = (size: number, count: number) => {
const length = count > 9 && count < 100 ? 2 : count < 10 ? 1 : 3;
return ' '.repeat(size - length);
};

export const rankDependencies = (log: ToolingLog) => {
const plugins = getAllPlugins(log);

const pluginMap = new Map<string, Dependencies>();
const pluginRequired = new Map<string, number>();
const pluginOptional = new Map<string, number>();
const pluginBundles = new Map<string, number>();
let minWidth = 0;

plugins.forEach((plugin) => {
pluginMap.set(plugin.manifest.id, {
required: plugin.manifest.requiredPlugins || [],
optional: plugin.manifest.optionalPlugins || [],
bundles: plugin.manifest.requiredBundles || [],
});

if (plugin.manifest.id.length > minWidth) {
minWidth = plugin.manifest.id.length;
}
});

pluginMap.forEach((dependencies) => {
dependencies.required.forEach((required) => {
pluginRequired.set(required, (pluginRequired.get(required) || 0) + 1);
});

dependencies.optional.forEach((optional) => {
pluginOptional.set(optional, (pluginOptional.get(optional) || 0) + 1);
});

dependencies.bundles?.forEach((bundle) => {
pluginBundles.set(bundle, (pluginBundles.get(bundle) || 0) + 1);
});
});

const sorted = [...pluginMap.entries()].sort((a, b) => {
const aRequired = pluginRequired.get(a[0]) || 0;
const aOptional = pluginOptional.get(a[0]) || 0;
const aBundles = pluginBundles.get(a[0]) || 0;
const aTotal = aRequired + aOptional + aBundles;

const bRequired = pluginRequired.get(b[0]) || 0;
const bOptional = pluginOptional.get(b[0]) || 0;
const bBundles = pluginBundles.get(b[0]) || 0;
const bTotal = bRequired + bOptional + bBundles;

return bTotal - aTotal;
});

log.debug(`Ranking ${sorted.length} plugins.`);

// sorted.forEach((plugin) => {
// log.info(`${plugin[0]}: ${plugin[1]}/${pluginOptional.get(plugin[0]) || 0}`);
// });

const multiBar = new MultiBar(
{
clearOnComplete: false,
hideCursor: true,
format: ' {bar} | {plugin} | {usage} | {info}',
},
Presets.shades_grey
);

multiBar.create(sorted.length, sorted.length, {
plugin: `${sorted.length} plugins${' '.repeat(minWidth - 11)}`,
usage: 'total',
info: 'req opt bun',
});

sorted.forEach(([plugin]) => {
const total = sorted.length;
const optional = pluginOptional.get(plugin) || 0;
const required = pluginRequired.get(plugin) || 0;
const bundles = pluginBundles.get(plugin) || 0;
const usage = optional + required + bundles;

multiBar.create(total, required + optional + bundles, {
plugin: `${plugin}${' '.repeat(minWidth - plugin.length)}`,
info: `${required}${getSpaces(4, required)}${optional}${getSpaces(
4,
optional
)}${bundles}${getSpaces(4, bundles)}`,
usage: `${usage}${getSpaces(5, usage)}`,
});
});

multiBar.stop();

return sorted;
};

0 comments on commit 6ace3b1

Please sign in to comment.