Skip to content

Commit d94a343

Browse files
authored
Viera/fix app update (#90)
* fix: address app update bugs
1 parent 6397ded commit d94a343

File tree

4 files changed

+79
-86
lines changed

4 files changed

+79
-86
lines changed

command-snapshot.json

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,8 @@
4747
"alias": [],
4848
"command": "orchestrator:app:update",
4949
"flagAliases": [],
50-
"flagChars": ["d", "g", "i", "l", "m", "n", "o", "r", "t"],
51-
"flags": [
52-
"api-version",
53-
"app-id",
54-
"app-name",
55-
"description",
56-
"flags-dir",
57-
"json",
58-
"label",
59-
"log-level",
60-
"runtime-method",
61-
"target-org",
62-
"template-id",
63-
"template-name"
64-
],
50+
"flagChars": ["d", "i", "l", "n", "o"],
51+
"flags": ["api-version", "app-id", "app-name", "description", "flags-dir", "json", "label", "target-org"],
6552
"plugin": "@salesforce/plugin-orchestrator"
6653
},
6754
{

messages/appframework.update.app.md

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# summary
22

3-
Update an app with a new template.
3+
Update an app's label and description.
44

55
# description
66

7-
Update an existing orchestrated app by associating it with a new template or modifying its properties. This command allows you to change the template that an app is based on, update its metadata (label, description), or modify runtime configuration settings.
7+
Update an existing orchestrated app's label and description. This command allows you to modify the display properties of an app without changing its underlying template or functionality.
88

9-
You can identify the app by either its unique ID or its name, and specify the new template by either its unique ID or its name. App and template IDs are guaranteed to be unique, while names should be unique within an org.
9+
You can identify the app by either its unique ID or its name. App IDs are guaranteed to be unique, while names should be unique within an org.
1010

11-
Updating an app with a new template changes the app's underlying structure and configuration to match the new template. This is useful for migrating apps to updated templates or changing an app's functionality. You can also update just the app's metadata without changing the template.
11+
Use this command to update the human-readable display name (label) and description that users see when viewing the app. This is useful for improving app discoverability and providing clearer information about the app's purpose.
1212

13-
You must have Data Cloud and Tableau Next enabled in your org and the AppFrameworkManageApp user permission to modify apps. Both the app and template must exist in the same org.
13+
You must have the AppFrameworkManageApp user permission to modify apps.
1414

1515
# flags.target-org.summary
1616

@@ -100,6 +100,10 @@ No app specified for update. You must specify either an app ID with --app-id or
100100

101101
No template specified for update. You must specify either a template ID with --template-id or a template name with --template-name.
102102

103+
# noUpdateFieldsSpecified
104+
105+
No update fields specified. You must specify at least one field to update such as --label or --description.
106+
103107
# fetchingApp
104108

105109
Fetching app details...
@@ -193,22 +197,18 @@ You don't have permission to update apps in this org.
193197

194198
# examples
195199

196-
- Update an app to use a new template by ID:
197-
198-
<%= config.bin %> <%= command.id %> --target-org myOrg --app-id 01t000000000123 --template-id 01t000000000456
199-
200-
- Update an app to use a new template by name:
200+
- Update an app's label:
201201

202-
<%= config.bin %> <%= command.id %> --target-org myOrg --app-name "My App" --template-name "New Template"
202+
<%= config.bin %> <%= command.id %> --target-org myOrg --app-id 1zAxx000000000123 --label "Updated App Label"
203203

204-
- Update an app with a new label and description:
204+
- Update an app's description:
205205

206-
<%= config.bin %> <%= command.id %> --target-org myOrg --app-id 01t000000000123 --label "Updated App" --description "Updated description for the app"
206+
<%= config.bin %> <%= command.id %> --target-org myOrg --app-name "My App" --description "Updated description for the app"
207207

208-
- Update an app with new template and runtime configuration:
208+
- Update both label and description:
209209

210-
<%= config.bin %> <%= command.id %> --target-org myOrg --app-name "analytics_app" --template-name "Analytics Template" --runtime-method async --log-level debug
210+
<%= config.bin %> <%= command.id %> --target-org myOrg --app-id 1zAxx000000000123 --label "Updated App" --description "Updated description for the app"
211211

212212
- Update an app using a specific API version:
213213

214-
<%= config.bin %> <%= command.id %> --target-org mySandbox --app-id 01t000000000123 --template-id 01t000000000789 --api-version 60.0
214+
<%= config.bin %> <%= command.id %> --target-org mySandbox --app-id 1zAxx000000000123 --label "New Label" --api-version 65.0

src/commands/orchestrator/app/update.ts

Lines changed: 4 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -50,18 +50,6 @@ export default class UpdateApp extends SfCommand<string> {
5050
description: messages.getMessage('flags.app-name.description'),
5151
exclusive: ['app-id'],
5252
}),
53-
'template-id': Flags.string({
54-
char: 't',
55-
summary: messages.getMessage('flags.template-id.summary'),
56-
description: messages.getMessage('flags.template-id.description'),
57-
exclusive: ['template-name'],
58-
}),
59-
'template-name': Flags.string({
60-
char: 'm',
61-
summary: messages.getMessage('flags.template-name.summary'),
62-
description: messages.getMessage('flags.template-name.description'),
63-
exclusive: ['template-id'],
64-
}),
6553
label: Flags.string({
6654
char: 'l',
6755
summary: messages.getMessage('flags.label.summary'),
@@ -72,18 +60,6 @@ export default class UpdateApp extends SfCommand<string> {
7260
summary: messages.getMessage('flags.description.summary'),
7361
description: messages.getMessage('flags.description.description'),
7462
}),
75-
'runtime-method': Flags.string({
76-
char: 'r',
77-
summary: messages.getMessage('flags.runtime-method.summary'),
78-
description: messages.getMessage('flags.runtime-method.description'),
79-
options: ['sync', 'async'],
80-
}),
81-
'log-level': Flags.string({
82-
char: 'g',
83-
summary: messages.getMessage('flags.log-level.summary'),
84-
description: messages.getMessage('flags.log-level.description'),
85-
options: ['debug', 'info', 'warn', 'error'],
86-
}),
8763
};
8864

8965
public async run(): Promise<string> {
@@ -98,10 +74,10 @@ export default class UpdateApp extends SfCommand<string> {
9874
);
9975
}
10076

101-
// Check that at least one of template-id or template-name is provided
102-
if (!flags['template-id'] && !flags['template-name']) {
77+
// Check that at least one update field is provided
78+
if (!flags.label && !flags.description) {
10379
throw new SfError(
104-
messages.getMessage('noTemplateSpecified'),
80+
messages.getMessage('noUpdateFieldsSpecified'),
10581
'AppUpdateError',
10682
messages.getMessages('error.UpdateError.Actions')
10783
);
@@ -136,34 +112,12 @@ export default class UpdateApp extends SfCommand<string> {
136112
appId = flags['app-id'] ?? '';
137113
}
138114

139-
let templateId = '';
140-
141-
// Get template ID if template name was provided
142-
if (flags['template-name']) {
143-
this.spinner.start(messages.getMessage('fetchingTemplate'));
144-
145-
// Since we don't have a direct way to list templates,
146-
// we need to get the template by name from a different utility class
147-
// For now, we'll throw an error and suggest using template-id instead
148-
this.spinner.stop();
149-
throw new SfError(
150-
messages.getMessage('noTemplateFound'),
151-
'AppUpdateError',
152-
messages.getMessages('error.UpdateError.Actions')
153-
);
154-
} else {
155-
templateId = flags['template-id'] ?? '';
156-
}
157-
158115
// Update the app
159116
this.spinner.start(messages.getMessage('updatingApp'));
160117

161-
const updatedAppId = await appFrameworkApp.updateApp(appId, {
162-
templateSourceId: templateId,
118+
const updatedAppId = await appFrameworkApp.patchApp(appId, {
163119
label: flags.label,
164120
description: flags.description,
165-
runtimeMethod: flags['runtime-method'],
166-
logLevel: flags['log-level'],
167121
});
168122

169123
this.spinner.stop();

src/utils/app/appframeworkapp.ts

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,10 @@ export default class AppFrameworkApp {
204204
appId: string,
205205
options: {
206206
templateSourceId: string;
207-
label?: string;
208-
description?: string;
209207
templateValues?: Record<string, unknown>;
210208
runtimeMethod?: string;
211209
logLevel?: string;
210+
chainName?: string;
212211
}
213212
): Promise<string> {
214213
if (!appId) {
@@ -224,11 +223,10 @@ export default class AppFrameworkApp {
224223
const body = Object.fromEntries(
225224
Object.entries({
226225
templateSourceId: options.templateSourceId,
227-
label: options.label,
228-
description: options.description,
229226
templateValues: options.templateValues ?? {},
230227
runtimeMethod: options.runtimeMethod,
231228
logLevel: options.logLevel,
229+
chainName: options.chainName,
232230
}).filter((entries) => entries[1] !== undefined)
233231
);
234232

@@ -239,7 +237,61 @@ export default class AppFrameworkApp {
239237
};
240238

241239
const response = await request<AppResponse>(this.connection, {
242-
method: 'POST',
240+
method: 'PUT',
241+
url,
242+
body,
243+
});
244+
245+
if (response && typeof response === 'object') {
246+
if ('id' in response && typeof response.id === 'string') {
247+
return response.id;
248+
} else if ('app' in response && response.app && typeof response.app === 'object' && 'id' in response.app) {
249+
return response.app.id;
250+
}
251+
}
252+
253+
return appId; // Return the original app ID if no specific ID was found in the response
254+
}
255+
256+
/**
257+
* Patch an existing app's metadata (label, description)
258+
*
259+
* @param appId - ID of the app to patch
260+
* @param options - Patch options
261+
* @returns The ID of the patched app
262+
*/
263+
public async patchApp(
264+
appId: string,
265+
options: {
266+
label?: string;
267+
description?: string;
268+
}
269+
): Promise<string> {
270+
if (!appId) {
271+
throw new Error('App ID must be provided');
272+
}
273+
274+
if (!options.label && !options.description) {
275+
throw new Error('At least one field (label or description) must be provided for patch');
276+
}
277+
278+
const url = `/apps/${appId}`;
279+
280+
const body = Object.fromEntries(
281+
Object.entries({
282+
label: options.label,
283+
description: options.description,
284+
}).filter((entries) => entries[1] !== undefined)
285+
);
286+
287+
// Define response types
288+
type AppResponse = {
289+
id?: string;
290+
app?: { id: string };
291+
};
292+
293+
const response = await request<AppResponse>(this.connection, {
294+
method: 'PATCH',
243295
url,
244296
body,
245297
});

0 commit comments

Comments
 (0)