Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

W-17037056 feat: add data update bulk/resume commands #1098

Merged
merged 29 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d33b455
chore: refactor bulk ingest utils
cristiand391 Oct 22, 2024
e3ddb40
feat: add `data update bulk/resume`
cristiand391 Oct 22, 2024
a57409a
fix: update `data import bulk` help
cristiand391 Oct 22, 2024
172c7d5
test: add bulk update NUT
cristiand391 Oct 22, 2024
f2faeb9
test: break up NUTs (#1099)
cristiand391 Oct 23, 2024
4f6145f
chore: unify bulk ingest logic
cristiand391 Oct 23, 2024
31c9f41
test: add bulk update NUTs to test matrix
cristiand391 Oct 23, 2024
feedba7
fix: insert operation
cristiand391 Oct 23, 2024
8de8751
fix: command-specific resume instructions
cristiand391 Oct 23, 2024
9c429a4
fix: command-specific stage title
cristiand391 Oct 23, 2024
7b01b4f
fix: pass operation opt
cristiand391 Oct 23, 2024
81afcca
test: fix update resume NUT on win
cristiand391 Oct 24, 2024
74a29e9
test: refactor/doc
cristiand391 Oct 24, 2024
4fb6c6b
chore: moar refactor/doc
cristiand391 Oct 24, 2024
a3c917a
chore: clean up msgs
cristiand391 Oct 24, 2024
b3785f5
feat: add column-delimiter flag to import/update bulk
cristiand391 Oct 24, 2024
c76a402
chore: update command snapshot
cristiand391 Oct 24, 2024
6e3bf7a
chore: eslint rule inline
cristiand391 Oct 25, 2024
d750429
test: validate async command's cache files
cristiand391 Oct 25, 2024
e20bdc6
chore: update msg
cristiand391 Oct 25, 2024
8a7c105
fix: edit help for new "data update bulk|resume" commands (#1106)
jshackell-sfdc Oct 29, 2024
0af5ff3
fix: remove `as string`
cristiand391 Oct 29, 2024
f688432
chore: use proper stop status
cristiand391 Oct 30, 2024
f12a42b
chore: share column-delimiter flag def
cristiand391 Oct 30, 2024
90b4e38
test: remove type assertions
cristiand391 Oct 30, 2024
6adb1e2
feat: detect column delimiter
cristiand391 Oct 30, 2024
0c1bf01
test: nut should detect column delimiter
cristiand391 Oct 30, 2024
dcb784e
Merge branch 'cd/bulk-update' of github.com:salesforcecli/plugin-data…
cristiand391 Oct 30, 2024
382e46d
Merge remote-tracking branch 'origin/main' into cd/bulk-update
cristiand391 Oct 30, 2024
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
11 changes: 11 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
command:
- 'yarn test:nuts:bulk:export'
- 'yarn test:nuts:bulk:import'
- 'yarn test:nuts:bulk:update'
- 'yarn test:nuts:data:bulk-upsert-delete'
- 'yarn test:nuts:data:create'
- 'yarn test:nuts:data:query'
- 'yarn test:nuts:data:record'
- 'yarn test:nuts:data:search'
- 'yarn test:nuts:data:tree'
fail-fast: false
with:
os: ${{ matrix.os }}
command: ${{ matrix.command }}
40 changes: 39 additions & 1 deletion command-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,18 @@
"command": "data:import:bulk",
"flagAliases": [],
"flagChars": ["a", "f", "o", "s", "w"],
"flags": ["api-version", "async", "file", "flags-dir", "json", "line-ending", "sobject", "target-org", "wait"],
"flags": [
"api-version",
"async",
"column-delimiter",
"file",
"flags-dir",
"json",
"line-ending",
"sobject",
"target-org",
"wait"
],
"plugin": "@salesforce/plugin-data"
},
{
Expand Down Expand Up @@ -235,6 +246,25 @@
"flags": ["api-version", "file", "flags-dir", "json", "query", "result-format", "target-org"],
"plugin": "@salesforce/plugin-data"
},
{
"alias": [],
"command": "data:update:bulk",
"flagAliases": [],
"flagChars": ["a", "f", "o", "s", "w"],
"flags": [
"api-version",
"async",
"column-delimiter",
"file",
"flags-dir",
"json",
"line-ending",
"sobject",
"target-org",
"wait"
],
"plugin": "@salesforce/plugin-data"
},
{
"alias": ["force:data:record:update"],
"command": "data:update:record",
Expand All @@ -255,6 +285,14 @@
],
"plugin": "@salesforce/plugin-data"
},
{
"alias": [],
"command": "data:update:resume",
"flagAliases": [],
"flagChars": ["i", "w"],
"flags": ["flags-dir", "job-id", "json", "use-most-recent", "wait"],
"plugin": "@salesforce/plugin-data"
},
{
"alias": [],
"command": "data:upsert:bulk",
Expand Down
35 changes: 35 additions & 0 deletions messages/bulkIngest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# export.resume

Run "sf %s --job-id %s" to resume the operation.
cristiand391 marked this conversation as resolved.
Show resolved Hide resolved

# error.timeout

The operation timed out after %s minutes.

Run "sf %s --job-id %s" to resume it.

# error.failedRecordDetails

Job finished being processed but failed to process %s records.

To review the details of this job, run this command:

sf org open --target-org %s --path "/lightning/setup/AsyncApiJobStatus/page?address=%2F%s"

# error.jobFailed

Job failed to be processed due to:

%s

To review the details of this job, run this command:

sf org open --target-org %s --path "/lightning/setup/AsyncApiJobStatus/page?address=%2F%s"

# error.jobAborted

Job has been aborted.

To review the details of this job, run this command:

sf org open --target-org %s --path "/lightning/setup/AsyncApiJobStatus/page?address=%2F%s"
38 changes: 3 additions & 35 deletions messages/data.import.bulk.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,40 +40,8 @@ Time to wait for the command to finish, in minutes.

# flags.line-ending.summary

Line ending used in the CSV file. Default value on Windows is `CRLF`; on macOS and Linux it's `LR`.
Line ending used in the CSV file. Default value on Windows is `CRLF`; on macOS and Linux it's `LF`.

# export.resume
# flags.column-delimiter.summary

Run "sf data import resume --job-id %s" to resume the operation.

# error.timeout

The operation timed out after %s minutes.

Run "sf data import resume --job-id %s" to resume it.

# error.failedRecordDetails

Job finished being processed but failed to import %s records.

To review the details of this job, run this command:

sf org open --target-org %s --path "/lightning/setup/AsyncApiJobStatus/page?address=%2F%s"

# error.jobFailed

Job failed to be processed due to:

%s

To review the details of this job, run this command:

sf org open --target-org %s --path "/lightning/setup/AsyncApiJobStatus/page?address=%2F%s"

# error.jobAborted

Job has been aborted.

To review the details of this job, run this command:

sf org open --target-org %s --path "/lightning/setup/AsyncApiJobStatus/page?address=%2F%s"
Column delimiter used in the CSV file. Default is COMMA.
32 changes: 0 additions & 32 deletions messages/data.import.resume.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,35 +27,3 @@ Job ID of the bulk import.
# flags.wait.summary

Time to wait for the command to finish, in minutes.

# error.failedRecordDetails

Job finished being processed but failed to import %s records.

To review the details of this job, run this command:

sf org open --target-org %s --path "/lightning/setup/AsyncApiJobStatus/page?address=%2F%s"

# error.timeout

The operation timed out after %s minutes.

Try re-running "sf data import resume --job-id %s" with a bigger wait time.

# error.jobFailed

Job failed to be processed due to:

%s

To review the details of this job, run this command:

sf org open --target-org %s --path "/lightning/setup/AsyncApiJobStatus/page?address=%2F%s"

# error.jobAborted

Job has been aborted.

To review the details of this job, run this command:

sf org open --target-org %s --path "/lightning/setup/AsyncApiJobStatus/page?address=%2F%s"
47 changes: 47 additions & 0 deletions messages/data.update.bulk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# summary

Bulk update records to an org from a CSV file. Uses Bulk API 2.0.

# description

You can use this command to update millions of records into the object from a file in comma-separated values (CSV) format.

All the records in the CSV file must be for the same Salesforce object. Specify the object with the `--sobject` flag.

Bulk updates can take a while, depending on how many records are in the CSV file. If the command times out, or you specified the --async flag, the command displays the job ID. To see the status and get the results of the job, run "sf data update resume" and pass the job ID to the --job-id flag.

For information and examples about how to prepare your CSV files, see "Prepare Data to Ingest" in the "Bulk API 2.0 and Bulk API Developer Guide" (https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/datafiles_prepare_data.htm).

# examples

- Update Account records from a CSV-formatted file into an org with alias "my-scratch"; if the update doesn't complete in 10 minutes, the command ends and displays a job ID:

<%= config.bin %> <%= command.id %> --file accounts.csv --sobject Account --wait 10 --target-org my-scratch

- Update asynchronously and use the default org; the command immediately returns a job ID that you then pass to the "sf data update resume" command:

<%= config.bin %> <%= command.id %> --file accounts.csv --sobject Account --async

# flags.async.summary

Don't wait for the command to complete.

# flags.wait.summary

Time to wait for the command to finish, in minutes.

# flags.file.summary

CSV file that contains the Salesforce object records you want to update.

# flags.sobject.summary

API name of the Salesforce object, either standard or custom, which you are updating.

# flags.line-ending.summary

Line ending used in the CSV file. Default value on Windows is `CRLF`; on macOS and Linux it's `LF`.

# flags.column-delimiter.summary

Column delimiter used in the CSV file. Default is COMMA.
29 changes: 29 additions & 0 deletions messages/data.update.resume.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# summary

Resume a bulk update job that you previously started. Uses Bulk API 2.0.

# description

When the original "sf data update bulk" command either times out or is run with the --async flag, it displays a job ID. To see the status and get the results of the bulk update, run this command by either passing it the job ID or using the --use-most-recent flag to specify the most recent bulk update job.

# examples

- Resume a bulk update job to your default org using an ID:

<%= config.bin %> <%= command.id %> --job-id 750xx000000005sAAA

- Resume the most recently run bulk update job for an org with alias my-scratch:

<%= config.bin %> <%= command.id %> --use-most-recent --target-org my-scratch

# flags.use-most-recent.summary

Use the job ID of the bulk update job that was most recently run.

# flags.job-id.summary

Job ID of the bulk update.

# flags.wait.summary

Time to wait for the command to finish, in minutes.
14 changes: 11 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@
"description": "Query records."
},
"update": {
"description": "Update a single record."
"description": "Update many records.",
"external": true
},
"upsert": {
"description": "Upsert many records."
Expand Down Expand Up @@ -103,8 +104,15 @@
"prepack": "sf-prepack",
"prepare": "sf-install",
"test": "wireit",
"test:nuts": "nyc mocha \"./test/**/*.nut.ts\" --slow 4500 --timeout 600000 --parallel --jobs 20",
"test:nuts:bulk": "nyc mocha \"./test/**/dataBulk.nut.ts\" --slow 4500 --timeout 600000 --parallel --jobs 20",
"test:nuts:bulk:import": "nyc mocha \"./test/commands/data/import/*.nut.ts\" --slow 4500 --timeout 600000 --parallel --jobs 20",
"test:nuts:bulk:export": "nyc mocha \"./test/commands/data/export/*.nut.ts\" --slow 4500 --timeout 600000 --parallel --jobs 20",
"test:nuts:bulk:update": "nyc mocha \"./test/commands/data/update/*.nut.ts\" --slow 4500 --timeout 600000 --parallel --jobs 20",
"test:nuts:data:tree": "nyc mocha \"./test/commands/data/tree/*.nut.ts\" --slow 4500 --timeout 600000 --parallel --jobs 20",
"test:nuts:data:query": "nyc mocha \"./test/commands/data/query/*.nut.ts\" --slow 4500 --timeout 600000 --parallel --jobs 20",
"test:nuts:data:record": "nyc mocha \"./test/commands/data/record/dataRecord.nut.ts\" --slow 4500 --timeout 600000 --parallel --jobs 20",
"test:nuts:data:search": "nyc mocha \"./test/commands/data/search.nut.ts\" --slow 4500 --timeout 600000 --parallel --jobs 20",
"test:nuts:data:create": "nyc mocha \"./test/commands/data/create/*.nut.ts\" --slow 4500 --timeout 600000 --parallel --jobs 20",
"test:nuts:data:bulk-upsert-delete": "nyc mocha \"./test/commands/data/dataBulk.nut.ts\" --slow 4500 --timeout 600000 --parallel --jobs 20",
"test:only": "wireit",
"version": "oclif readme"
},
Expand Down
69 changes: 69 additions & 0 deletions src/bulkDataRequestCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,75 @@ export class BulkImportRequestCache extends TTLConfig<TTLConfig.Options, BulkExp
}
}

export class BulkUpdateRequestCache extends TTLConfig<TTLConfig.Options, BulkExportCacheConfig> {
public static getDefaultOptions(): TTLConfig.Options {
return {
isGlobal: true,
isState: true,
filename: BulkUpdateRequestCache.getFileName(),
stateFolder: Global.SF_STATE_FOLDER,
ttl: Duration.days(7),
};
}

public static getFileName(): string {
return 'bulk-data-update-cache.json';
}

public static async unset(key: string): Promise<void> {
const cache = await BulkImportRequestCache.create();
cache.unset(key);
await cache.write();
}

/**
* Creates a new bulk data import cache entry for the given bulk request id.
*
* @param bulkRequestId
* @param username
* @param apiVersion
*/
public async createCacheEntryForRequest(bulkRequestId: string, username: string, apiVersion: string): Promise<void> {
this.set(bulkRequestId, {
jobId: bulkRequestId,
username,
apiVersion,
});
await this.write();
Logger.childFromRoot('BulkUpdateCache').debug(`bulk cache saved for ${bulkRequestId}`);
}

public async resolveResumeOptionsFromCache(jobIdOrMostRecent: string | boolean): Promise<ResumeBulkImportOptions> {
if (typeof jobIdOrMostRecent === 'boolean') {
const key = this.getLatestKey();
if (!key) {
throw messages.createError('error.missingCacheEntryError');
}
// key definitely exists because it came from the cache
const entry = this.get(key);

return {
jobInfo: { id: entry.jobId },
options: {
connection: (await Org.create({ aliasOrUsername: entry.username })).getConnection(),
},
};
} else {
const entry = this.get(jobIdOrMostRecent);
if (!entry) {
throw messages.createError('error.bulkRequestIdNotFound', [jobIdOrMostRecent]);
}

return {
jobInfo: { id: entry.jobId },
options: {
connection: (await Org.create({ aliasOrUsername: entry.username })).getConnection(),
},
};
}
}
}

export class BulkExportRequestCache extends TTLConfig<TTLConfig.Options, BulkExportCacheConfig> {
public static getDefaultOptions(): TTLConfig.Options {
return {
Expand Down
Loading