diff --git a/command-snapshot.json b/command-snapshot.json index 68798ca..b3d9eca 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -10,6 +10,7 @@ "flags": [ "allevents", "apiversion", + "appdescription", "appname", "async", "definitionfile", @@ -81,6 +82,8 @@ "plugin": "@salesforce/analytics", "flags": [ "apiversion", + "appdescription", + "appname", "async", "json", "loglevel", @@ -238,7 +241,7 @@ { "command": "analytics:template:create", "plugin": "@salesforce/analytics", - "flags": ["apiversion", "description", "folderid", "json", "label", "loglevel", "targetusername", "recipeids"] + "flags": ["apiversion", "description", "folderid", "json", "label", "loglevel", "recipeids", "targetusername"] }, { "command": "analytics:template:delete", @@ -250,6 +253,11 @@ "plugin": "@salesforce/analytics", "flags": ["apiversion", "json", "loglevel", "targetusername", "templateid", "templatename"] }, + { + "command": "analytics:template:lint", + "plugin": "@salesforce/analytics", + "flags": ["apiversion", "json", "loglevel", "targetusername", "templateid", "templatename"] + }, { "command": "analytics:template:list", "plugin": "@salesforce/analytics", @@ -271,20 +279,15 @@ "folderid", "json", "loglevel", + "recipeids", "targetusername", "templateid", - "templatename", - "recipeids" + "templatename" ] }, - { - "command": "analytics:template:lint", - "plugin": "@salesforce/analytics", - "flags": ["loglevel", "targetusername", "apiversion", "json", "templateid", "templatename"] - }, { "command": "analytics:template:validate", "plugin": "@salesforce/analytics", - "flags": ["loglevel", "targetusername", "apiversion", "json", "templateid", "templatename", "valuesfile"] + "flags": ["apiversion", "json", "loglevel", "targetusername", "templateid", "templatename", "valuesfile"] } ] diff --git a/messages/app.json b/messages/app.json index 4d8c01f..656716c 100644 --- a/messages/app.json +++ b/messages/app.json @@ -21,6 +21,8 @@ "templatenameFlagLongDescription": "The name of the Analytics template.", "appnameFlagDescription": "app name", "appnameFlagLongDescription": "The name of the created app.", + "appdescriptionFlagDescription": "app description", + "appdescriptionFlagLongDescription": "The description of the created app.", "applogFlagDescription": "specify to include app creation log details", "applogFlagLongDescription": "Specify to include app creation log details.", "appCreateAsyncDescription": "create app asynchronously", diff --git a/messages/autoinstall.json b/messages/autoinstall.json index 124ff7a..d3304c7 100644 --- a/messages/autoinstall.json +++ b/messages/autoinstall.json @@ -26,6 +26,10 @@ "autoinstallidFlagLongDescription": "ID of the auto-install request.", "applogFlagDescription": "specify to include app creation log details", "applogFlagLongDescription": "Specify to include app creation log details.", + "appnameFlagDescription": "app name", + "appnameFlagLongDescription": "The name of the created app.", + "appdescriptionFlagDescription": "app description", + "appdescriptionFlagLongDescription": "The description of the created app.", "displayLogHeader": "App Log", "displayNoLogAvailable": "No app log available.", "displayDateFormat": "YYYY-MM-DD HH:mm:ss", diff --git a/src/commands/analytics/app/create.ts b/src/commands/analytics/app/create.ts index 61b7833..47d5928 100644 --- a/src/commands/analytics/app/create.ts +++ b/src/commands/analytics/app/create.ts @@ -51,6 +51,10 @@ export default class Create extends SfdxCommand { description: messages.getMessage('appnameFlagDescription'), longDescription: messages.getMessage('appnameFlagLongDescription') }), + appdescription: flags.string({ + description: messages.getMessage('appdescriptionFlagDescription'), + longDescription: messages.getMessage('appdescriptionFlagLongDescription') + }), async: flags.boolean({ char: 'a', description: messages.getMessage('appCreateAsyncDescription'), @@ -108,7 +112,7 @@ export default class Create extends SfdxCommand { throw new SfdxError(`Template '${this.flags.templateid || this.flags.templatename}' not found.`); } return { - description: matchedTemplate.description, + description: (this.flags.appdescription || matchedTemplate.description) as string, label: (this.flags.appname || matchedTemplate.label) as string, templateSourceId: matchedTemplate.id, assetIcon: '16.png', @@ -138,6 +142,7 @@ export default class Create extends SfdxCommand { body.name = (body.name || this.flags.appname) as string; body.label = (body.label || this.flags.appname) as string; } + body.description = (body.description ?? this.flags.appdescription) as string; return body; } else { throw new SfdxError( diff --git a/src/commands/analytics/autoinstall/app/create.ts b/src/commands/analytics/autoinstall/app/create.ts index e001d24..220ff52 100644 --- a/src/commands/analytics/autoinstall/app/create.ts +++ b/src/commands/analytics/autoinstall/app/create.ts @@ -7,7 +7,7 @@ import { flags, SfdxCommand } from '@salesforce/command'; import { Messages, Org, SfdxError } from '@salesforce/core'; -import AutoInstall from '../../../../lib/analytics/autoinstall/autoinstall'; +import AutoInstall, { AutoInstallCreateAppConfigurationBody } from '../../../../lib/analytics/autoinstall/autoinstall'; import { DEF_APP_CREATE_UPDATE_TIMEOUT, DEF_POLLING_INTERVAL, @@ -40,6 +40,14 @@ export default class Create extends SfdxCommand { longDescription: messages.getMessage('templatenameFlagLongDescription'), exclusive: ['templateid'] }), + appname: flags.string({ + description: messages.getMessage('appnameFlagDescription'), + longDescription: messages.getMessage('appnameFlagLongDescription') + }), + appdescription: flags.string({ + description: messages.getMessage('appdescriptionFlagDescription'), + longDescription: messages.getMessage('appdescriptionFlagLongDescription') + }), noenqueue: flags.boolean({ description: messages.getMessage('noenqueueFlagDescription'), longDescription: messages.getMessage('noenqueueFlagLongDescription'), @@ -75,8 +83,17 @@ export default class Create extends SfdxCommand { if (!templateInput) { throw new SfdxError(messages.getMessage('missingRequiredField')); } + const autoinstall = new AutoInstall(this.org as Org); - const autoInstallId = await autoinstall.create(templateInput, !this.flags.noenqueue); + + const appConfiguration: AutoInstallCreateAppConfigurationBody = { + appName: this.flags.appname as string, + appLabel: this.flags.appname as string, + appDescription: this.flags.appdescription as string + }; + + const autoInstallId = await autoinstall.create(templateInput, appConfiguration, !this.flags.noenqueue); + // they did't enqueue or said they don't want to wait, so just return now if (this.flags.noenqueue || this.flags.async || this.flags.wait <= 0) { this.ux.log(messages.getMessage('appCreateRequestSuccess', [autoInstallId])); diff --git a/src/lib/analytics/autoinstall/autoinstall.ts b/src/lib/analytics/autoinstall/autoinstall.ts index f24c469..59655d2 100644 --- a/src/lib/analytics/autoinstall/autoinstall.ts +++ b/src/lib/analytics/autoinstall/autoinstall.ts @@ -49,6 +49,12 @@ export type AutoInstallRequestType = Record & { }; }; +export type AutoInstallCreateAppConfigurationBody = { + appName?: string; + appLabel?: string; + appDescription?: string; +}; + function isFinishedRequest(r: AutoInstallRequestType): boolean { const status = r.requestStatus?.toLocaleLowerCase(); return status === 'cancelled' || status === 'success' || status === 'failed'; @@ -126,11 +132,18 @@ export default class AutoInstall { return fetchAllPages(this.connection, this.autoInstallUrl + '?pageSize=200', 'requests'); } - public create(templateApiName: string, enqueue = true): Promise { + public create( + templateApiName: string, + appConfiguration: AutoInstallCreateAppConfigurationBody, + enqueue = true + ): Promise { const body = JSON.stringify({ templateApiName, requestStatus: enqueue ? enqueuedStatus : newStatus, - requestType: createType + requestType: createType, + configuration: { + appConfiguration + } }); return this.performPost(body); } diff --git a/test/commands/app/create.test.ts b/test/commands/app/create.test.ts index aaefe3a..a2c76a8 100644 --- a/test/commands/app/create.test.ts +++ b/test/commands/app/create.test.ts @@ -179,6 +179,49 @@ describe('analytics:app:create', () => { }); }); + test + .withOrg({ username: 'test@org.com' }, true) + .withConnectionRequest(request => { + request = ensureJsonMap(request); + if (request.method === 'GET') { + // call for all templates + return Promise.resolve({ + templates: [testTemplateJson, sustainabilityTemplateJson] + }); + } + if (request.method === 'POST') { + saveOffRequestBody(request.body as string); + return Promise.resolve({ id: appId }); + } + return Promise.reject(); + }) + .stdout() + .command([ + 'analytics:app:create', + '--templatename', + 'Sustainability', + '--appname', + 'customname', + '--appdescription', + 'customdesc', + '--async' + ]) + .it( + 'runs analytics:app:create --templatename Sustainability --appname customname --appdescription customdesc --async', + ctx => { + expect(ctx.stdout).to.contain(messages.getMessage('createAppSuccessAsync', [appId])); + expect(requestBody, 'requestBody').to.not.be.undefined; + expect(requestBody, 'requestBody').to.include({ + // request body should have values from the templates GET + templateSourceId: sustainabilityTemplateJson.id, + // but name and label should come from the cli arg + label: 'customname', + name: 'customname', + description: 'customdesc' + }); + } + ); + test .withOrg({ username: 'test@org.com' }, true) .withConnectionRequest(request => { diff --git a/test/commands/autoinstall/app/create.test.ts b/test/commands/autoinstall/app/create.test.ts index ee82942..ec04c34 100644 --- a/test/commands/autoinstall/app/create.test.ts +++ b/test/commands/autoinstall/app/create.test.ts @@ -9,7 +9,7 @@ import { UX } from '@salesforce/command'; import * as core from '@salesforce/core'; import { expect, test } from '@salesforce/command/lib/test'; import { SfdxError } from '@salesforce/core'; -import { JsonMap } from '@salesforce/ts-types'; +import { AnyJson, ensureJsonMap, ensureString, JsonMap } from '@salesforce/ts-types'; import { AutoInstallRequestType, AutoInstallStatus } from '../../../../src/lib/analytics/autoinstall/autoinstall'; core.Messages.importMessagesDirectory(__dirname); @@ -32,10 +32,21 @@ function stubTimeout(callback?: () => unknown) { } describe('analytics:autoinstall:app:create', () => { + let requestBody: AnyJson | undefined; + function saveOffRequestBody(json: string | undefined) { + requestBody = undefined; + try { + requestBody = json && (JSON.parse(json) as AnyJson); + } catch (e) { + expect.fail('Error parsing request body: ' + (e instanceof Error ? e.message : String(e))); + } + } + // use this so we can have return different values from withConnectionRequest() to simulate polling let requestNum: number; beforeEach(() => { requestNum = 0; + requestBody = undefined; }); test @@ -261,4 +272,32 @@ describe('analytics:autoinstall:app:create', () => { .it('runs analytics:autoinstall:app:create with error during polling', ctx => { expect(ctx.stderr).to.contain('expected error in polling'); }); + + // Test that --appname and --appdescription values appear in the request body + test + .withOrg({ username: 'test@org.com' }, true) + .withConnectionRequest(async request => { + request = ensureJsonMap(request); + saveOffRequestBody(ensureString(request.body)); + return requestWithStatus('New'); + }) + .stdout() + .command([ + 'analytics:autoinstall:app:create', + '--async', + '-n', + 'abc', + '--appname', + 'customname', + '--appdescription', + 'customdesc' + ]) + .it('runs analytics:autoinstall:app:create --async --appname customname --appdescription customdesc', ctx => { + expect(ctx.stdout).to.contain(messages.getMessage('appCreateRequestSuccess', ['0UZxx0000004FzkGAE'])); + expect(requestBody, 'requestBody').to.have.nested.include({ + 'configuration.appConfiguration.appLabel': 'customname', + 'configuration.appConfiguration.appName': 'customname', + 'configuration.appConfiguration.appDescription': 'customdesc' + }); + }); });