Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 57 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ Contributers are welcome! Please reach out on [Linkedin](https://www.linkedin.co
- [`sf jsc apex schedule start`](#sf-jsc-apex-schedule-start)
- [`sf jsc apex schedule stop`](#sf-jsc-apex-schedule-stop)
- [`sf jsc data export`](#sf-jsc-data-export)
- [`sf jsc maintain field-usage analyse`](#sf-jsc-maintain-field-usage-analyse)
- [`sf jsc maintain garbage collect`](#sf-jsc-maintain-garbage-collect)
- [`sf jsc manifest rollout`](#sf-jsc-manifest-rollout)
- [`sf jsc manifest validate`](#sf-jsc-manifest-validate)
Expand Down Expand Up @@ -108,7 +109,7 @@ EXAMPLES
$ sf jsc apex schedule export -j "Auto" -d tmp/dev
```

_See code: [src/commands/jsc/apex/schedule/export.ts](https://github.com/j-schreiber/js-sf-cli-plugin/blob/v0.14.0/src/commands/jsc/apex/schedule/export.ts)_
_See code: [src/commands/jsc/apex/schedule/export.ts](https://github.com/j-schreiber/js-sf-cli-plugin/blob/v0.15.0/src/commands/jsc/apex/schedule/export.ts)_

## `sf jsc apex schedule manage`

Expand Down Expand Up @@ -155,7 +156,7 @@ FLAG DESCRIPTIONS
the command may still fail, when run without this flag.
```

_See code: [src/commands/jsc/apex/schedule/manage.ts](https://github.com/j-schreiber/js-sf-cli-plugin/blob/v0.14.0/src/commands/jsc/apex/schedule/manage.ts)_
_See code: [src/commands/jsc/apex/schedule/manage.ts](https://github.com/j-schreiber/js-sf-cli-plugin/blob/v0.15.0/src/commands/jsc/apex/schedule/manage.ts)_

## `sf jsc apex schedule start`

Expand Down Expand Up @@ -219,7 +220,7 @@ FLAG DESCRIPTIONS
messages. If this doesn't help, use the --trace flag to output full debug logs from the execution.
```

_See code: [src/commands/jsc/apex/schedule/start.ts](https://github.com/j-schreiber/js-sf-cli-plugin/blob/v0.14.0/src/commands/jsc/apex/schedule/start.ts)_
_See code: [src/commands/jsc/apex/schedule/start.ts](https://github.com/j-schreiber/js-sf-cli-plugin/blob/v0.15.0/src/commands/jsc/apex/schedule/start.ts)_

## `sf jsc apex schedule stop`

Expand Down Expand Up @@ -284,7 +285,7 @@ FLAG DESCRIPTIONS
messages. If this doesn't help, use the --trace flag to output full debug logs from the execution.
```

_See code: [src/commands/jsc/apex/schedule/stop.ts](https://github.com/j-schreiber/js-sf-cli-plugin/blob/v0.14.0/src/commands/jsc/apex/schedule/stop.ts)_
_See code: [src/commands/jsc/apex/schedule/stop.ts](https://github.com/j-schreiber/js-sf-cli-plugin/blob/v0.15.0/src/commands/jsc/apex/schedule/stop.ts)_

## `sf jsc data export`

Expand Down Expand Up @@ -318,7 +319,55 @@ EXAMPLES
$ sf jsc data export
```

_See code: [src/commands/jsc/data/export.ts](https://github.com/j-schreiber/js-sf-cli-plugin/blob/v0.14.0/src/commands/jsc/data/export.ts)_
_See code: [src/commands/jsc/data/export.ts](https://github.com/j-schreiber/js-sf-cli-plugin/blob/v0.15.0/src/commands/jsc/data/export.ts)_

## `sf jsc maintain field-usage analyse`

Analyse the utilisation of fields for one or more sobjects.

```
USAGE
$ sf jsc maintain field-usage analyse -s <value>... -o <value> [--json] [--flags-dir <value>] [--custom-fields-only] [--api-version
<value>]

FLAGS
-o, --target-org=<value> (required) Username or alias of the target org, where analysis is run.
-s, --sobject=<value>... (required) The name of an sobject to analyse.
--api-version=<value> Override the api version used for api requests made by this command
--custom-fields-only Specify this flag to only analyse custom fields.

GLOBAL FLAGS
--flags-dir=<value> Import flag values from a directory.
--json Format output as json.

DESCRIPTION
Analyse the utilisation of fields for one or more sobjects.

Retrieves the total number of records for an sobject, then each filterable field is analysed
for how many records have a "non nullish" value. Not all fields can be analysed: You can find
more information of analysable data types in the type column of output table.

EXAMPLES
Analyse all fields for Account and MyCustomObject\_\_c object

$ sf jsc maintain field-usage analyse -o MyTargetOrg -s Account -s MyCustomObject\_\_c

Analyse only custom fields for Account object

$ sf jsc maintain field-usage analyse -o MyTargetOrg -s Account --custom-fields-only

FLAG DESCRIPTIONS
-s, --sobject=<value>... The name of an sobject to analyse.

Specify this flag multiple times to analyse multiple sobjects with a single command execution. Use the full API name
of the object.

--custom-fields-only Specify this flag to only analyse custom fields.

If omitted, the command analyses both standard fields and custom fields of each object.
```

_See code: [src/commands/jsc/maintain/field-usage/analyse.ts](https://github.com/j-schreiber/js-sf-cli-plugin/blob/v0.15.0/src/commands/jsc/maintain/field-usage/analyse.ts)_

## `sf jsc maintain garbage collect`

Expand Down Expand Up @@ -393,7 +442,7 @@ FLAG DESCRIPTIONS
needed, if you specify at least one package flag.
```

_See code: [src/commands/jsc/maintain/garbage/collect.ts](https://github.com/j-schreiber/js-sf-cli-plugin/blob/v0.14.0/src/commands/jsc/maintain/garbage/collect.ts)_
_See code: [src/commands/jsc/maintain/garbage/collect.ts](https://github.com/j-schreiber/js-sf-cli-plugin/blob/v0.15.0/src/commands/jsc/maintain/garbage/collect.ts)_

## `sf jsc manifest rollout`

Expand Down Expand Up @@ -427,7 +476,7 @@ EXAMPLES
$ sf jsc manifest rollout
```

_See code: [src/commands/jsc/manifest/rollout.ts](https://github.com/j-schreiber/js-sf-cli-plugin/blob/v0.14.0/src/commands/jsc/manifest/rollout.ts)_
_See code: [src/commands/jsc/manifest/rollout.ts](https://github.com/j-schreiber/js-sf-cli-plugin/blob/v0.15.0/src/commands/jsc/manifest/rollout.ts)_

## `sf jsc manifest validate`

Expand Down Expand Up @@ -459,6 +508,6 @@ EXAMPLES
$ sf jsc manifest validate
```

_See code: [src/commands/jsc/manifest/validate.ts](https://github.com/j-schreiber/js-sf-cli-plugin/blob/v0.14.0/src/commands/jsc/manifest/validate.ts)_
_See code: [src/commands/jsc/manifest/validate.ts](https://github.com/j-schreiber/js-sf-cli-plugin/blob/v0.15.0/src/commands/jsc/manifest/validate.ts)_

<!-- commandsstop -->
8 changes: 4 additions & 4 deletions messages/jsc.data.export.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ Path to the plan file that defines the export.

Output directory to export all fields.

# examples

- <%= config.bin %> <%= command.id %>

# flags.validate-only.summary

Does not retrieve records. Only validates the plan.

# examples

- <%= config.bin %> <%= command.id %>
39 changes: 39 additions & 0 deletions messages/jsc.maintain.field-usage.analyse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# summary

Analyse the utilisation of fields for one or more sobjects.

# description

Retrieves the total number of records for an sobject, then each filterable field is analysed
for how many records have a "non nullish" value. Not all fields can be analysed: You can find
more information of analysable data types in the type column of output table.

# flags.sobject.summary

The name of an sobject to analyse.

# flags.sobject.description

Specify this flag multiple times to analyse multiple sobjects with a single command execution. Use the full API name of the object.

# flags.target-org.summary

Username or alias of the target org, where analysis is run.

# flags.custom-fields-only.summary

Specify this flag to only analyse custom fields.

# flags.custom-fields-only.description

If omitted, the command analyses both standard fields and custom fields of each object.

# examples

- Analyse all fields for Account and MyCustomObject\_\_c object

<%= config.bin %> <%= command.id %> -o MyTargetOrg -s Account -s MyCustomObject\_\_c

- Analyse only custom fields for Account object

<%= config.bin %> <%= command.id %> -o MyTargetOrg -s Account --custom-fields-only
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@j-schreiber/sf-plugin",
"description": "Salesforce plugin to orchestrate multi-org deployments, ease org maintenance and migrate data.",
"version": "0.14.0",
"version": "0.15.0",
"repository": {
"type": "git",
"url": "git+https://github.com/j-schreiber/js-sf-cli-plugin"
Expand All @@ -10,6 +10,7 @@
"dependencies": {
"@jsforce/jsforce-node": "^3.4.2",
"@oclif/core": "^4",
"@oclif/multi-stage-output": "^0.8.16",
"@salesforce/apex-node": "^8.1.19",
"@salesforce/core": "^8.5.7",
"@salesforce/kit": "^3.2.3",
Expand Down Expand Up @@ -73,6 +74,9 @@
"subtopics": {
"garbage": {
"description": "Analyse and clean garbage from package installs"
},
"field-usage": {
"description": "description for jsc.maintain.field-usage"
}
}
},
Expand Down
99 changes: 99 additions & 0 deletions src/commands/jsc/maintain/field-usage/analyse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/* eslint-disable no-await-in-loop */
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
import SObjectAnalyser from '../../../../field-usage/sobjectAnalyser.js';
import { FieldUsageStats, FieldUsageTable } from '../../../../field-usage/fieldUsageTypes.js';
import FieldUsageMultiStageOutput, {
DESCRIBE_STAGE,
FIELD_STAGE,
OUTPUT_STAGE,
} from '../../../../field-usage/fieldUsageMultiStage.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@j-schreiber/sf-plugin', 'jsc.maintain.field-usage.analyse');

export type JscMaintainFieldUsageAnalyseResult = {
sobjects: Record<string, FieldUsageTable>;
};

export default class JscMaintainFieldUsageAnalyse extends SfCommand<JscMaintainFieldUsageAnalyseResult> {
public static readonly summary = messages.getMessage('summary');
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessages('examples');

public static readonly flags = {
sobject: Flags.string({
multiple: true,
required: true,
char: 's',
summary: messages.getMessage('flags.sobject.summary'),
description: messages.getMessage('flags.sobject.description'),
}),
'target-org': Flags.requiredOrg({
summary: messages.getMessage('flags.target-org.summary'),
char: 'o',
required: true,
}),
'custom-fields-only': Flags.boolean({
summary: messages.getMessage('flags.custom-fields-only.summary'),
description: messages.getMessage('flags.custom-fields-only.description'),
}),
'api-version': Flags.orgApiVersion(),
};

public async run(): Promise<JscMaintainFieldUsageAnalyseResult> {
const { flags } = await this.parse(JscMaintainFieldUsageAnalyse);
const targetOrg = flags['target-org'].getConnection(flags['api-version']);
const analyser = new SObjectAnalyser(targetOrg);

const fieldUsageTables: Record<string, FieldUsageTable> = {};
for (const sobj of flags.sobject) {
const ms = FieldUsageMultiStageOutput.newInstance(sobj, flags.json);

analyser.on('describeSuccess', (data: { fieldCount: number; resolvedName: string }) => {
ms.updateData({ describeStatus: 'Success' });
ms.goto(FIELD_STAGE, { fieldCount: `${data.fieldCount}` });
});
analyser.on('totalRecordsRetrieve', (data: { totalCount: number }) => {
ms.updateData({ totalRecords: `${data.totalCount}` });
});
analyser.on('fieldAnalysis', (data: { fieldName: string; fieldCounter: string }) => {
ms.goto(FIELD_STAGE, { fieldInAnalysis: `Analysing ${data.fieldCounter}: ${data.fieldName}` });
});

ms.goto(DESCRIBE_STAGE);
try {
const sobjectUsageResult = await analyser.analyseFieldUsage(sobj, {
customFieldsOnly: flags['custom-fields-only'],
});
ms.goto(OUTPUT_STAGE);
fieldUsageTables[sobj] = sobjectUsageResult;
const formattedOutput = formatOutput(sobjectUsageResult.fields);
ms.stop('completed');
this.table({
data: formattedOutput,
columns: ['name', 'type', 'absolutePopulated', { key: 'percentFormatted', name: 'Percent' }],
});
} catch (err) {
ms.error();
this.error(String(err));
} finally {
analyser.removeAllListeners();
}
}
return { sobjects: fieldUsageTables };
}
}

function formatOutput(data: FieldUsageStats[]): Array<FieldUsageStats & { percentFormatted: string }> {
return data.map((field) => {
const result = {
...field,
percentFormatted: field.percentagePopulated.toLocaleString(undefined, {
style: 'percent',
minimumFractionDigits: 2,
}),
};
return result;
});
}
29 changes: 21 additions & 8 deletions src/common/metadata/describeApi.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import fs from 'node:fs';
import { Connection } from '@salesforce/core';
import { Connection, Messages } from '@salesforce/core';
import { DescribeSObjectResult } from '@jsforce/jsforce-node';
import { LOCAL_CACHE_DIR } from '../constants.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@j-schreiber/sf-plugin', 'exportplan');

export default class DescribeApi {
private cachePath: string;
private readonly cachePath: string;

public constructor(private conn: Connection) {
public constructor(private readonly conn: Connection) {
this.cachePath = `./${LOCAL_CACHE_DIR}/${conn.getUsername() as string}/describes`;
}

Expand All @@ -18,12 +21,22 @@ export default class DescribeApi {
return describeResult;
}
fs.mkdirSync(this.cachePath, { recursive: true });
if (isToolingObject) {
describeResult = await this.conn.tooling.describe(objectName);
} else {
describeResult = await this.conn.describe(objectName);
}
describeResult = await this.fetchDescribe(objectName, isToolingObject);
fs.writeFileSync(fullFilePath, JSON.stringify(describeResult, null, 2));
return describeResult;
}

private async fetchDescribe(objectName: string, isToolingObject?: boolean): Promise<DescribeSObjectResult> {
try {
if (isToolingObject) {
const result = await this.conn.tooling.describe(objectName);
return result;
} else {
const result = await this.conn.describe(objectName);
return result;
}
} catch (err) {
throw messages.createError('InvalidSObjectName', [objectName, String(err)]);
}
}
}
11 changes: 2 additions & 9 deletions src/common/migrationPlanObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@
/* eslint-disable no-await-in-loop */
import fs from 'node:fs';
import { DescribeSObjectResult, QueryResult, Record } from '@jsforce/jsforce-node';
import { Connection, Messages, SfError } from '@salesforce/core';
import { Connection, SfError } from '@salesforce/core';
import { ZMigrationPlanObjectDataType, MigrationPlanObjectQueryResult } from '../types/migrationPlanObjectData.js';
import DescribeApi from './metadata/describeApi.js';
import QueryBuilder from './utils/queryBuilder.js';
import { eventBus } from './comms/eventBus.js';
import { ProcessingStatus, CommandStatusEvent } from './comms/processingEvents.js';
import PlanCache from './planCache.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@j-schreiber/sf-plugin', 'exportplan');

export default class MigrationPlanObject {
private describeResult?: DescribeSObjectResult;
private queryBuilder?: QueryBuilder;
Expand Down Expand Up @@ -196,11 +193,7 @@ export default class MigrationPlanObject {
private async describeObject(): Promise<DescribeSObjectResult> {
if (!this.describeResult) {
const descApi: DescribeApi = new DescribeApi(this.conn);
try {
this.describeResult = await descApi.describeSObject(this.data.objectName, this.data.isToolingObject);
} catch (err) {
throw messages.createError('InvalidSObjectName', [this.getObjectName(), String(err)]);
}
this.describeResult = await descApi.describeSObject(this.data.objectName, this.data.isToolingObject);
}
return this.describeResult;
}
Expand Down
Loading