Skip to content

Commit

Permalink
dependency-cruiser scan
Browse files Browse the repository at this point in the history
  • Loading branch information
elena-shostak committed Nov 8, 2024
1 parent 1761047 commit ee0dffb
Show file tree
Hide file tree
Showing 28 changed files with 1,751 additions and 24 deletions.
9 changes: 9 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2018,6 +2018,15 @@ module.exports = {
'@kbn/imports/no_group_crossing_imports': 'warn',
},
},
{
files: ['packages/kbn-dependency-usage/**/*.{ts,tsx}'],
rules: {
// disabling it since package is a CLI tool
'no-console': 'off',
// disabling it since package is marked as module and it requires extension for files written
'@kbn/imports/uniform_imports': 'off',
},
},
],
};

Expand Down
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ packages/kbn-data-service @elastic/kibana-visualizations @elastic/kibana-data-di
packages/kbn-data-stream-adapter @elastic/security-threat-hunting-explore
packages/kbn-data-view-utils @elastic/kibana-data-discovery
packages/kbn-datemath @elastic/kibana-data-discovery
packages/kbn-dependency-usage @elastic/kibana-security
packages/kbn-dev-cli-errors @elastic/kibana-operations
packages/kbn-dev-cli-runner @elastic/kibana-operations
packages/kbn-dev-proc-runner @elastic/kibana-operations
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1417,6 +1417,7 @@
"@kbn/core-ui-settings-server-mocks": "link:packages/core/ui-settings/core-ui-settings-server-mocks",
"@kbn/core-usage-data-server-mocks": "link:packages/core/usage-data/core-usage-data-server-mocks",
"@kbn/cypress-config": "link:packages/kbn-cypress-config",
"@kbn/dependency-usage": "link:packages/kbn-dependency-usage",
"@kbn/dev-cli-errors": "link:packages/kbn-dev-cli-errors",
"@kbn/dev-cli-runner": "link:packages/kbn-dev-cli-runner",
"@kbn/dev-proc-runner": "link:packages/kbn-dev-proc-runner",
Expand Down Expand Up @@ -1695,6 +1696,7 @@
"cypress-recurse": "^1.35.2",
"date-fns": "^2.29.3",
"dependency-check": "^4.1.0",
"dependency-cruiser": "^16.4.2",
"ejs": "^3.1.10",
"enzyme": "^3.11.0",
"enzyme-to-json": "^3.6.2",
Expand Down
124 changes: 124 additions & 0 deletions packages/kbn-dependency-usage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# @kbn/dependency-usage

#### 1. Show all packages/plugins within a directory that use a specific dependency

```sh
bash scripts/dependency_usage.sh -d rxjs -p x-pack/plugins/security_solution
```
or

```sh
bash scripts/dependency_usage.sh --dependency-name rxjs --paths x-pack/plugins/security_solution
```

**Example**:
- `-d rxjs`: Specifies the dependency to look for (`rxjs`).
- `-p x-pack/plugins/security_solution`: Sets the directory to search within (`x-pack/plugins/security_solution`).

#### 2. Show all packages/plugins within a directory grouped by code owner

Use this command to find all packages or plugins within a directory that include a specific dependency, grouped by code owner.

```sh
bash scripts/dependency_usage.sh -d rxjs -p x-pack/plugins -g owner
```
or
```sh
bash scripts/dependency_usage.sh --dependency-name rxjs --paths x-pack/plugins --group-by owner
```

**Explanation**:
- `-d rxjs`: Specifies the dependency to search for (`rxjs`).
- `-p x-pack/plugins`: Sets the directory to scan for plugins using this dependency.
- `-g owner`: Groups results by code owner.
- **Output**: Lists plugins within `x-pack/plugins` that use `rxjs`, organized by code owner.

---

#### 3. Show all dependencies for a specific package/plugin or directory

Use this command to display all dependencies used within a specific package, plugin, or directory.

```sh
bash scripts/dependency_usage.sh -p x-pack/plugins/security_solution
```
or
```sh
bash scripts/dependency_usage.sh --paths x-pack/plugins/security_solution
```

**Explanation**:
- `-p x-pack/plugins/security_solution`: Specifies the package or directory for which to list all dependencies.
- **Output**: Lists all dependencies for `x-pack/plugins/security_solution`.

---

#### 4. Group by code owner with adjustable collapse depth for fine-grained grouping

When a package or plugin has multiple subteams, adjust the `--collapse-depth` option to define how granular or the grouping by code owner should be.

**Detailed Subteam Grouping**:
Shows all subteams within `security_solution`.

```sh
bash scripts/dependency_usage.sh -p x-pack/plugins/security_solution -g owner --collapse-depth 4
```

**Collapsed Grouping**:
Groups the results under a higher-level owner (e.g., `security_solution` as a single group).

```bash
bash scripts/dependency_usage.sh -p x-pack/plugins/security_solution -g owner --collapse-depth 1
```

**Explanation**:
- `-p x-pack/plugins/security_solution`: Specifies the directory to scan.
- `-g owner`: Groups results by code owner.
- `--collapse-depth`: Defines the depth for grouping, where higher numbers show more granular subteams.
- **Output**: Lists dependencies grouped by code owner at different levels of depth based on the `--collapse-depth` value.

---

#### 5. Show all dependencies matching a pattern (e.g., `react-*`) within a package

Use this command to search for dependencies that match a specific pattern (such as `react-*`) within a package, and output the results to a specified file.

```bash
bash scripts/dependency_usage.sh -p x-pack/plugins/security_solution -d 'react-*' -o ./tmp/results.json
```

**Explanation**:
- `-p x-pack/plugins/security_solution`: Specifies the directory or package to search within.
- `-d 'react-*'`: Searches for dependencies that match the pattern `react-*`.
- `-o ./tmp/results.json`: Outputs the results to a specified file (`results.json` in the `./tmp` directory).
- **Output**: Saves a list of all dependencies matching `react-*` in `x-pack/plugins/security_solution` to `./tmp/results.json`.

---

### Using `madge` for building dependency graph
Added for comparison. Supports only full scan for third-party dependencies, no proper groping by codeowner/package.

By default, Madge outputs paths relative to the `baseDir`, which can vary depending on how you run the command or set the directory structure. There are multiple problems with that:
1. `baseDir` set to `.` results in empty graph
2. Having paths that are relative makes it challenging to perform reliable lookups or aggregations, i. e. grouping by codeowners

#### Show all dependencies

```sh
bash scripts/dependency_usage.sh -p x-pack/plugins -o ./tmp/deps-result-madge.json -t madge
```

# Dependency cruiser vs Madge perf stats

| Analysis | Real Time | User Time | Sys Time |
|-----------------------------------------|-------------|-------------|------------|
| All plugins (dependency-cruiser) | 7m 21.126s | 7m 53.099s | 20.581s |
| All plugins (madge) | 4m 38.998s | 4m 26.131s | 39.043s |
| Single plugin (dependency-cruiser) | 31.360s | 45.352s | 2.208s |
| Single plugin (madge) | 1m 8.398s | 1m 14.524s | 11.065s |
| Multiple plugins (dependency-cruiser) | 36.403s | 50.563s | 2.814s |
| Multiple plugins (madge) | 1m 11.620s | 1m 18.473s | 11.334s |
| x-pack/packages (dependency-cruiser) | 6.638s | 12.646s | 0.654s |
| x-pack/packages (madge) | 9.148s | 10.827s | 1.425s |
| packages (dependency-cruiser) | 25.744s | 39.073s | 2.191s |
| packages (madge) | 16.299s | 22.242s | 2.235s |
15 changes: 15 additions & 0 deletions packages/kbn-dependency-usage/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

/* eslint-disable no-restricted-syntax */
export default {
preset: '@kbn/test',
rootDir: '../..',
roots: ['<rootDir>/packages/kbn-dependency-usage'],
};
6 changes: 6 additions & 0 deletions packages/kbn-dependency-usage/kibana.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"devOnly": true,
"type": "shared-common",
"id": "@kbn/dependency-usage",
"owner": "@elastic/kibana-security"
}
11 changes: 11 additions & 0 deletions packages/kbn-dependency-usage/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.base.json",
"name": "@kbn/dependency-usage",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0",
"type": "module",
"exports": {
"./src/*": "./src/*"
}
}
103 changes: 103 additions & 0 deletions packages/kbn-dependency-usage/src/cli.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { identifyDependencyUsageWithCruiser } from './dependency_graph/providers/cruiser.ts';
import { configureYargs } from './cli';

jest.mock('chalk', () => ({
green: jest.fn((str) => str),
yellow: jest.fn((str) => str),
cyan: jest.fn((str) => str),
magenta: jest.fn((str) => str),
blue: jest.fn((str) => str),
bold: { magenta: jest.fn((str) => str), blue: jest.fn((str) => str) },
}));

jest.mock('./dependency_graph/providers/cruiser', () => ({
identifyDependencyUsageWithCruiser: jest.fn(),
}));

jest.mock('./cli', () => ({
...jest.requireActual('./cli'),
runCLI: jest.fn(),
}));

describe('dependency-usage CLI', () => {
const parser = configureYargs()
.fail((message: string) => {
throw new Error(message);
})
.exitProcess(false);

beforeEach(() => {
jest.spyOn(console, 'log').mockImplementation(() => {});
});

afterEach(() => {
jest.resetAllMocks();
});

it('should handle verbose option', () => {
const argv = parser.parse(['--paths', './plugins', '--verbose']);
expect(argv.verbose).toBe(true);

expect(identifyDependencyUsageWithCruiser).toHaveBeenCalledWith(
expect.any(Array),
undefined,
expect.objectContaining({ isVerbose: true })
);
});

it('should group results by specified group-by option', () => {
const argv = parser.parse(['--paths', './src', '--group-by', 'owner']);
expect(argv['group-by']).toBe('owner');

expect(identifyDependencyUsageWithCruiser).toHaveBeenCalledWith(
expect.any(Array),
undefined,
expect.objectContaining({ groupBy: 'owner' })
);
});

it('should use default values when optional arguments are not provided', () => {
const argv = parser.parse(['--paths', './src']);
expect(argv.paths).toEqual(['./src']);
expect(argv['dependency-name']).toBeUndefined();
expect(argv['collapse-depth']).toBe(1);
expect(argv.verbose).toBe(false);
});

it('should throw an error if summary is used without dependency-name', () => {
expect(() => {
parser.parse(['--summary', '--paths', './src']);
}).toThrow('Summary option can only be used when a dependency name is provided');
});

it('should set default values for unspecified options', () => {
const argv = parser.parse(['--paths', './src']);
expect(argv.tool).toBe('cruiser');
expect(argv['collapse-depth']).toBe(1);
});

it('should validate collapse-depth as a positive integer', () => {
expect(() => {
parser.parse(['--paths', './src', '--collapse-depth', '0']);
}).toThrow('Collapse depth must be a positive integer');
});

it('should output results to specified output path', () => {
const argv = parser.parse(['--paths', './src', '--output-path', './output.json']);
expect(argv['output-path']).toBe('./output.json');
});

it('should print results to console if no output path is specified', () => {
const argv = parser.parse(['--paths', './src']);
expect(argv['output-path']).toBeUndefined();
});
});
Loading

0 comments on commit ee0dffb

Please sign in to comment.