@contrail/extensions-sdk 1.0.33 → 1.0.35
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/lib/apps/app-context.d.ts +6 -0
- package/lib/apps/document-generation.d.ts +107 -0
- package/lib/apps/document-generation.js +56 -0
- package/lib/apps/index.d.ts +1 -0
- package/lib/apps/index.js +1 -0
- package/lib/apps/plan.d.ts +67 -1
- package/lib/apps/plan.js +39 -0
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { User } from '@contrail/entity-types';
|
|
2
|
+
import { DocumentGenerationIntent } from './document-generation';
|
|
2
3
|
interface Document {
|
|
3
4
|
id: string;
|
|
4
5
|
name: string;
|
|
@@ -122,6 +123,11 @@ export interface BoardContext {
|
|
|
122
123
|
rootWorkspaceId?: string;
|
|
123
124
|
typeId?: string;
|
|
124
125
|
workspaceId?: string;
|
|
126
|
+
/**
|
|
127
|
+
* Why the host opened this extension, when it opened it against generated
|
|
128
|
+
* content. Absent on an ordinary launch from a menu.
|
|
129
|
+
*/
|
|
130
|
+
documentGenerationIntent?: DocumentGenerationIntent;
|
|
125
131
|
}
|
|
126
132
|
export interface ShowcaseContext {
|
|
127
133
|
id: string;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract for an extension that generates board content.
|
|
3
|
+
*
|
|
4
|
+
* Any extension that stamps `documentGenerationConfigId` onto elements is
|
|
5
|
+
* claiming a block of someone's board, and the board offers Refresh, Configure,
|
|
6
|
+
* Select all and Details against that claim. Those only work if the extension
|
|
7
|
+
* holds up its end, which is this file.
|
|
8
|
+
*
|
|
9
|
+
* 1. Write a `document-generation-config` shaped like {@link DocumentGenerationConfigRecord}
|
|
10
|
+
* - `kind: 'app'` at minimum, or the board treats it as the built-in
|
|
11
|
+
* generator's and that editor will fail on the missing fields.
|
|
12
|
+
* 2. Stamp that config's id onto EVERY element you create, and scope every read
|
|
13
|
+
* and delete to elements carrying it. Elements without the stamp are someone
|
|
14
|
+
* else's.
|
|
15
|
+
* 3. Read {@link DocumentGenerationApp.getIntent} on launch and honour the mode.
|
|
16
|
+
* `regenerate` means: same spec, latest data, no questions asked.
|
|
17
|
+
* 4. When `headless` is true, render nothing and wait for nobody. Finish with
|
|
18
|
+
* {@link DocumentGenerationApp.complete} or {@link DocumentGenerationApp.fail} -
|
|
19
|
+
* the host has no other way to tell success from an extension that gave up.
|
|
20
|
+
* 5. Declare `supportsDocumentGeneration` in app.yml, and
|
|
21
|
+
* `supportsHeadlessRegenerate` if you honour rule 4.
|
|
22
|
+
*/
|
|
23
|
+
export declare const GENERATION_COMMAND: {
|
|
24
|
+
readonly progress: "generation:progress";
|
|
25
|
+
readonly complete: "generation:complete";
|
|
26
|
+
readonly failed: "generation:failed";
|
|
27
|
+
};
|
|
28
|
+
export declare type DocumentGenerationMode = 'create' | 'edit' | 'regenerate';
|
|
29
|
+
export interface DocumentGenerationIntent {
|
|
30
|
+
mode: DocumentGenerationMode;
|
|
31
|
+
/** Absent for `create`. */
|
|
32
|
+
documentGenerationConfigId?: string;
|
|
33
|
+
/** True when no UI will be seen and no user is waiting. */
|
|
34
|
+
headless?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/** One row of the board's details panel. The host renders these verbatim. */
|
|
37
|
+
export interface GenerationSummaryRow {
|
|
38
|
+
label: string;
|
|
39
|
+
value: string;
|
|
40
|
+
}
|
|
41
|
+
export interface GenerationSource {
|
|
42
|
+
/** An entity name the platform can read, e.g. `assortment`. */
|
|
43
|
+
kind: string;
|
|
44
|
+
id: string;
|
|
45
|
+
label?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface GenerationReference {
|
|
48
|
+
appIdentifier: string;
|
|
49
|
+
extensionIdentifier: string;
|
|
50
|
+
/**
|
|
51
|
+
* Which of the app's generators made this, when it has more than one.
|
|
52
|
+
* Names the generator, never its output - the same extension may produce
|
|
53
|
+
* frames of cards, a chart or a table.
|
|
54
|
+
*/
|
|
55
|
+
generatorId?: string;
|
|
56
|
+
generatorVersion?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The fields the board reads off a `document-generation-config`. Anything else -
|
|
60
|
+
* `spec` in particular - is the extension's own and the host never interprets it.
|
|
61
|
+
*/
|
|
62
|
+
export interface DocumentGenerationConfigRecord {
|
|
63
|
+
id?: string;
|
|
64
|
+
/** `app` for anything an installed app generated; `native` is the built-in generator. */
|
|
65
|
+
kind: 'app';
|
|
66
|
+
name: string;
|
|
67
|
+
documentId: string;
|
|
68
|
+
generatorRef: GenerationReference;
|
|
69
|
+
/** Drives the source links and the staleness check. */
|
|
70
|
+
sources: GenerationSource[];
|
|
71
|
+
/** Opaque to the host: whatever the layout needs to run again. */
|
|
72
|
+
spec: unknown;
|
|
73
|
+
specVersion: number;
|
|
74
|
+
/** What the details panel shows. Written by the extension; nothing else can. */
|
|
75
|
+
summary?: GenerationSummaryRow[];
|
|
76
|
+
/** What the board may offer against this content. `headlessRefresh` enables Refresh. */
|
|
77
|
+
capabilities?: {
|
|
78
|
+
headlessRefresh?: boolean;
|
|
79
|
+
};
|
|
80
|
+
lastGeneratedOn?: string;
|
|
81
|
+
anchor?: {
|
|
82
|
+
x: number;
|
|
83
|
+
y: number;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export interface GenerationResult {
|
|
87
|
+
created: number;
|
|
88
|
+
updated: number;
|
|
89
|
+
deleted: number;
|
|
90
|
+
unchanged: number;
|
|
91
|
+
/**
|
|
92
|
+
* False when the run replaced its elements wholesale rather than diffing, so
|
|
93
|
+
* the counts describe a rewrite. Defaults to true.
|
|
94
|
+
*/
|
|
95
|
+
isDiff?: boolean;
|
|
96
|
+
}
|
|
97
|
+
export declare class DocumentGenerationApp {
|
|
98
|
+
/** Why the host opened this extension. Absent when it was opened from a menu. */
|
|
99
|
+
static getIntent(): DocumentGenerationIntent | undefined;
|
|
100
|
+
static isHeadless(): boolean;
|
|
101
|
+
/** Optional. Gives the host something to show while a long run proceeds. */
|
|
102
|
+
static progress(message: string): void;
|
|
103
|
+
/** Required to end a headless run that succeeded. */
|
|
104
|
+
static complete(result: GenerationResult): void;
|
|
105
|
+
/** Required to end a headless run that failed. The message reaches the board. */
|
|
106
|
+
static fail(message: string): void;
|
|
107
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DocumentGenerationApp = exports.GENERATION_COMMAND = void 0;
|
|
4
|
+
const actions_1 = require("../actions/actions");
|
|
5
|
+
const app_context_1 = require("./app-context");
|
|
6
|
+
/**
|
|
7
|
+
* The contract for an extension that generates board content.
|
|
8
|
+
*
|
|
9
|
+
* Any extension that stamps `documentGenerationConfigId` onto elements is
|
|
10
|
+
* claiming a block of someone's board, and the board offers Refresh, Configure,
|
|
11
|
+
* Select all and Details against that claim. Those only work if the extension
|
|
12
|
+
* holds up its end, which is this file.
|
|
13
|
+
*
|
|
14
|
+
* 1. Write a `document-generation-config` shaped like {@link DocumentGenerationConfigRecord}
|
|
15
|
+
* - `kind: 'app'` at minimum, or the board treats it as the built-in
|
|
16
|
+
* generator's and that editor will fail on the missing fields.
|
|
17
|
+
* 2. Stamp that config's id onto EVERY element you create, and scope every read
|
|
18
|
+
* and delete to elements carrying it. Elements without the stamp are someone
|
|
19
|
+
* else's.
|
|
20
|
+
* 3. Read {@link DocumentGenerationApp.getIntent} on launch and honour the mode.
|
|
21
|
+
* `regenerate` means: same spec, latest data, no questions asked.
|
|
22
|
+
* 4. When `headless` is true, render nothing and wait for nobody. Finish with
|
|
23
|
+
* {@link DocumentGenerationApp.complete} or {@link DocumentGenerationApp.fail} -
|
|
24
|
+
* the host has no other way to tell success from an extension that gave up.
|
|
25
|
+
* 5. Declare `supportsDocumentGeneration` in app.yml, and
|
|
26
|
+
* `supportsHeadlessRegenerate` if you honour rule 4.
|
|
27
|
+
*/
|
|
28
|
+
exports.GENERATION_COMMAND = {
|
|
29
|
+
progress: 'generation:progress',
|
|
30
|
+
complete: 'generation:complete',
|
|
31
|
+
failed: 'generation:failed',
|
|
32
|
+
};
|
|
33
|
+
class DocumentGenerationApp {
|
|
34
|
+
/** Why the host opened this extension. Absent when it was opened from a menu. */
|
|
35
|
+
static getIntent() {
|
|
36
|
+
var _a, _b, _c;
|
|
37
|
+
return (_c = (_b = (_a = (0, app_context_1.getAppContext)()) === null || _a === void 0 ? void 0 : _a.appContext) === null || _b === void 0 ? void 0 : _b.board) === null || _c === void 0 ? void 0 : _c.documentGenerationIntent;
|
|
38
|
+
}
|
|
39
|
+
static isHeadless() {
|
|
40
|
+
var _a;
|
|
41
|
+
return ((_a = DocumentGenerationApp.getIntent()) === null || _a === void 0 ? void 0 : _a.headless) === true;
|
|
42
|
+
}
|
|
43
|
+
/** Optional. Gives the host something to show while a long run proceeds. */
|
|
44
|
+
static progress(message) {
|
|
45
|
+
(0, actions_1.getExtensionActions)().sendMessageToHost({ command: exports.GENERATION_COMMAND.progress, data: { message } });
|
|
46
|
+
}
|
|
47
|
+
/** Required to end a headless run that succeeded. */
|
|
48
|
+
static complete(result) {
|
|
49
|
+
(0, actions_1.getExtensionActions)().sendMessageToHost({ command: exports.GENERATION_COMMAND.complete, data: result });
|
|
50
|
+
}
|
|
51
|
+
/** Required to end a headless run that failed. The message reaches the board. */
|
|
52
|
+
static fail(message) {
|
|
53
|
+
(0, actions_1.getExtensionActions)().sendMessageToHost({ command: exports.GENERATION_COMMAND.failed, data: { message } });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.DocumentGenerationApp = DocumentGenerationApp;
|
package/lib/apps/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { getAppContext, AppContext, BoardContext, PlanContext, PlanFilterCriterion, PlanSortCriterion, PlanViewState, ShowcaseContext, VibeIQAppType, } from './app-context';
|
|
2
|
+
export * from './document-generation';
|
|
2
3
|
export * from './boards';
|
|
3
4
|
export * from './plan';
|
|
4
5
|
export * from './showcase';
|
package/lib/apps/index.js
CHANGED
|
@@ -18,6 +18,7 @@ exports.VibeIQAppType = exports.getAppContext = void 0;
|
|
|
18
18
|
var app_context_1 = require("./app-context");
|
|
19
19
|
Object.defineProperty(exports, "getAppContext", { enumerable: true, get: function () { return app_context_1.getAppContext; } });
|
|
20
20
|
Object.defineProperty(exports, "VibeIQAppType", { enumerable: true, get: function () { return app_context_1.VibeIQAppType; } });
|
|
21
|
+
__exportStar(require("./document-generation"), exports);
|
|
21
22
|
__exportStar(require("./boards"), exports);
|
|
22
23
|
__exportStar(require("./plan"), exports);
|
|
23
24
|
__exportStar(require("./showcase"), exports);
|
package/lib/apps/plan.d.ts
CHANGED
|
@@ -8,7 +8,8 @@ export declare enum PlanCommand {
|
|
|
8
8
|
GET_PLACEHOLDERS = "plan:get_placeholders",
|
|
9
9
|
GET_VIEW_STATE = "plan:get_view_state",
|
|
10
10
|
ASSIGN_ITEMS = "plan:assign_items",
|
|
11
|
-
SCROLL_TO_PLACEHOLDER = "plan:scroll_to_placeholder"
|
|
11
|
+
SCROLL_TO_PLACEHOLDER = "plan:scroll_to_placeholder",
|
|
12
|
+
UPDATE_PLACEHOLDERS = "plan:update_placeholders"
|
|
12
13
|
}
|
|
13
14
|
/**
|
|
14
15
|
* A row on a plan. Beyond the item linkage below, properties are the org-configured
|
|
@@ -43,6 +44,45 @@ export interface PlaceholderItemAssignment {
|
|
|
43
44
|
/** The item to assign — an item family or an item option. */
|
|
44
45
|
itemId: string;
|
|
45
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Edits to one existing row, keyed by property slug.
|
|
49
|
+
*
|
|
50
|
+
* Only two kinds of property can be written this way:
|
|
51
|
+
*
|
|
52
|
+
* - `isDropped` — `true` drops the row, `false` restores it.
|
|
53
|
+
* - Assortment-item properties (`forecast`, `targetVolume`, ...) — plan-placeholder properties
|
|
54
|
+
* that also exist on the `assortment-item` type and on neither `item` nor `project-item`.
|
|
55
|
+
*
|
|
56
|
+
* Item and project-item properties are refused: edit those entities directly and the plan picks the
|
|
57
|
+
* change up through propagation.
|
|
58
|
+
*
|
|
59
|
+
* Values take the shape {@link PlanApp.getPlaceholders} returns. An object-reference or user-list
|
|
60
|
+
* property takes the referenced object (with its `id`) or `null`.
|
|
61
|
+
*/
|
|
62
|
+
export interface PlaceholderChange {
|
|
63
|
+
/** The row to edit. */
|
|
64
|
+
id: string;
|
|
65
|
+
changes: {
|
|
66
|
+
[propertySlug: string]: any;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export interface UpdatePlaceholdersOptions {
|
|
70
|
+
/** Skip the undo/redo entry. Intended for bulk programmatic writes. */
|
|
71
|
+
skipRecordingUndoRedo?: boolean;
|
|
72
|
+
}
|
|
73
|
+
/** One property edit the plan declined, and why. The row's other edits are still applied. */
|
|
74
|
+
export interface RejectedPlaceholderChange {
|
|
75
|
+
placeholderId: string;
|
|
76
|
+
propertySlug: string;
|
|
77
|
+
/** Human-readable, e.g. `This value is calculated with a formula.` */
|
|
78
|
+
reason: string;
|
|
79
|
+
}
|
|
80
|
+
export interface UpdatePlaceholdersResult {
|
|
81
|
+
/** The edited rows as they are after the write, formulas re-evaluated. */
|
|
82
|
+
placeholders: Array<PlanPlaceholder>;
|
|
83
|
+
/** Edits refused by the row's own rules — permissions, rule sets, value validation, drop checks. */
|
|
84
|
+
rejectedChanges: Array<RejectedPlaceholderChange>;
|
|
85
|
+
}
|
|
46
86
|
export interface ScrollToPlaceholderOptions {
|
|
47
87
|
/**
|
|
48
88
|
* Also bring this column into view horizontally.
|
|
@@ -155,6 +195,32 @@ export declare class PlanApp {
|
|
|
155
195
|
* Requires the plan to be editable.
|
|
156
196
|
*/
|
|
157
197
|
static assignItems(assignments: Array<PlaceholderItemAssignment>): Promise<void>;
|
|
198
|
+
/**
|
|
199
|
+
* Edits existing rows: drops or restores them, and sets assortment-item properties such as
|
|
200
|
+
* forecasts. See {@link PlaceholderChange} for what can be written.
|
|
201
|
+
*
|
|
202
|
+
* Runs the same write path as a grid edit, so formulas re-evaluate, the change is undoable, it is
|
|
203
|
+
* persisted, and it is broadcast to other users. Resolves once the write has been applied, with
|
|
204
|
+
* the updated rows.
|
|
205
|
+
*
|
|
206
|
+
* Two kinds of failure are reported differently:
|
|
207
|
+
*
|
|
208
|
+
* - **Throws, and writes nothing,** when the request is wrong: a row id not on this plan, or a
|
|
209
|
+
* property that cannot be written through this method. These are bugs in the caller.
|
|
210
|
+
* - **Rejects individual edits,** listed in `rejectedChanges`, when a row's own rules refuse them:
|
|
211
|
+
* the property is read-only, a formula, or locked by a rule set; the value fails validation; or
|
|
212
|
+
* the row cannot be dropped. Every other edit is applied, as a paste into the grid would be.
|
|
213
|
+
*
|
|
214
|
+
* ```ts
|
|
215
|
+
* const { rejectedChanges } = await PlanApp.updatePlaceholders([
|
|
216
|
+
* { id: rowA, changes: { isDropped: true } },
|
|
217
|
+
* { id: rowB, changes: { forecast: 1200 } },
|
|
218
|
+
* ]);
|
|
219
|
+
* ```
|
|
220
|
+
*
|
|
221
|
+
* Requires the plan to be editable.
|
|
222
|
+
*/
|
|
223
|
+
static updatePlaceholders(placeholderChanges: Array<PlaceholderChange>, options?: UpdatePlaceholdersOptions): Promise<UpdatePlaceholdersResult>;
|
|
158
224
|
/**
|
|
159
225
|
* Scrolls the plan's grid so a row is in view.
|
|
160
226
|
*
|
package/lib/apps/plan.js
CHANGED
|
@@ -23,6 +23,7 @@ var PlanCommand;
|
|
|
23
23
|
PlanCommand["GET_VIEW_STATE"] = "plan:get_view_state";
|
|
24
24
|
PlanCommand["ASSIGN_ITEMS"] = "plan:assign_items";
|
|
25
25
|
PlanCommand["SCROLL_TO_PLACEHOLDER"] = "plan:scroll_to_placeholder";
|
|
26
|
+
PlanCommand["UPDATE_PLACEHOLDERS"] = "plan:update_placeholders";
|
|
26
27
|
})(PlanCommand = exports.PlanCommand || (exports.PlanCommand = {}));
|
|
27
28
|
class PlanApp {
|
|
28
29
|
static getCurrentPlan() {
|
|
@@ -172,6 +173,44 @@ class PlanApp {
|
|
|
172
173
|
PlanApp.validateAndReturnResults(results);
|
|
173
174
|
});
|
|
174
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Edits existing rows: drops or restores them, and sets assortment-item properties such as
|
|
178
|
+
* forecasts. See {@link PlaceholderChange} for what can be written.
|
|
179
|
+
*
|
|
180
|
+
* Runs the same write path as a grid edit, so formulas re-evaluate, the change is undoable, it is
|
|
181
|
+
* persisted, and it is broadcast to other users. Resolves once the write has been applied, with
|
|
182
|
+
* the updated rows.
|
|
183
|
+
*
|
|
184
|
+
* Two kinds of failure are reported differently:
|
|
185
|
+
*
|
|
186
|
+
* - **Throws, and writes nothing,** when the request is wrong: a row id not on this plan, or a
|
|
187
|
+
* property that cannot be written through this method. These are bugs in the caller.
|
|
188
|
+
* - **Rejects individual edits,** listed in `rejectedChanges`, when a row's own rules refuse them:
|
|
189
|
+
* the property is read-only, a formula, or locked by a rule set; the value fails validation; or
|
|
190
|
+
* the row cannot be dropped. Every other edit is applied, as a paste into the grid would be.
|
|
191
|
+
*
|
|
192
|
+
* ```ts
|
|
193
|
+
* const { rejectedChanges } = await PlanApp.updatePlaceholders([
|
|
194
|
+
* { id: rowA, changes: { isDropped: true } },
|
|
195
|
+
* { id: rowB, changes: { forecast: 1200 } },
|
|
196
|
+
* ]);
|
|
197
|
+
* ```
|
|
198
|
+
*
|
|
199
|
+
* Requires the plan to be editable.
|
|
200
|
+
*/
|
|
201
|
+
static updatePlaceholders(placeholderChanges, options) {
|
|
202
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
203
|
+
PlanApp.validatePlanContext();
|
|
204
|
+
if (!(placeholderChanges === null || placeholderChanges === void 0 ? void 0 : placeholderChanges.length)) {
|
|
205
|
+
return { placeholders: [], rejectedChanges: [] };
|
|
206
|
+
}
|
|
207
|
+
const results = yield (0, actions_1.getExtensionActions)().sendMessageToHost({
|
|
208
|
+
command: PlanCommand.UPDATE_PLACEHOLDERS,
|
|
209
|
+
data: { placeholderChanges, options },
|
|
210
|
+
});
|
|
211
|
+
return PlanApp.validateAndReturnResults(results);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
175
214
|
/**
|
|
176
215
|
* Scrolls the plan's grid so a row is in view.
|
|
177
216
|
*
|
package/package.json
CHANGED