@salesforce/afv-skills 1.44.0 → 1.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/automation-flow-generate/SKILL.md +11 -5
- package/skills/consumer-goods-rtr-datacloud-export-configure/SKILL.md +72 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/references/inputs-and-namespace.md +38 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/references/procedure.md +158 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/detect-namespace.js +86 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/render-apex.js +64 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/resolve-id-by-name.js +47 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/sf-rest.js +171 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/soql-escape.js +26 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/upsert-report-config.apex +51 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/upsert-system-setting.apex +21 -0
- package/skills/consumer-goods-tpe-dashboard-configure/SKILL.md +74 -0
- package/skills/consumer-goods-tpe-dashboard-configure/references/phases-1-6.md +112 -0
- package/skills/consumer-goods-tpe-dashboard-configure/references/phases-7-12.md +157 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/find-failure-reason.js +132 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/poll-status.js +116 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/render-apex.js +64 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/run-data-transform.js +121 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/schedule-business-period-export.apex +27 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/sf-rest.js +171 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/soql-escape.js +25 -0
- package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/SKILL.md +141 -0
- package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/references/payload-shapes.md +447 -0
- package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/references/procedure.md +263 -0
- package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/scripts/clone-tpe-dashboards.js +537 -0
- package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/scripts/sf-rest.js +195 -0
- package/skills/consumer-goods-tpe-datakit-deploy/SKILL.md +157 -0
- package/skills/consumer-goods-tpe-datakit-deploy/scripts/detect-namespace.js +86 -0
- package/skills/consumer-goods-tpe-datakit-deploy/scripts/download-static-resource.js +151 -0
- package/skills/consumer-goods-tpe-datakit-deploy/scripts/extract-crm-field-permissions.js +115 -0
- package/skills/consumer-goods-tpe-datakit-deploy/scripts/sf-rest.js +109 -0
- package/skills/consumer-goods-tpe-datakit-deploy/scripts/update-field-permissions.js +433 -0
- package/skills/dx-code-analyzer-configure/scripts/validate-config.sh +14 -10
- package/skills/dx-code-analyzer-run/scripts/apply-fixes.js +45 -4
- package/skills/dx-code-analyzer-run/scripts/describe-rule.js +52 -32
- package/skills/platform-apex-logs-debug/SKILL.md +7 -7
- package/skills/platform-custom-application-generate/SKILL.md +4 -4
- package/skills/platform-custom-object-generate/SKILL.md +7 -7
- package/skills/platform-custom-tab-generate/SKILL.md +1 -1
- package/skills/platform-flexipage-generate/SKILL.md +4 -0
- package/skills/platform-list-view-generate/SKILL.md +1 -0
- package/skills/platform-soql-query/SKILL.md +8 -8
- package/skills/platform-value-set-generate/SKILL.md +2 -2
- package/skills/service-catalog-template-coordinate/SKILL.md +263 -0
- package/skills/service-catalog-template-coordinate/examples/output-templates.md +44 -0
- package/skills/service-catalog-template-coordinate/references/mcp-invocation.md +183 -0
- package/skills/service-catalog-template-coordinate/references/operations.md +230 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/SKILL.md +243 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/references/cli-invocation.md +205 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/references/helper-contracts.md +236 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/references/permset-topology.md +132 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-activated-agents.mjs +106 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-agent-access-state.mjs +113 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-assignment-state.mjs +99 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-platform-permset-availability.mjs +155 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/gate-unified-catalog-tiers.mjs +100 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/rank-candidate-users.mjs +95 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/resolve-target-user.mjs +86 -0
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +44 -23
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +33 -9
- package/skills/service-itsm-agentic-setup-agentforce-studio-configure/SKILL.md +17 -18
- package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +3 -1
- package/skills/service-itsm-agentic-setup-configure/SKILL.md +20 -12
- package/skills/service-itsm-agentic-setup-configure/examples/output-templates.md +73 -5
- package/skills/service-itsm-agentic-setup-employee-agent-configure/SKILL.md +8 -7
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/cli-invocation.md +45 -33
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/reactivation.md +8 -6
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/workflow-detail.md +10 -10
- package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-agent-existence.mjs +114 -56
- package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-preflight.mjs +33 -17
- package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/render-report.mjs +9 -3
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/SKILL.md +8 -7
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/cli-invocation.md +43 -32
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/reactivation.md +6 -4
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/workflow-detail.md +10 -10
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-agent-existence.mjs +106 -55
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-preflight.mjs +27 -13
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/render-report.mjs +9 -3
- package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +159 -161
- package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone-action.json +51 -0
- package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone.json +1 -1
- package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +120 -0
- package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +28 -5
- package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +19 -1
- package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +350 -30
- package/skills/service-itsm-channels-coordinate/SKILL.md +80 -213
- package/skills/service-itsm-slack-itservice-configure/SKILL.md +363 -0
- package/skills/service-itsm-slack-itservice-configure/references/connect-agentforce-to-slack.md +159 -0
- package/skills/service-itsm-slack-itservice-configure/references/manage-slack-connection.md +88 -0
- package/skills/service-itsm-slack-itservice-configure/references/manage-user-access.md +117 -0
- package/skills/service-itsm-slack-itservice-configure/references/record-visibility.md +78 -0
- package/skills/service-itsm-slack-itservice-configure/references/site-membership-verification.md +126 -0
- package/skills/service-itsm-slack-itservice-configure/scripts/classify-user-access.mjs +167 -0
- package/skills/service-itsm-teams-configure/SKILL.md +50 -47
- package/skills/service-itsm-teams-configure/references/azure-credential-population.md +42 -28
- package/skills/service-itsm-teams-configure/references/gotchas.md +1 -2
- package/skills/service-itsm-teams-coordinate/SKILL.md +22 -18
- package/skills/service-itsm-teams-coordinate/examples/output-templates.md +12 -9
- package/skills/service-itsm-teams-itdesk-configure/SKILL.md +60 -44
- package/skills/service-itsm-teams-itservice-configure/SKILL.md +56 -70
- package/skills/service-catalog-template-deploy/SKILL.md +0 -310
- package/skills/service-catalog-template-deploy/references/cli-invocation.md +0 -258
- package/skills/service-catalog-template-deploy/scripts/activate-verify.mjs +0 -164
- package/skills/service-catalog-template-deploy/scripts/build-deploy-payload.mjs +0 -94
- package/skills/service-catalog-template-deploy/scripts/resolve-template.mjs +0 -331
- package/skills/service-catalog-template-search/SKILL.md +0 -212
- package/skills/service-catalog-template-search/references/cli-invocation.md +0 -128
- package/skills/service-catalog-template-search/scripts/classify-catalog.mjs +0 -205
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Authenticated Salesforce REST callout, replacing `sf api request rest`
|
|
4
|
+
* everywhere in this skill — that command is beta, doesn't support --json,
|
|
5
|
+
* and beta CLI surfaces can change or be pulled without notice. Fetches the
|
|
6
|
+
* org's instanceUrl and access token via the `sf` CLI, then performs a
|
|
7
|
+
* plain HTTP request with Node's built-in fetch. Access token retrieval is
|
|
8
|
+
* version-gated the same way as `datakit-for-tpe`'s
|
|
9
|
+
* `download-static-resource.js`: `sf org auth show-access-token` for CLI
|
|
10
|
+
* >= 2.136.8 (below that version, `sf org display`'s own `result.accessToken`
|
|
11
|
+
* is a redacted hint string, not a real token — see the `sfJson`/`getAccessToken`
|
|
12
|
+
* pair below). The token is never printed.
|
|
13
|
+
*
|
|
14
|
+
* CLI usage:
|
|
15
|
+
* node sf-rest.js --target-org <username> --path <path> \
|
|
16
|
+
* [--method GET|POST|PATCH|DELETE] [--body <json-string>]
|
|
17
|
+
*
|
|
18
|
+
* <path> is relative to the org's instanceUrl, e.g.
|
|
19
|
+
* "/services/apexrest/tpm-api/..." or "/services/data/v67.0/...".
|
|
20
|
+
* Prints the response body as one line of JSON to stdout on both success
|
|
21
|
+
* and failure, so a non-2xx error body (e.g. `{"error":"..."}`) is still
|
|
22
|
+
* readable by the caller. Exit codes: 0 = 2xx response, 1 = non-2xx
|
|
23
|
+
* response, 2 = usage/CLI error.
|
|
24
|
+
*
|
|
25
|
+
* Module usage (for sibling scripts in this same skill's scripts/ dir —
|
|
26
|
+
* never imported across skill directories):
|
|
27
|
+
* const { restRequest } = require('./sf-rest');
|
|
28
|
+
* const { ok, status, body } = await restRequest({ targetOrg, path, method, body: jsonString });
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Also exposes two subcommands that print only safe, non-secret fields from
|
|
33
|
+
* `sf org display --json` — that command's `result` includes `accessToken`
|
|
34
|
+
* (a live credential), so nothing in this skill should run it directly and
|
|
35
|
+
* surface the raw output; these strip it before printing:
|
|
36
|
+
* node sf-rest.js org-status --target-org <username>
|
|
37
|
+
* -> {"username","alias","connectedStatus","orgId"} ; exit 1 if not Connected
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
const { execFileSync } = require('child_process');
|
|
41
|
+
|
|
42
|
+
function sfJson(cliArgs) {
|
|
43
|
+
const raw = execFileSync('sf', cliArgs, { encoding: 'utf8' });
|
|
44
|
+
return JSON.parse(raw).result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const ACCESS_TOKEN_VIA_SHOW_COMMAND_SINCE = [2, 136, 8];
|
|
48
|
+
|
|
49
|
+
function getCliVersion() {
|
|
50
|
+
// Unlike every other `sf ... --json` command, `sf version --json` returns
|
|
51
|
+
// its fields at the top level — no `{status, result}` envelope.
|
|
52
|
+
const raw = execFileSync('sf', ['version', '--json'], { encoding: 'utf8' });
|
|
53
|
+
const { cliVersion } = JSON.parse(raw);
|
|
54
|
+
const match = cliVersion.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
55
|
+
// A pre-release/suffixed version string (e.g. a nightly build) that
|
|
56
|
+
// doesn't match this pattern is treated as "new enough" below, via
|
|
57
|
+
// isAtLeast's null passthrough — never silently fall back to the
|
|
58
|
+
// redacted-token path on an unparseable version.
|
|
59
|
+
return match ? match.slice(1).map(Number) : null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isAtLeast(version, minVersion) {
|
|
63
|
+
if (!version) return true;
|
|
64
|
+
for (let i = 0; i < minVersion.length; i += 1) {
|
|
65
|
+
const a = version[i] || 0;
|
|
66
|
+
const b = minVersion[i] || 0;
|
|
67
|
+
if (a !== b) return a > b;
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getAccessToken(targetOrg) {
|
|
73
|
+
const useShowAccessTokenCommand = isAtLeast(
|
|
74
|
+
getCliVersion(),
|
|
75
|
+
ACCESS_TOKEN_VIA_SHOW_COMMAND_SINCE
|
|
76
|
+
);
|
|
77
|
+
if (useShowAccessTokenCommand) {
|
|
78
|
+
return sfJson([
|
|
79
|
+
'org', 'auth', 'show-access-token',
|
|
80
|
+
'--target-org', targetOrg,
|
|
81
|
+
'--no-prompt', '--json',
|
|
82
|
+
]).accessToken;
|
|
83
|
+
}
|
|
84
|
+
return sfJson(['org', 'display', '--target-org', targetOrg, '--json']).accessToken;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function restRequest({ targetOrg, path, method, body }) {
|
|
88
|
+
const { instanceUrl } = sfJson(['org', 'display', '--target-org', targetOrg, '--json']);
|
|
89
|
+
const accessToken = getAccessToken(targetOrg);
|
|
90
|
+
|
|
91
|
+
const headers = { Authorization: `Bearer ${accessToken}` };
|
|
92
|
+
if (body !== undefined) headers['Content-Type'] = 'application/json';
|
|
93
|
+
|
|
94
|
+
const response = await fetch(`${instanceUrl}${path}`, {
|
|
95
|
+
method: method || 'GET',
|
|
96
|
+
headers,
|
|
97
|
+
body,
|
|
98
|
+
});
|
|
99
|
+
const text = await response.text();
|
|
100
|
+
let parsedBody = {};
|
|
101
|
+
if (text.length) {
|
|
102
|
+
try {
|
|
103
|
+
parsedBody = JSON.parse(text);
|
|
104
|
+
} catch {
|
|
105
|
+
parsedBody = text;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { ok: response.ok, status: response.status, body: parsedBody };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function getOrgStatus(targetOrg) {
|
|
112
|
+
const info = sfJson(['org', 'display', '--target-org', targetOrg, '--json']);
|
|
113
|
+
return {
|
|
114
|
+
username: info.username,
|
|
115
|
+
alias: info.alias || null,
|
|
116
|
+
connectedStatus: info.connectedStatus,
|
|
117
|
+
orgId: info.id,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function parseArgs(argv) {
|
|
122
|
+
const args = {};
|
|
123
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
124
|
+
const token = argv[i];
|
|
125
|
+
if (!token.startsWith('--')) continue;
|
|
126
|
+
args[token.slice(2)] = argv[i + 1];
|
|
127
|
+
i += 1;
|
|
128
|
+
}
|
|
129
|
+
return args;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function requireArg(args, name) {
|
|
133
|
+
if (!args[name]) {
|
|
134
|
+
console.error(`Missing required --${name}`);
|
|
135
|
+
process.exit(2);
|
|
136
|
+
}
|
|
137
|
+
return args[name];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function main() {
|
|
141
|
+
const [maybeCommand, ...rest] = process.argv.slice(2);
|
|
142
|
+
|
|
143
|
+
if (maybeCommand === 'org-status') {
|
|
144
|
+
const args = parseArgs(rest);
|
|
145
|
+
const status = getOrgStatus(requireArg(args, 'target-org'));
|
|
146
|
+
process.stdout.write(JSON.stringify(status));
|
|
147
|
+
if (status.connectedStatus !== 'Connected') process.exit(1);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const args = parseArgs(process.argv.slice(2));
|
|
152
|
+
const targetOrg = requireArg(args, 'target-org');
|
|
153
|
+
const path = requireArg(args, 'path');
|
|
154
|
+
const method = args.method || 'GET';
|
|
155
|
+
|
|
156
|
+
const { ok, status, body } = await restRequest({ targetOrg, path, method, body: args.body });
|
|
157
|
+
process.stdout.write(JSON.stringify(body));
|
|
158
|
+
if (!ok) {
|
|
159
|
+
console.error(`sf-rest: HTTP ${status}`);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (require.main === module) {
|
|
165
|
+
main().catch((err) => {
|
|
166
|
+
console.error(err);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = { restRequest, getOrgStatus };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Escape a value for safe interpolation into a single-quoted SOQL/Apex
|
|
4
|
+
* string literal — escape backslashes first, then single quotes, so a value
|
|
5
|
+
* like `O'Brien Foods` (or one ending in a backslash) can't break out of the
|
|
6
|
+
* literal it's placed in. Shared by render-apex.js (Apex template
|
|
7
|
+
* substitution) and any SKILL step that builds a `sf data query` string by
|
|
8
|
+
* hand from admin-supplied free text (Sales Org / KPI Set names).
|
|
9
|
+
*
|
|
10
|
+
* CLI usage: node scripts/soql-escape.js "O'Brien Foods"
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
function escapeSoqlLiteral(value) {
|
|
14
|
+
return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (require.main === module) {
|
|
18
|
+
const value = process.argv[2];
|
|
19
|
+
if (value === undefined) {
|
|
20
|
+
console.error('Usage: soql-escape.js <value>');
|
|
21
|
+
process.exit(2);
|
|
22
|
+
}
|
|
23
|
+
console.log(escapeSoqlLiteral(value));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = { escapeSoqlLiteral };
|
package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/upsert-report-config.apex
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Idempotent upsert of an RTR_Report_Configuration__c row by
|
|
2
|
+
// (Usage__c, Internal_Name__c, Sales_Organization__c). Usage is restricted to
|
|
3
|
+
// the two Data-Cloud-targeted values this skill supports:
|
|
4
|
+
// 'datacloudpromotionmeasuresmeta' or 'datacloudaccountplanmeasuresmeta'
|
|
5
|
+
// (distinct from the CSV-export 'integrationmeta'/'reportmeta' pair used by
|
|
6
|
+
// the sibling setup-rtr-export skill). JSON__c is assembled off-Salesforce as
|
|
7
|
+
// {"measurecodes":[...],"datefrom":"YYYY-MM-DD","object":"promotionmeasures"
|
|
8
|
+
// |"accountplanmeasures"} and passed in as %JSON_LITERAL%. Each placeholder
|
|
9
|
+
// below is substituted exactly once, into a String variable declaration —
|
|
10
|
+
// the only place a raw value is embedded as an Apex literal, and
|
|
11
|
+
// render-apex.js escapes it there (see soql-escape.js). The SOQL WHERE
|
|
12
|
+
// clause and every field assignment then use the resulting Apex variables as
|
|
13
|
+
// SOQL bind variables (:usage, :internalName, ...) or plain assignments —
|
|
14
|
+
// never a second literal substitution of the same placeholder. Pass raw,
|
|
15
|
+
// unescaped values for every placeholder below.
|
|
16
|
+
//
|
|
17
|
+
// %NS% -> namespace field prefix e.g. "cgcloud_dev__"
|
|
18
|
+
// %USAGE% -> 'datacloudpromotionmeasuresmeta' or
|
|
19
|
+
// 'datacloudaccountplanmeasuresmeta'
|
|
20
|
+
// %INTERNAL_NAME% -> free-text admin-supplied name (e.g. "O'Brien Foods")
|
|
21
|
+
// %SALES_ORG_ID% -> 15/18-char Sales_Organization__c Id
|
|
22
|
+
// %KPI_SET_ID% -> 15/18-char KPI_Set__c Id
|
|
23
|
+
// %JSON_LITERAL% -> the full JSON body, raw (not pre-escaped)
|
|
24
|
+
String usage = '%USAGE%';
|
|
25
|
+
String internalName = '%INTERNAL_NAME%';
|
|
26
|
+
String salesOrgId = '%SALES_ORG_ID%';
|
|
27
|
+
String kpiSetId = '%KPI_SET_ID%';
|
|
28
|
+
String jsonLiteral = '%JSON_LITERAL%';
|
|
29
|
+
|
|
30
|
+
List<%NS%RTR_Report_Configuration__c> existing = [
|
|
31
|
+
SELECT Id
|
|
32
|
+
FROM %NS%RTR_Report_Configuration__c
|
|
33
|
+
WHERE %NS%Usage__c = :usage
|
|
34
|
+
AND %NS%Internal_Name__c = :internalName
|
|
35
|
+
AND %NS%Sales_Organization__c = :salesOrgId
|
|
36
|
+
WITH USER_MODE
|
|
37
|
+
LIMIT 1
|
|
38
|
+
];
|
|
39
|
+
%NS%RTR_Report_Configuration__c row;
|
|
40
|
+
if (existing.isEmpty()) {
|
|
41
|
+
row = new %NS%RTR_Report_Configuration__c();
|
|
42
|
+
row.%NS%Usage__c = usage;
|
|
43
|
+
row.%NS%Internal_Name__c = internalName;
|
|
44
|
+
row.%NS%Sales_Organization__c = salesOrgId;
|
|
45
|
+
} else {
|
|
46
|
+
row = existing[0];
|
|
47
|
+
}
|
|
48
|
+
row.%NS%Reporting_KPI_Set__c = kpiSetId;
|
|
49
|
+
row.%NS%JSON__c = jsonLiteral;
|
|
50
|
+
upsert as user row;
|
|
51
|
+
System.debug(LoggingLevel.INFO, 'RTR_DATACLOUD_CONFIG_ID=' + row.Id);
|
package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/upsert-system-setting.apex
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Idempotent upsert of the Data_Cloud_Enabled System_Setting__c row.
|
|
2
|
+
// Query-then-upsert instead of `sf data upsert record` — that CLI subcommand
|
|
3
|
+
// only supports bulk/resume CSV, not a single record. Placeholder:
|
|
4
|
+
// %NS% -> namespace field prefix, e.g. "cgcloud_dev__" (empty for source org)
|
|
5
|
+
List<%NS%System_Setting__c> existing = [
|
|
6
|
+
SELECT Id
|
|
7
|
+
FROM %NS%System_Setting__c
|
|
8
|
+
WHERE Name = 'Data_Cloud_Enabled'
|
|
9
|
+
WITH USER_MODE
|
|
10
|
+
LIMIT 1
|
|
11
|
+
];
|
|
12
|
+
%NS%System_Setting__c row;
|
|
13
|
+
if (existing.isEmpty()) {
|
|
14
|
+
row = new %NS%System_Setting__c();
|
|
15
|
+
row.Name = 'Data_Cloud_Enabled';
|
|
16
|
+
} else {
|
|
17
|
+
row = existing[0];
|
|
18
|
+
}
|
|
19
|
+
row.%NS%Value__c = 'true';
|
|
20
|
+
upsert as user row;
|
|
21
|
+
System.debug(LoggingLevel.INFO, 'RTR_DATACLOUD_SETTING_ID=' + row.Id);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: consumer-goods-tpe-dashboard-configure
|
|
3
|
+
description: "End-to-end headless setup of Trade Promotion Effectiveness (TPE) dashboards for a Trade Promotion Management (TPM) Cloud org, covering tenant pairing checks, permission sets, SSOT and Tableau Next enablement, C360 SDM verification, the TPM Accruals data kit install, business-period export, RTR Data Cloud export configuration, the TPM_PROMOTION_MEASURE Data Transform, TPE Analytics app install, and optional KPI dashboard customization. Use when a customer or admin wants to set up TPE dashboards, enable Trade Promotion Effectiveness, install the TPE analytics app, or set up and customize TPE dashboards with their own KPIs."
|
|
4
|
+
metadata:
|
|
5
|
+
version: "1.0"
|
|
6
|
+
minApiVersion: "67.0"
|
|
7
|
+
domains: ["Consumer Goods"]
|
|
8
|
+
relatedSkills:
|
|
9
|
+
- "consumer-goods-rtr-datacloud-export-configure"
|
|
10
|
+
- "consumer-goods-tpe-dashboard-custom-kpi-configure"
|
|
11
|
+
- "consumer-goods-tpe-datakit-deploy"
|
|
12
|
+
cliTools:
|
|
13
|
+
- tool: ["node"]
|
|
14
|
+
semver: ">=20.0.0"
|
|
15
|
+
- tool: ["sf"]
|
|
16
|
+
semver: ">=2.0.0"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Consumer Goods TPE Dashboard Configure
|
|
20
|
+
|
|
21
|
+
Bring a TPM org from bare state to a running TPE dashboard in one guided session, driven entirely by `sf` CLI + REST/anonymous-Apex against an already-authenticated org — never a password or interactive login on the user's behalf.
|
|
22
|
+
|
|
23
|
+
Persona: a **Salesforce TPM System Admin** setting up TPE dashboards for their org.
|
|
24
|
+
|
|
25
|
+
Delegates three of its twelve steps to standalone sibling skills (own `SKILL.md`, own inputs, self-contained per this repo's skills architecture rules — no shared files):
|
|
26
|
+
|
|
27
|
+
- Step 7 → `consumer-goods-tpe-datakit-deploy`
|
|
28
|
+
- Step 9 → `consumer-goods-rtr-datacloud-export-configure`
|
|
29
|
+
- Step 12 → `consumer-goods-tpe-dashboard-custom-kpi-configure` (optional, only if the customer wants custom KPIs on the base dashboards)
|
|
30
|
+
|
|
31
|
+
Every other step runs directly in this skill.
|
|
32
|
+
|
|
33
|
+
## Inputs to collect first
|
|
34
|
+
|
|
35
|
+
Ask before starting. Do not guess.
|
|
36
|
+
|
|
37
|
+
1. **TPM System Admin username** — must already be authenticated via `sf` CLI. Verify with `node ./scripts/sf-rest.js org-status --target-org <username>` (never `sf org display --json` directly — see `references/phases-1-6.md`).
|
|
38
|
+
2. **Dry-run?** — offer by default. In dry-run, every read/verify/check step still runs; every write/trigger/install step is skipped and recorded as `pending — not run (dry-run)`.
|
|
39
|
+
|
|
40
|
+
Also detect the installed package namespace and version-gate the org (Consumer Goods Cloud >= 262.2 required) before Phase 1 — see "Namespace detection" in `references/phases-1-6.md`.
|
|
41
|
+
|
|
42
|
+
## Phase list
|
|
43
|
+
|
|
44
|
+
| Phase | Step | Summary | Details |
|
|
45
|
+
|---|---|---|---|
|
|
46
|
+
| Setup | — | Collect inputs, detect namespace, version-gate the org | `references/phases-1-6.md` |
|
|
47
|
+
| 1 | Tenant pairing | Ask user to confirm Hyperforce Processing Service pairing (not verifiable programmatically) | `references/phases-1-6.md` |
|
|
48
|
+
| 2 | TPM Admin permission set(s) | Ask user to confirm org-specific persona permission sets are assigned | `references/phases-1-6.md` |
|
|
49
|
+
| 3 | GenieAdmin / TableauEinsteinAdmin | Query, and on confirmation assign, these two permission sets | `references/phases-1-6.md` |
|
|
50
|
+
| 4 | SSOT package | Verify the `ssot` managed package is installed | `references/phases-1-6.md` |
|
|
51
|
+
| 5 | Enable Tableau Next | Ask user to confirm manual UI enablement (not verifiable programmatically) | `references/phases-1-6.md` |
|
|
52
|
+
| 6 | C360 SDM | Verify or trigger + poll the C360 Unified Semantic Model app install | `references/phases-1-6.md` |
|
|
53
|
+
| 7 | TPM Accruals Data Kit | **Delegated** to `consumer-goods-tpe-datakit-deploy` | `references/phases-7-12.md` |
|
|
54
|
+
| 8 | Business Period export | Anonymous-Apex export via a Hyperforce-substrate Sales Org, poll, then confirm the `tpmbusinessperiod` Data Stream sync | `references/phases-7-12.md` |
|
|
55
|
+
| 9 | RTR Data Cloud export | **Delegated** to `consumer-goods-rtr-datacloud-export-configure` | `references/phases-7-12.md` |
|
|
56
|
+
| 10 | TPM_PROMOTION_MEASURE Data Transform | Ask (credit consumption), then run and poll the Data Transform | `references/phases-7-12.md` |
|
|
57
|
+
| 11 | TPE Analytics App install | Trigger + poll the async app-framework install (~15-20 min) | `references/phases-7-12.md` |
|
|
58
|
+
| 12 | Customize KPIs (optional) | **Delegated** to `consumer-goods-tpe-dashboard-custom-kpi-configure`, only if requested | `references/phases-7-12.md` |
|
|
59
|
+
|
|
60
|
+
`references/phases-7-12.md` also documents the shared `scripts/poll-status.js` poller and the full set of hard Rules (never authenticate on the user's behalf, never hardcode a namespace or Sales Org, never claim success on anything but a terminal success status, a poll timeout is not a failure, etc.) that apply across every phase.
|
|
61
|
+
|
|
62
|
+
## Scripts
|
|
63
|
+
|
|
64
|
+
- `scripts/sf-rest.js` — authenticated REST calls; also exposes `org-status` (strips `accessToken` before printing).
|
|
65
|
+
- `scripts/poll-status.js` — generic async-status poller, reused by Phases 6 and 11.
|
|
66
|
+
- `scripts/find-failure-reason.js` — extracts the actual per-requirement failure reason for a `FailedStatus` app-framework install (Phase 11), including "DMO not available" detection.
|
|
67
|
+
- `scripts/render-apex.js` — renders an anonymous-Apex template with `--var` substitutions; loads `scripts/soql-escape.js` to escape substituted literals.
|
|
68
|
+
- `scripts/soql-escape.js` — escapes a value for safe interpolation into a single-quoted SOQL/Apex string literal.
|
|
69
|
+
- `scripts/run-data-transform.js` — triggers and polls a named Data Transform (Phase 10).
|
|
70
|
+
- `scripts/schedule-business-period-export.apex` — anonymous-Apex template for Phase 8.
|
|
71
|
+
|
|
72
|
+
## Report
|
|
73
|
+
|
|
74
|
+
At the end of a run, give the user a structured status per phase (1-12): pass / blocked / pending, with the specific blocking reason and remediation click-path where applicable. Call out anything skipped due to dry-run, a declined confirmation, or (Phase 12) the customer opting out of KPI customization.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Phases 1-6 — Prerequisites, Permission Sets, SSOT, Tableau Next, C360 SDM
|
|
2
|
+
|
|
3
|
+
## Inputs to collect first
|
|
4
|
+
|
|
5
|
+
Ask before starting. Do not guess.
|
|
6
|
+
|
|
7
|
+
1. **TPM System Admin username** — must already be authenticated via `sf` CLI. Verify via the sibling script, never `sf org display --json` directly — that command's raw output includes `accessToken` (a live credential), which this script strips before printing:
|
|
8
|
+
```bash
|
|
9
|
+
node ./scripts/sf-rest.js org-status --target-org <username>
|
|
10
|
+
```
|
|
11
|
+
Prints `{"username","alias","connectedStatus","orgId"}`. Non-zero exit or `connectedStatus != "Connected"` ⇒ stop, ask the user to run `sf org login web` (or their usual login flow for sf-cli) and re-run this check. Every subsequent command in this skill uses `--target-org <username>`.
|
|
12
|
+
2. **Dry-run?** — offer by default. In dry-run, every read/verify/check step still runs; every write/trigger/install step is skipped and recorded as `pending — not run (dry-run)`.
|
|
13
|
+
|
|
14
|
+
## Namespace detection (run once, before Phase 1)
|
|
15
|
+
|
|
16
|
+
This package's namespace is never assumed — it will be a managed-package namespace (e.g. `cgcloud`). Detect once and reuse everywhere below:
|
|
17
|
+
|
|
18
|
+
1. ```bash
|
|
19
|
+
sf package installed list --target-org <username> --json
|
|
20
|
+
```
|
|
21
|
+
Find the entry with `SubscriberPackageName == "Consumer Goods Cloud"`. If found, `NS` = its `SubscriberPackageNamespace`, and **version-gate immediately** (step 4 below) using that same entry's `SubscriberPackageVersionNumber` before doing anything else in this skill.
|
|
22
|
+
2. If package is not found inform the user and stop the process.
|
|
23
|
+
3. **Minimum package version gate** (managed-package installs only, i.e. step 1 found a match): this skill's features require **Consumer Goods Cloud package version 262.2 or later**. Parse `SubscriberPackageVersionNumber` (format `<major>.<minor>.<patch>.<build>`, e.g. `"262.2.0.3"`) into `(major, minor)`. Compare against the required `(262, 2)`:
|
|
24
|
+
- `major > 262`, or (`major == 262` and `minor >= 2`) ⇒ pass, continue.
|
|
25
|
+
- Otherwise ⇒ **stop immediately, before Phase 1.** Tell the user plainly, with both numbers spelled out:
|
|
26
|
+
*"This org has Consumer Goods Cloud package version `<installed SubscriberPackageVersionNumber>` installed. TPE dashboard setup requires version `262.2` or later. Please upgrade the package before continuing."*
|
|
27
|
+
Do not proceed into any phase — this is a hard block, not a warning, since the underlying feature genuinely isn't present below that version.
|
|
28
|
+
|
|
29
|
+
Derive, and reuse for every step below and when delegating to `consumer-goods-rtr-datacloud-export-configure` (Phase 9):
|
|
30
|
+
|
|
31
|
+
- `NS_SEGMENT` — REST URL path segment: `"<NS>/"` if `NS` is non-empty, else `""`. Insert immediately after `/services/apexrest/`. (Standard Salesforce platform REST APIs under `/services/data/...` — Phases 6 and 11 — are never namespaced; don't apply this there.)
|
|
32
|
+
- `NS_APEX` — Apex class-reference prefix: `"<NS>."` if `NS` is non-empty, else `""`. Prefix every reference to a class from this package (`OffPlatformCallout`, `OffPlatformCalloutResponse`, `TransactionHandler`, etc.) in anonymous Apex with this.
|
|
33
|
+
- `NS_FIELD` — custom object/field API-name prefix: `"<NS>__"` if `NS` is non-empty, else `""`. Prefix every custom object and custom field API name (standard fields like `Id`/`Name` never take this prefix) in SOQL and `sf data`/`sf sobject` commands with this.
|
|
34
|
+
|
|
35
|
+
## Phase 1 (Step 1) — Tenant pairing status
|
|
36
|
+
|
|
37
|
+
This cannot be verified programmatically — there is no supported, non-beta way to check Processing Service tenant pairing status from this skill. Explicitly ask the user for a **yes/no confirmation** (e.g. via the `AskUserQuestion` tool if available, with Yes/No options) — never state it as a plain sentence the user could mistake for informational text: *"Is the Hyperforce Processing Service paired for this org?"* Proceed on yes; on no or no response, stop here and tell the user to complete Processing Service pairing before continuing.
|
|
38
|
+
|
|
39
|
+
Record the user's confirmation for the final report.
|
|
40
|
+
|
|
41
|
+
## Phase 2 (Step 2) — Permission set(s) for the TPM Admin persona
|
|
42
|
+
|
|
43
|
+
"TPM Admin persona" is a role, not an actual permission set name. This cannot be verified programmatically — the exact permission set name/assignment backing that persona is org-specific. Do not query or infer it. Explicitly ask the user for a **yes/no confirmation** (e.g. via the `AskUserQuestion` tool if available, with Yes/No options) — never state it as a plain sentence the user could mistake for informational text: *"Are the permission set(s) required for the TPM Admin persona assigned to <username>?"* Wait for an explicit yes before continuing; on no, stop and tell the user to assign it first.
|
|
44
|
+
|
|
45
|
+
## Phase 3 (Step 3) — GenieAdmin / TableauEinsteinAdmin permission sets
|
|
46
|
+
|
|
47
|
+
1. Resolve both permission sets' Labels once, so every user-facing message below shows the Label — never the API `Name`:
|
|
48
|
+
```bash
|
|
49
|
+
sf data query --target-org <username> \
|
|
50
|
+
--query "SELECT Id, Name, Label FROM PermissionSet WHERE Name IN ('GenieAdmin','TableauEinsteinAdmin')"
|
|
51
|
+
```
|
|
52
|
+
2. Query assignments by `PermissionSet.Name` (still the stable API name, for the `WHERE` filter — only the Label from point 1 goes in front of the user):
|
|
53
|
+
```bash
|
|
54
|
+
sf data query --target-org <username> \
|
|
55
|
+
--query "SELECT PermissionSet.Name FROM PermissionSetAssignment WHERE Assignee.Username = '<username>' AND PermissionSet.Name IN ('GenieAdmin','TableauEinsteinAdmin')"
|
|
56
|
+
```
|
|
57
|
+
3. Whichever name is absent from the result rows is missing.
|
|
58
|
+
4. For each missing one, tell the user its **Label** (from point 1) and ask permission to assign it — e.g. *"User <username> is missing the <Label> permission set. Assign it now?"* Never assign without an explicit go-ahead.
|
|
59
|
+
5. On confirmation, use the Id already resolved in point 1 (no need to re-query):
|
|
60
|
+
```bash
|
|
61
|
+
sf data create record --sobject PermissionSetAssignment \
|
|
62
|
+
--values "AssigneeId=<userId> PermissionSetId=<psId>" --target-org <username>
|
|
63
|
+
```
|
|
64
|
+
If declined, skip and note it in the report.
|
|
65
|
+
6. Record the confirmed-assigned GenieAdmin result — Step 9's delegated skill reuses it (point 1 of its own Procedure) instead of re-querying.
|
|
66
|
+
|
|
67
|
+
## Phase 4 (Step 4) — SSOT package verification
|
|
68
|
+
|
|
69
|
+
1. ```bash
|
|
70
|
+
sf package installed list --target-org <username> --json
|
|
71
|
+
```
|
|
72
|
+
2. Check `result[]` for `SubscriberPackageNamespace == "ssot"` (Salesforce Standard Data Model).
|
|
73
|
+
3. Missing ⇒ tell the user SSOT isn't installed and ask them to install it manually from Data Cloud setup — this is a manual install, not scriptable. Stop and wait for confirmation, then re-run this check.
|
|
74
|
+
4. Present ⇒ record the installed version and continue.
|
|
75
|
+
|
|
76
|
+
## Phase 5 (Step 5) — Enable Tableau Next
|
|
77
|
+
|
|
78
|
+
TPE dashboards run on Tableau Next; this must be enabled before C360 SDM (Phase 6) and the TPE Analytics app install (Phase 11).
|
|
79
|
+
|
|
80
|
+
There is no reliable programmatic way to enable or verify this — it's a manual UI toggle only, and the `TableauIncludedAppManager` permission set check isn't a reliable signal of enablement. Do not query for it, and do not attempt any API call to enable it.
|
|
81
|
+
|
|
82
|
+
1. **Cannot be verified programmatically.** Explicitly ask the user for a **yes/no confirmation** (e.g. via the `AskUserQuestion` tool if available, with Yes/No options) — never state it as a plain sentence the user could mistake for informational text: *"Is Tableau Next enabled in this org? (Setup → Tableau Next Setup → Enable Tableau Next)"*
|
|
83
|
+
2. On yes, record enabled and continue to Phase 6.
|
|
84
|
+
3. On no, tell the user to enable it manually at Setup → Tableau Next Setup → Enable Tableau Next, then stop and wait — re-ask the same yes/no question before continuing. Do not proceed on a guess, and do not attempt any automated enablement.
|
|
85
|
+
|
|
86
|
+
## Phase 6 (Step 6) — C360 SDM verification and installation
|
|
87
|
+
|
|
88
|
+
1. **Check status** — via `scripts/sf-rest.js`, not `sf api request rest` (beta, no `--json`, can change without notice):
|
|
89
|
+
```bash
|
|
90
|
+
node ./scripts/sf-rest.js \
|
|
91
|
+
--target-org <username> \
|
|
92
|
+
--path "/services/data/v67.0/app-framework/apps?templateSourceId=sfdc_internal__C360UnifiedSemanticModel"
|
|
93
|
+
```
|
|
94
|
+
Any returned app with `applicationStatus` of `SuccessStatus` or `SuccessWithWarningsStatus` ⇒ record installed, move on.
|
|
95
|
+
2. **Not installed / not successful** — tell the user, ask permission to trigger installation. On confirmation:
|
|
96
|
+
```bash
|
|
97
|
+
node ./scripts/sf-rest.js \
|
|
98
|
+
--target-org <username> --method POST \
|
|
99
|
+
--path "/services/data/v67.0/app-framework/apps" \
|
|
100
|
+
--body '{"label":"C360_SDM","name":"C360_SDM","description":"C360 Unified Semantic Data Model","templateSourceId":"sfdc_internal__C360UnifiedSemanticModel","templateValues":{"isTest":false},"runtimeMethod":"Async"}'
|
|
101
|
+
```
|
|
102
|
+
Extract `app.id`, then poll with the shared poller:
|
|
103
|
+
```bash
|
|
104
|
+
node ./scripts/poll-status.js --target-org <username> \
|
|
105
|
+
--path "/services/data/v67.0/app-framework/apps/<appId>" \
|
|
106
|
+
--status-field applicationStatus \
|
|
107
|
+
--success-values SuccessStatus,SuccessWithWarningsStatus \
|
|
108
|
+
--failure-values FailedStatus \
|
|
109
|
+
--interval-seconds 30 --max-wait-seconds 900
|
|
110
|
+
```
|
|
111
|
+
Timeout while `InProgressStatus` ⇒ tell the user it's still installing, not a failure — re-run this step later. `FailedStatus` ⇒ stop, report failure, point to `/lightning/setup/C360/home` for a manual retry.
|
|
112
|
+
3. Declined ⇒ skip and note it; Phase 7 re-checks and re-prompts rather than assuming.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Phases 7-12 — Data Kit, Business Period Export, RTR Export, Data Transform, App Install, KPI Customization
|
|
2
|
+
|
|
3
|
+
## Phase 7 (Step 7) — TPM Accruals Data Kit for TPE (delegated)
|
|
4
|
+
|
|
5
|
+
Invoke the `consumer-goods-tpe-datakit-deploy` skill via the `Skill` tool, passing through:
|
|
6
|
+
|
|
7
|
+
- TPM admin username + dry-run flag (Inputs).
|
|
8
|
+
- The permission set(s) for the TPM Admin persona (Phase 2) and GenieAdmin (Phase 3) confirmation results, so it doesn't re-ask/re-query either.
|
|
9
|
+
- The detected `NS` (raw namespace string) from the Namespace detection step, so it doesn't re-detect.
|
|
10
|
+
|
|
11
|
+
`consumer-goods-tpe-datakit-deploy` accepts all of these as its own inputs (it also runs standalone, deriving them itself when not supplied). Treat its final report (pass/blocked/pending) as this phase's result and fold it into the overall report. **When `consumer-goods-tpe-datakit-deploy` returns, continue directly to Phase 8 in this same run — its own "Report" section is an intermediate result for this skill, not a signal to stop.**
|
|
12
|
+
|
|
13
|
+
## Phase 8 (Step 8) — Export TPM Business Period data
|
|
14
|
+
|
|
15
|
+
**Two distinct `salesorg` values — do not conflate them:** the `OffPlatformCallout` constructor argument is tenant/substrate routing only (must be a real Hyperforce-substrate Sales Org, called exactly once); the JSON body's `"salesorg":"*"` is a business-level "export for every sales org" parameter, not a platform-level wildcard.
|
|
16
|
+
|
|
17
|
+
This step must stay anonymous Apex, not a direct REST call to `ScheduleRTRExportService` (`/measures/export/schedule`, the same endpoint `consumer-goods-rtr-datacloud-export-configure` Phase 10 calls): that REST resource reads a single `salesorg` field out of its JSON body and uses that same value for **both** tenant/substrate routing (`SalesOrgSubstrate` mode — `SalesOrganizationServiceWOSharing.getTenantSubstrateEnum(salesorg)`) and the forwarded payload. `"*"` doesn't resolve to a real `Sales_Organization__c`, so it can't be used for routing — there's no way to decouple a real routing Sales Org from the payload's wildcard through that REST resource. The template below (run via `sf apex run`) keeps that decoupling: a real Sales Org drives routing via the `OffPlatformCallout` constructor; the JSON body's `salesorg` stays `"*"`.
|
|
18
|
+
|
|
19
|
+
1. Query for a routing Sales Org (never hardcode one), with `NS_FIELD` prefixed on the custom object and custom field (`Name` is standard, no prefix):
|
|
20
|
+
```bash
|
|
21
|
+
sf data query --target-org <username> \
|
|
22
|
+
--query "SELECT Name FROM <NS_FIELD>Sales_Organization__c WHERE <NS_FIELD>Tenant_Substrate__c = 'Hyperforce_AWS' LIMIT 1"
|
|
23
|
+
```
|
|
24
|
+
Zero rows ⇒ **block** — no Hyperforce-substrate Sales Org to route `SCHEDULE_RTR_EXPORT` through. Never fall back to a GCP-substrate org.
|
|
25
|
+
2. Render and run the template, passing that `Name` and `NS_APEX` (the sibling `scripts/render-apex.js`, same substitution pattern as `consumer-goods-rtr-datacloud-export-configure`'s templates):
|
|
26
|
+
```bash
|
|
27
|
+
node ./scripts/render-apex.js scripts/schedule-business-period-export.apex \
|
|
28
|
+
--var NS_APEX=<NS_APEX> --var SALES_ORG=<hyperforceSalesOrgName>
|
|
29
|
+
sf apex run --target-org <username> -f <rendered-path> --json
|
|
30
|
+
```
|
|
31
|
+
`sf apex run` provisions its own temporary trace flag for the execute-anonymous call automatically — no separate "enable debug logs" step is needed, and none should be asked of the user. Read the response back from the debug log line `SCHEDULE_RTR_EXPORT_RESPONSE=<response>` in the `--json` output's `result.logs`.
|
|
32
|
+
3. Inspect the returned response for a 2xx; log it in full. The response body field is `requestId` (confirmed live — not `csvGuid`, despite sharing the schedule endpoint used elsewhere); use its value as `<requestId>` below. Poll its status with the shared poller — its flat `{"Status": "..."}` body shape fits `poll-status.js`'s single-field contract just as well as the app-install responses in Phases 6 and 11 do. **The `salesorg` query param on this status call is tenant/substrate routing, not the business-level wildcard** — pass the same real Hyperforce-substrate Sales Org Name (`<hyperforceSalesOrgName>`) resolved in step 1/2, never the JSON body's `"*"`; passing `"*"` here (confirmed live) fails with `CGCloudException: {"reason":"CONFIGURATION","message":"Sales Org \"*\" not found."}`.
|
|
33
|
+
```bash
|
|
34
|
+
node ./scripts/poll-status.js --target-org <username> \
|
|
35
|
+
--path "/services/apexrest/<NS_SEGMENT>measures/export/<requestId>/status?salesorg=<hyperforceSalesOrgName>" \
|
|
36
|
+
--status-field Status \
|
|
37
|
+
--success-values Ready \
|
|
38
|
+
--failure-values Error,Aborted \
|
|
39
|
+
--interval-seconds 30 --max-wait-seconds 900
|
|
40
|
+
```
|
|
41
|
+
`Ready` = terminal success, `Error`/`Aborted` = terminal failure, `Queued`/`InProgress` = keep polling (timeout while non-terminal ⇒ not a failure — a later run can re-poll).
|
|
42
|
+
4. Record pass/blocked/pending.
|
|
43
|
+
5. **Sync the `tpmbusinessperiod` Data Stream.** Step 3's `Ready` status only confirms the export landed on the Processing Services host — the `tpmbusinessperiod` Data Stream in Data Cloud still needs to ingest it before the exported business-period data shows up downstream. This skill never triggers that sync itself — Data Stream sync/refresh isn't something this skill drives via `sf`/REST, and it's an action better left to the user to run interactively in Data Cloud setup. Only ask once step 3 reached `Ready` (skip if step 3 failed or is still pending). This is a hard blocking gate, not a status update — pose it as an explicit yes/no confirmation question, not a plain narrated sentence that moves straight on to Phase 9: *"The tpmbusinessperiod export has completed. Please go to Data Cloud → Data Streams → `tpmbusinessperiod`, sync it, and wait until the sync finishes (or wait for the scheduled sync). Has the sync finished?"* Do not proceed to Phase 9 on a generic "continue"/"next step" from the user — that answers a different question (moving the overall flow along), not this specific one (whether the Data Stream sync is actually done). Wait for an explicit confirmation before moving to Phase 9. Dry-run ⇒ skip, record `pending — not run (dry-run)`.
|
|
44
|
+
|
|
45
|
+
## Phase 9 (Step 9) — Data Cloud measures RTR export (delegated)
|
|
46
|
+
|
|
47
|
+
Invoke the `consumer-goods-rtr-datacloud-export-configure` skill via the `Skill` tool, passing through:
|
|
48
|
+
|
|
49
|
+
- TPM admin username + dry-run flag (Inputs).
|
|
50
|
+
- The detected `NS` (raw namespace string) from the Namespace detection step, so it doesn't re-detect.
|
|
51
|
+
- The GenieAdmin confirmation result from Phase 3 (so it isn't re-queried).
|
|
52
|
+
|
|
53
|
+
Treat its final report as this phase's result and fold it into the overall report. **When it returns, continue directly to Phase 10 in this same run — do not stop here.**
|
|
54
|
+
|
|
55
|
+
## Phase 10 (Step 10) — TPM_PROMOTION_MEASURE Data Transform
|
|
56
|
+
|
|
57
|
+
1. **Wait until all CRM Data Streams are synced before running the transform.** The TPM_PROMOTION_MEASURE Data Transform reads from CRM Data Streams — running it before they've finished syncing produces stale or incomplete measures. This cannot be verified programmatically — never check or infer stream status yourself (e.g. via SOQL against `DataStream`/`DataStreamHistory`, Tooling/REST calls, or any other query); the user is the sole source of truth here. Explicitly ask the user for a **yes/no confirmation** (e.g. via the `AskUserQuestion` tool if available, with Yes/No options) — never state it as a plain sentence the user could mistake for informational text: *"Before we run the Data Transform, please make sure all the relevant CRM Data Streams have finished syncing — every one should show Last Run Status = Success. Have all of them reached Success? (You can find the list at Data Cloud → Setup → Data Kits → TPM Accruals → Data Stream Bundles → expand the TPM section.)"* Do not proceed to step 2 on a generic "continue"/"next step" — wait for explicit confirmation.
|
|
58
|
+
- If the user says any stream isn't `Success` yet: `None` — has never run; ask them to manually refresh the stream. `Pending` — a run is already queued; ask them to just refresh the status (not the stream itself — it's already running, refreshing the stream would just requeue it). Then re-ask the same yes/no question.
|
|
59
|
+
Dry-run ⇒ skip, record `pending — not run (dry-run)`.
|
|
60
|
+
2. **Ask before running — Data Cloud credit consumption.** Tell the user plainly: *"Running the TPM_PROMOTION_MEASURE Data Transform will consume Data Cloud credits proportional to the volume of promotion-measure data processed. Proceed?"* Never trigger without an explicit yes. In dry-run, skip and record `pending — not run (dry-run)`.
|
|
61
|
+
3. On confirmation, hand off to the shared script. This Data Transform's name is never namespaced, regardless of the package namespace detected earlier — pass it as-is:
|
|
62
|
+
```bash
|
|
63
|
+
node ./scripts/run-data-transform.js --target-org <username> --transform-name TPM_PROMOTION_MEASURE
|
|
64
|
+
```
|
|
65
|
+
4. Record pass/blocked/pending, the final `lastRunStatus`, and (on failure) `lastRunErrorMessage`. A timeout while `lastRunStatus` is still non-terminal after the script's 1-hour default wait is not a failure — a later run can re-poll (pass `--max-wait-seconds` explicitly for a longer window if needed).
|
|
66
|
+
|
|
67
|
+
## Phase 11 (Step 11) — Install TPE Analytics App
|
|
68
|
+
|
|
69
|
+
1. **Prerequisite check** — Tableau Next enabled (Phase 5), GenieAdmin (Phase 3), C360 SDM `SuccessStatus` (Phase 6), and data kit deployed (Phase 7) are already independently verified earlier in this run; treat as a fast confirmation, not a re-block, when all four already passed. Otherwise re-check and block on whichever failed.
|
|
70
|
+
2. **Install trigger** — the confirmed TPE template id is `sfdc_internal__Trade_Promotion_Effectiveness` (not the MCP tooling's built-in ids). Via `scripts/sf-rest.js`, not `sf api request rest`:
|
|
71
|
+
```bash
|
|
72
|
+
node ./scripts/sf-rest.js \
|
|
73
|
+
--target-org <username> --method POST \
|
|
74
|
+
--path "/services/data/v67.0/app-framework/apps" \
|
|
75
|
+
--body '{"label":"TPE_Analytics","name":"TPE_Analytics","templateSourceId":"sfdc_internal__Trade_Promotion_Effectiveness","runtimeMethod":"Async"}'
|
|
76
|
+
```
|
|
77
|
+
Extract `app.id`. Never use `Sync` — this is a 400-500 task pipeline (~15-20 minutes).
|
|
78
|
+
3. **Poll** with the same shared poller, a longer window this time:
|
|
79
|
+
```bash
|
|
80
|
+
node ./scripts/poll-status.js --target-org <username> \
|
|
81
|
+
--path "/services/data/v67.0/app-framework/apps/<appId>" \
|
|
82
|
+
--status-field applicationStatus \
|
|
83
|
+
--success-values SuccessStatus,SuccessWithWarningsStatus \
|
|
84
|
+
--failure-values FailedStatus \
|
|
85
|
+
--interval-seconds 60 --max-wait-seconds 1200
|
|
86
|
+
```
|
|
87
|
+
4. `SuccessStatus` / `SuccessWithWarningsStatus` ⇒ record success (note warnings). Timeout while `InProgressStatus` ⇒ inform the user it's still installing — a later run can re-poll; not a failure.
|
|
88
|
+
|
|
89
|
+
`FailedStatus` ⇒ **find the actual reason before stopping.** `applicationStatus: FailedStatus` alone carries no detail — the per-requirement failure message only lives on the install's async execution graph (its Domino runtime), never on the app resource itself. Use the sibling script rather than hand-parsing this (confirmed live: the graph is deeply nested and its own failure-message template is inconsistently formatted):
|
|
90
|
+
```bash
|
|
91
|
+
node ./scripts/find-failure-reason.js --target-org <username> --app-id <appId>
|
|
92
|
+
```
|
|
93
|
+
Prints one line of JSON: `{"failedNodes":[{"node","statusMessage"}],"dmoNotAvailable":[<DMO API names>]}`. `failedNodes` is every failed requirement's human-readable message from the most recent install attempt — log it in full; the underlying message is generic ("...enabled or minimum required set of fields mapped in your org. Please make sure required Datakits are installed and data stream is deployed."), so don't assume every failure is DMO-related.
|
|
94
|
+
|
|
95
|
+
- **`dmoNotAvailable` is non-empty** ⇒ a "DMO not available" failure. Every DMO name it can surface here is one this org's `consumer-goods-tpe-datakit-deploy` Phase 4 DMO-mapping deploy is responsible for (e.g. `ssot__BusinessPeriod__dlm`, `MeasureDefinition_std__dlm`, `ssot__PromotionOffer__dlm`, `ssot__Promotion__dlm`, `ssot__Account__dlm`, `ssot__PromotionAccount__dlm`, `ssot__PromotionTemplate__dlm`, `ssot__ProductCategory__dlm`, `PromotionProductMeasure_std__dlm`, `PromotionOfferProductMeasure_std__dlm`) — treat any other DMO name it lists the same way. Whether a DMO's mapping is actually active in the org's Data Cloud tenant isn't exposed over any documented REST/SOQL API (confirmed live — `ssot/data-model-objects` lists DMO definitions, not per-org mapping/activation state) — ask the user directly, one question per DMO, via `AskUserQuestion` (Yes/No):
|
|
96
|
+
*"The TPE Analytics App install failed because `<DMO>` isn't available in Data Cloud. Please check Setup → Data Cloud → Data Model → find `<DMO>` → its Mapping tab. Is `<DMO>` mapped to a data source object in this org?"*
|
|
97
|
+
- **No (not mapped)** ⇒ something went wrong with the data kit deployment itself, not with this phase. **Stop the entire `consumer-goods-tpe-dashboard-configure` run here** — don't just record this phase as blocked and continue. Point the user back to Phase 7 (`consumer-goods-tpe-datakit-deploy`), specifically its Phase 5 `DataKitDeploymentLog` verification, to find and fix the actual deployment error. Only re-attempt this phase after that comes back clean.
|
|
98
|
+
- **Yes (mapped)** ⇒ a known Data Cloud quirk: deploying `dataSrcDataModelFieldMap` metadata doesn't always fully activate the mapping in the org's Data Cloud tenant until it's opened and saved once in the UI. Ask the user to open that same Mapping tab and click **Save** (no field changes needed) for `<DMO>`'s mapping, confirm it saved without error, then retry this phase from step 2 — POST a new install rather than re-polling the failed `appId`.
|
|
99
|
+
- **`failedNodes` is non-empty but `dmoNotAvailable` is empty** ⇒ a different requirement failed. Relay the exact `statusMessage`(s) to the user and stop this phase — don't guess a remediation for a failure shape this skill hasn't seen before.
|
|
100
|
+
- **`failedNodes` is empty** ⇒ the graph-level detail wasn't available yet (e.g. activities not populated). Fall back to reporting bare `FailedStatus` and point the user to the install's `installationHistoryUrl` / `appHubUrl` (already captured from the create response in step 2) for a manual look in Setup.
|
|
101
|
+
5. Record the app id, name, and final status.
|
|
102
|
+
|
|
103
|
+
## Phase 12 (Step 12) — Customize TPE dashboard KPIs (delegated, optional)
|
|
104
|
+
|
|
105
|
+
Implements Salesforce Help's [Customize Trade Promotion Effectiveness Analytics
|
|
106
|
+
Dashboards](https://help.salesforce.com/s/articleView?id=ind.tpe_tpm_tab_next_dashboard_customize.htm&type=5) —
|
|
107
|
+
specifically **Customization Scenario 1** ("Use Custom KPIs with Standard Dashboards"). Let the user
|
|
108
|
+
know this when asking (step 1 below), so they can cross-reference the doc if they want more detail
|
|
109
|
+
before deciding.
|
|
110
|
+
|
|
111
|
+
1. **Ask before proceeding.** Tell the user plainly: *"The base TPE Analytics app is installed with
|
|
112
|
+
its standard KPIs. Would you like to customize the Promotion/Tactic dashboards with your own KPI
|
|
113
|
+
measure codes now? (For more details see scenario 1 in https://help.salesforce.com/s/articleView?id=ind.tpe_tpm_tab_next_dashboard_customize.htm&type=5)"* Never proceed on anything other than an explicit yes. On no/undecided, skip
|
|
114
|
+
this phase entirely and record `skipped — not requested` — note that `consumer-goods-tpe-dashboard-custom-kpi-configure` can
|
|
115
|
+
still be run standalone later. In dry-run, still ask (it's a plan-only decision); record
|
|
116
|
+
`pending — not run (dry-run)` if yes, `skipped — not requested` if no.
|
|
117
|
+
2. **Prerequisite check** — Phase 11's TPE Analytics app install must have reported
|
|
118
|
+
`SuccessStatus`/`SuccessWithWarningsStatus`; `consumer-goods-tpe-dashboard-custom-kpi-configure` clones that app's Extended
|
|
119
|
+
TPM Analytics SDM and dashboards, so it cannot run against a missing/failed install. If Phase 11
|
|
120
|
+
didn't succeed, skip this phase and note it's blocked on Phase 11, not independently broken.
|
|
121
|
+
3. On a yes, collect `consumer-goods-tpe-dashboard-custom-kpi-configure`'s own inputs now (suffix, per-slot overrides — see
|
|
122
|
+
its `SKILL.md`'s "Inputs to collect first" for the exact shape), then invoke it via the `Skill`
|
|
123
|
+
tool, passing through:
|
|
124
|
+
- TPM admin username + dry-run flag (Inputs).
|
|
125
|
+
- The suffix and per-slot `--overrides` just collected.
|
|
126
|
+
4. Treat its final report (pass/blocked/pending per artifact) as this phase's result and fold it
|
|
127
|
+
into the overall report. This is the only phase in this skill that ever mutates the TPE Analytics
|
|
128
|
+
app's semantic model/dashboards after Phase 11 — `consumer-goods-tpe-dashboard-custom-kpi-configure` never touches the base
|
|
129
|
+
artifacts themselves, only adds suffixed clones alongside them (see its own `SKILL.md`). **This is
|
|
130
|
+
the last phase — once it returns (or is skipped per step 1), proceed straight to this skill's own
|
|
131
|
+
overall Report below; don't stop mid-run waiting on anything further from the sub-skill.**
|
|
132
|
+
|
|
133
|
+
## Shared poller
|
|
134
|
+
|
|
135
|
+
`scripts/poll-status.js` — generic, reused by Phases 6 and 11 (and any future async-install step): calls `restRequest()` from the sibling `scripts/sf-rest.js` on each tick (a direct authenticated HTTP GET, not `sf api request rest` — that command is beta, has no `--json`, and can change or be pulled without notice), reads `--status-field` off the response (handles both a single-object and a first-array-element shape), stops on a success/failure value or timeout, non-zero exit on failure/timeout. See its own header comment for the full flag list. Do not write a second poll loop — extend this one if a new step needs different response shape handling.
|
|
136
|
+
|
|
137
|
+
## Diagnosing an app-framework install failure
|
|
138
|
+
|
|
139
|
+
`scripts/find-failure-reason.js` — used by Phase 11's `FailedStatus` branch (and reusable as-is for Phase 6's, if it ever needs the same depth of detail): `applicationStatus`/`requestStatus` values like `FailedStatus` never carry a reason on the app resource itself. The reason only exists on the install's Domino runtime — walk `GET /app-framework/apps/<appId>/activities` to the latest attempt's `runtimeRequest.id`, then `GET /domino/runtimes/<runtimeRequestId>` to its `definition.nodes`, and collect every node whose `results.validate.taskStatus` starts with `Fail`. Confirmed live against a real failure (an org with a DMO mapping deliberately removed): the readiness-check nodes for each required DMO report `-1` when the DMO can't be queried in Data Cloud, and the paired requirement node's `statusMessage` reads *"Sorry! You don't have [`<dmo>__dlm`] enabled or minimum required set of fields mapped in your org. Please make sure required Datakits are installed and data stream is deployed."* — the script extracts the DMO API name out of that message into `dmoNotAvailable`. Do not hand-parse this graph inline elsewhere; extend this script if a new failure shape needs handling. Note its own failure-message template is inconsistently formatted (confirmed live — one DMO's message is missing its opening `[`) and HTML-entity-encoded; the script normalizes both before matching/printing.
|
|
140
|
+
|
|
141
|
+
## Rules
|
|
142
|
+
|
|
143
|
+
- Never authenticate on the user's behalf — if `org-status` shows disconnected, stop and ask the user to log in themselves.
|
|
144
|
+
- Never run `sf org display --json` directly and read its output — always go through `scripts/sf-rest.js`'s `org-status` subcommands, which strip `accessToken` before printing.
|
|
145
|
+
- Never proceed past the Namespace detection step on a managed-package org below Consumer Goods Cloud version 262.2 — state both the installed and required version numbers and stop.
|
|
146
|
+
- Never proceed past Phase 1 until the user has explicitly confirmed (yes/no) that Hyperforce Processing Service pairing is ready. Phase 1 only ever asks about Hyperforce, never GCP.
|
|
147
|
+
- Never assign a permission set, write a record, or trigger an install without an explicit user go-ahead first.
|
|
148
|
+
- Never attempt to enable or verify Tableau Next via any REST/Tooling/Connect API call, or by querying for the `TableauIncludedAppManager` permission set — there's no reliable programmatic path (the enable APIs are confirmed unavailable live against a test org on `v63.0`, and the permission set isn't a reliable enablement signal); Phase 5 always asks the user directly and defers enablement to the Setup UI.
|
|
149
|
+
- Never hardcode a Sales Org code — always query for one with the required `Tenant_Substrate__c`.
|
|
150
|
+
- Never hardcode the namespace (`cgcloud_dev`, `cgcloud`, or any other string) in a REST path, Apex snippet, or SOQL query — always use the detected `NS`/`NS_SEGMENT`/`NS_APEX`/`NS_FIELD` from the Namespace detection step.
|
|
151
|
+
- Never claim an async install succeeded on anything other than `SuccessStatus`/`SuccessWithWarningsStatus` (apps), a user-confirmed ready `state` (tenant status), or a non-`FAILURE` terminal `lastRunStatus` with a fresh `lastRunDate` (data transform).
|
|
152
|
+
- A poll timeout while still in an in-progress state is **not** a failure — report it as still-running and let a later run re-poll.
|
|
153
|
+
- This skill never deploys Apex. It only calls `global` Apex classes, REST endpoints already shipped in the target org's installed package, or the anonymous-Apex templates in its own `scripts/` directory — never a `public`/`private` method directly, never a class this skill deploys itself (skills are distributed independently of this repo; see the architecture rule on self-contained skills), and never a script or template from outside this skill's own directory.
|
|
154
|
+
|
|
155
|
+
## Report
|
|
156
|
+
|
|
157
|
+
At the end of a run, give the user a structured status per phase (1-12): pass / blocked / pending, with the specific blocking reason and remediation click-path where applicable. Call out anything skipped due to dry-run, a declined confirmation, or (Phase 12) the customer opting out of KPI customization.
|