@jumoo/processing 18.2.0 → 19.0.0-beta01
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/dist/api/index.d.ts +2 -2
- package/dist/api/index.js +2 -2
- package/dist/api/sdk.gen.d.ts +4 -1
- package/dist/api/sdk.gen.js +19 -0
- package/dist/api/types.gen.d.ts +332 -1
- package/dist/api/types.gen.js +18 -0
- package/dist/lang/files/en.d.ts +26 -0
- package/dist/lang/files/en.js +26 -0
- package/dist/pipeline/components/background.element.js +1 -0
- package/dist/pipeline/components/pipeline-list.element.js +1 -0
- package/dist/pipeline/components/pipeline-table.element.js +23 -23
- package/dist/pipeline/index.d.ts +1 -0
- package/dist/pipeline/index.js +1 -0
- package/dist/pipeline/modal/errors/error-modal.element.js +3 -1
- package/dist/pipeline/pipeline-conflict.d.ts +31 -0
- package/dist/pipeline/pipeline-conflict.js +42 -0
- package/dist/pipeline/pipeline.context.d.ts +6 -1
- package/dist/pipeline/pipeline.context.js +94 -11
- package/dist/pipeline/pipeline.element.d.ts +11 -0
- package/dist/pipeline/pipeline.element.js +131 -1
- package/dist/pipeline/pipeline.repository.d.ts +41 -0
- package/dist/pipeline/pipeline.repository.js +22 -0
- package/dist/pipeline/pipeline.source.d.ts +24 -1
- package/dist/pipeline/pipeline.source.js +24 -5
- package/dist/signalr/signalr.context.d.ts +9 -0
- package/dist/signalr/signalr.context.js +7 -4
- package/package.json +59 -52
- package/dist/hey-api.d.ts +0 -11
- package/dist/hey-api.js +0 -14
|
@@ -60,7 +60,7 @@ let JumooPipelinesTableElement = class JumooPipelinesTableElement extends UmbLit
|
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
62
|
columnAlias: 'background',
|
|
63
|
-
value: html `<umb-icon
|
|
63
|
+
value: html `<umb-icon
|
|
64
64
|
.name=${when((pipeline.state.backgroundState?.isBackgroundRequest ?? false) == true, () => 'icon-check', () => 'icon-close')}></umb-icon>`,
|
|
65
65
|
},
|
|
66
66
|
{
|
|
@@ -77,35 +77,35 @@ let JumooPipelinesTableElement = class JumooPipelinesTableElement extends UmbLit
|
|
|
77
77
|
};
|
|
78
78
|
})
|
|
79
79
|
.filter((item) => item !== undefined);
|
|
80
|
-
return html `
|
|
81
|
-
<umb-table
|
|
82
|
-
.config=${this._tableConfig}
|
|
83
|
-
.columns=${this.tableColumns}
|
|
84
|
-
.items=${items}
|
|
85
|
-
.filter=${this.filter}></umb-table>
|
|
80
|
+
return html `
|
|
81
|
+
<umb-table
|
|
82
|
+
.config=${this._tableConfig}
|
|
83
|
+
.columns=${this.tableColumns}
|
|
84
|
+
.items=${items}
|
|
85
|
+
.filter=${this.filter}></umb-table>
|
|
86
86
|
`;
|
|
87
87
|
}
|
|
88
88
|
renderActions(pipeline) {
|
|
89
89
|
if (!pipeline.id)
|
|
90
90
|
return nothing;
|
|
91
|
-
return html `<uui-button-group
|
|
92
|
-
><uui-button
|
|
93
|
-
.id=${pipeline.id}
|
|
94
|
-
label="View"
|
|
95
|
-
color="positive"
|
|
96
|
-
look="outline"
|
|
97
|
-
@click=${__classPrivateFieldGet(this, _JumooPipelinesTableElement_instances, "m", _JumooPipelinesTableElement_onViewPipeline)}
|
|
98
|
-
>View</uui-button
|
|
99
|
-
>
|
|
91
|
+
return html `<uui-button-group
|
|
92
|
+
><uui-button
|
|
93
|
+
.id=${pipeline.id}
|
|
94
|
+
label="View"
|
|
95
|
+
color="positive"
|
|
96
|
+
look="outline"
|
|
97
|
+
@click=${__classPrivateFieldGet(this, _JumooPipelinesTableElement_instances, "m", _JumooPipelinesTableElement_onViewPipeline)}
|
|
98
|
+
>View</uui-button
|
|
99
|
+
>
|
|
100
100
|
${this.hideDelete
|
|
101
101
|
? ''
|
|
102
|
-
: html `<uui-button
|
|
103
|
-
label="Delete"
|
|
104
|
-
color="danger"
|
|
105
|
-
look="outline"
|
|
106
|
-
@click=${() => __classPrivateFieldGet(this, _JumooPipelinesTableElement_instances, "m", _JumooPipelinesTableElement_onDeletePipeline).call(this, pipeline.id)}
|
|
107
|
-
>Delete</uui-button
|
|
108
|
-
>`}</uui-button-group
|
|
102
|
+
: html `<uui-button
|
|
103
|
+
label="Delete"
|
|
104
|
+
color="danger"
|
|
105
|
+
look="outline"
|
|
106
|
+
@click=${() => __classPrivateFieldGet(this, _JumooPipelinesTableElement_instances, "m", _JumooPipelinesTableElement_onDeletePipeline).call(this, pipeline.id)}
|
|
107
|
+
>Delete</uui-button
|
|
108
|
+
>`}</uui-button-group
|
|
109
109
|
>`;
|
|
110
110
|
}
|
|
111
111
|
};
|
package/dist/pipeline/index.d.ts
CHANGED
package/dist/pipeline/index.js
CHANGED
|
@@ -39,7 +39,9 @@ let JumooProcessingErrorModal = class JumooProcessingErrorModal extends UmbModal
|
|
|
39
39
|
<div class="detail">${this.data?.error.action}</div>
|
|
40
40
|
</div>
|
|
41
41
|
<div class="more">
|
|
42
|
-
<div class="label"
|
|
42
|
+
<div class="label">
|
|
43
|
+
<umb-localize key="jumooProcError_step"></umb-localize>
|
|
44
|
+
</div>
|
|
43
45
|
<div class="detail">
|
|
44
46
|
${this.data?.error.step}
|
|
45
47
|
${when(this.data?.error.subStep, () => html `(${this.data?.error.subStep})`)}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
|
2
|
+
import type { PipelineRunStatus } from '../api';
|
|
3
|
+
/**
|
|
4
|
+
* @description a pipeline that couldn't be created or resumed because another one holds its
|
|
5
|
+
* concurrency key (the server's 409 `PipelineConflictProblemDetails`).
|
|
6
|
+
*/
|
|
7
|
+
export interface PipelineConflict {
|
|
8
|
+
/** the pipeline holding the key */
|
|
9
|
+
runningPipelineId: string;
|
|
10
|
+
/** the key that is held */
|
|
11
|
+
concurrencyKey: string;
|
|
12
|
+
/** the server's message */
|
|
13
|
+
detail?: string | null;
|
|
14
|
+
/** the holder's status, when the current user can see it */
|
|
15
|
+
holder?: PipelineRunStatus;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* @description reads the 409 "already running" body off an error, whether it is the raw
|
|
19
|
+
* problem details the generated client throws or the `UmbApiError` tryExecute wraps it in.
|
|
20
|
+
* @param error the error from a request.
|
|
21
|
+
* @returns the conflict, or undefined for any other error.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getPipelineConflict(error: unknown): PipelineConflict | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* @description tryExecute, except a 409 "already running" comes back as the error without a
|
|
26
|
+
* notification, so the caller can show it inline. Every other error notifies as normal.
|
|
27
|
+
* @param host the host the request (and any notification) belongs to.
|
|
28
|
+
* @param promise the request.
|
|
29
|
+
* @returns the response, as tryExecute returns it.
|
|
30
|
+
*/
|
|
31
|
+
export declare function tryExecuteAllowingConflict<T>(host: UmbControllerHost, promise: Promise<T>): Promise<import("@umbraco-cms/backoffice/resources").UmbApiResponse<T>>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { tryExecute, UmbApiError } from '@umbraco-cms/backoffice/resources';
|
|
2
|
+
/**
|
|
3
|
+
* @description reads the 409 "already running" body off an error, whether it is the raw
|
|
4
|
+
* problem details the generated client throws or the `UmbApiError` tryExecute wraps it in.
|
|
5
|
+
* @param error the error from a request.
|
|
6
|
+
* @returns the conflict, or undefined for any other error.
|
|
7
|
+
*/
|
|
8
|
+
export function getPipelineConflict(error) {
|
|
9
|
+
const body = UmbApiError.isUmbApiError(error) ? error.problemDetails : error;
|
|
10
|
+
if (!body || typeof body !== 'object')
|
|
11
|
+
return undefined;
|
|
12
|
+
const problem = body;
|
|
13
|
+
if (problem.status !== 409 || typeof problem.runningPipelineId !== 'string')
|
|
14
|
+
return undefined;
|
|
15
|
+
return {
|
|
16
|
+
runningPipelineId: problem.runningPipelineId,
|
|
17
|
+
concurrencyKey: typeof problem.concurrencyKey === 'string' ? problem.concurrencyKey : '',
|
|
18
|
+
detail: typeof problem.detail === 'string' ? problem.detail : undefined,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* @description tryExecute, except a 409 "already running" comes back as the error without a
|
|
23
|
+
* notification, so the caller can show it inline. Every other error notifies as normal.
|
|
24
|
+
* @param host the host the request (and any notification) belongs to.
|
|
25
|
+
* @param promise the request.
|
|
26
|
+
* @returns the response, as tryExecute returns it.
|
|
27
|
+
*/
|
|
28
|
+
export async function tryExecuteAllowingConflict(host, promise) {
|
|
29
|
+
let conflict;
|
|
30
|
+
const response = await tryExecute(host, promise.catch((error) => {
|
|
31
|
+
if (!getPipelineConflict(error))
|
|
32
|
+
throw error;
|
|
33
|
+
conflict = error;
|
|
34
|
+
return undefined;
|
|
35
|
+
}));
|
|
36
|
+
if (!conflict)
|
|
37
|
+
return response;
|
|
38
|
+
const problem = conflict;
|
|
39
|
+
return {
|
|
40
|
+
error: new UmbApiError(problem.detail ?? problem.title ?? 'Conflict', 409, null, problem),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
|
2
2
|
import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
|
3
3
|
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
|
4
|
-
import { PipelineState, ProcessingMessage, ProcessingOptions, ProcessingResults } from '@jumoo/processing';
|
|
4
|
+
import { PipelineState, ProcessingMessage, ProcessingOptions, ProcessingResults, PipelineConflict } from '@jumoo/processing';
|
|
5
5
|
/**
|
|
6
6
|
* @description context for keeping pipeline status and progress as we do things.
|
|
7
7
|
*/
|
|
@@ -13,6 +13,11 @@ export declare class PipelineContext extends UmbControllerBase {
|
|
|
13
13
|
readonly results: import("rxjs").Observable<ProcessingResults | undefined>;
|
|
14
14
|
readonly message: import("rxjs").Observable<ProcessingMessage | undefined>;
|
|
15
15
|
readonly backgroundState: import("rxjs").Observable<PipelineState | undefined>;
|
|
16
|
+
/**
|
|
17
|
+
* @description set when the pipeline couldn't be created or resumed because another one
|
|
18
|
+
* holds its concurrency key - "already running".
|
|
19
|
+
*/
|
|
20
|
+
readonly conflict: import("rxjs").Observable<PipelineConflict | undefined>;
|
|
16
21
|
constructor(host: UmbControllerHost);
|
|
17
22
|
private isMessage;
|
|
18
23
|
private isState;
|
|
@@ -9,11 +9,14 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
9
9
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
|
-
var _PipelineContext_instances, _PipelineContext_repository, _PipelineContext_hubContext, _PipelineContext_pipeline, _PipelineContext_state, _PipelineContext_options, _PipelineContext_results, _PipelineContext_message, _PipelineContext_backgroundState, _PipelineContext_clientId, _PipelineContext_hasConnectedBefore, _PipelineContext_onClientIdChanged, _PipelineContext_isInFlight, _PipelineContext_trigger, _PipelineContext_close, _PipelineContext_finalized, _PipelineContext_valid, _PipelineContext_pending;
|
|
12
|
+
var _PipelineContext_instances, _PipelineContext_repository, _PipelineContext_hubContext, _PipelineContext_pipeline, _PipelineContext_state, _PipelineContext_options, _PipelineContext_results, _PipelineContext_message, _PipelineContext_backgroundState, _PipelineContext_conflict, _PipelineContext_clientId, _PipelineContext_hasConnectedBefore, _PipelineContext_onClientIdChanged, _PipelineContext_isInFlight, _PipelineContext_setConflict, _PipelineContext_retryAfterSignIn, _PipelineContext_waitForSignIn, _PipelineContext_trigger, _PipelineContext_close, _PipelineContext_finalized, _PipelineContext_valid, _PipelineContext_pending;
|
|
13
13
|
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
|
14
14
|
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
|
15
15
|
import { UmbBooleanState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
|
|
16
|
-
import {
|
|
16
|
+
import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth';
|
|
17
|
+
import { UmbApiError } from '@umbraco-cms/backoffice/resources';
|
|
18
|
+
import { filter, firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs';
|
|
19
|
+
import { PipelineStatus, PipelineRepository, ProcessingSignalRContext, getPipelineConflict, } from '@jumoo/processing';
|
|
17
20
|
/**
|
|
18
21
|
* @description context for keeping pipeline status and progress as we do things.
|
|
19
22
|
*/
|
|
@@ -35,6 +38,12 @@ export class PipelineContext extends UmbControllerBase {
|
|
|
35
38
|
this.message = __classPrivateFieldGet(this, _PipelineContext_message, "f").asObservable();
|
|
36
39
|
_PipelineContext_backgroundState.set(this, new UmbObjectState(undefined));
|
|
37
40
|
this.backgroundState = __classPrivateFieldGet(this, _PipelineContext_backgroundState, "f").asObservable();
|
|
41
|
+
_PipelineContext_conflict.set(this, new UmbObjectState(undefined));
|
|
42
|
+
/**
|
|
43
|
+
* @description set when the pipeline couldn't be created or resumed because another one
|
|
44
|
+
* holds its concurrency key - "already running".
|
|
45
|
+
*/
|
|
46
|
+
this.conflict = __classPrivateFieldGet(this, _PipelineContext_conflict, "f").asObservable();
|
|
38
47
|
// tracks the SignalR connection id ourselves, alongside the hub context's own clientId
|
|
39
48
|
// observable, so #onClientIdChanged can tell "first connect" (nothing to reattach - there's
|
|
40
49
|
// no prior, now-dead connection) apart from "changed after already having one" (a genuine
|
|
@@ -59,8 +68,7 @@ export class PipelineContext extends UmbControllerBase {
|
|
|
59
68
|
__classPrivateFieldSet(this, _PipelineContext_repository, new PipelineRepository(this), "f");
|
|
60
69
|
this.provideContext(JUMOO_PROCESSING_CONTEXT, this);
|
|
61
70
|
__classPrivateFieldSet(this, _PipelineContext_hubContext, new ProcessingSignalRContext(this), "f");
|
|
62
|
-
|
|
63
|
-
__classPrivateFieldGet(this, _PipelineContext_hubContext, "f").startHub(token, ['message', 'state']);
|
|
71
|
+
__classPrivateFieldGet(this, _PipelineContext_hubContext, "f").startHub(['message', 'state']);
|
|
64
72
|
this.observe(__classPrivateFieldGet(this, _PipelineContext_hubContext, "f").messagesOf('message'), (message) => {
|
|
65
73
|
if (!message?.message)
|
|
66
74
|
return;
|
|
@@ -86,8 +94,13 @@ export class PipelineContext extends UmbControllerBase {
|
|
|
86
94
|
return message !== undefined;
|
|
87
95
|
}
|
|
88
96
|
async createPipeline(processor, strategy, options) {
|
|
89
|
-
|
|
90
|
-
__classPrivateFieldGet(this,
|
|
97
|
+
__classPrivateFieldGet(this, _PipelineContext_conflict, "f").setValue(undefined);
|
|
98
|
+
const { data, error } = await __classPrivateFieldGet(this, _PipelineContext_instances, "m", _PipelineContext_retryAfterSignIn).call(this, () => __classPrivateFieldGet(this, _PipelineContext_repository, "f").tryCreate(processor, strategy, options));
|
|
99
|
+
if (error) {
|
|
100
|
+
await __classPrivateFieldGet(this, _PipelineContext_instances, "m", _PipelineContext_setConflict).call(this, error);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
__classPrivateFieldGet(this, _PipelineContext_pipeline, "f").setValue(data);
|
|
91
104
|
}
|
|
92
105
|
async loadPipeline(id) {
|
|
93
106
|
const pipeline = await __classPrivateFieldGet(this, _PipelineContext_repository, "f").Get(id);
|
|
@@ -112,8 +125,14 @@ export class PipelineContext extends UmbControllerBase {
|
|
|
112
125
|
async process() {
|
|
113
126
|
if (!__classPrivateFieldGet(this, _PipelineContext_pipeline, "f").value)
|
|
114
127
|
return;
|
|
115
|
-
const
|
|
116
|
-
const response = await __classPrivateFieldGet(this,
|
|
128
|
+
const pipelineId = __classPrivateFieldGet(this, _PipelineContext_pipeline, "f").value;
|
|
129
|
+
const { data: response, error } = await __classPrivateFieldGet(this, _PipelineContext_instances, "m", _PipelineContext_retryAfterSignIn).call(this, () =>
|
|
130
|
+
// the connection id is read each time: the hub can reconnect while the user signs in.
|
|
131
|
+
__classPrivateFieldGet(this, _PipelineContext_repository, "f").tryProcess(pipelineId, __classPrivateFieldGet(this, _PipelineContext_hubContext, "f")?.getClientId() ?? null));
|
|
132
|
+
if (error) {
|
|
133
|
+
await __classPrivateFieldGet(this, _PipelineContext_instances, "m", _PipelineContext_setConflict).call(this, error);
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
117
136
|
if (response?.state) {
|
|
118
137
|
__classPrivateFieldGet(this, _PipelineContext_state, "f").setValue(response.state);
|
|
119
138
|
}
|
|
@@ -128,7 +147,10 @@ export class PipelineContext extends UmbControllerBase {
|
|
|
128
147
|
async updateOptions(options) {
|
|
129
148
|
if (!__classPrivateFieldGet(this, _PipelineContext_pipeline, "f").value)
|
|
130
149
|
return;
|
|
131
|
-
const
|
|
150
|
+
const pipelineId = __classPrivateFieldGet(this, _PipelineContext_pipeline, "f").value;
|
|
151
|
+
const { data: response, error } = await __classPrivateFieldGet(this, _PipelineContext_instances, "m", _PipelineContext_retryAfterSignIn).call(this, () => __classPrivateFieldGet(this, _PipelineContext_repository, "f").tryUpdate(pipelineId, options));
|
|
152
|
+
if (error)
|
|
153
|
+
await __classPrivateFieldGet(this, _PipelineContext_instances, "m", _PipelineContext_setConflict).call(this, error);
|
|
132
154
|
if (response?.options) {
|
|
133
155
|
console.debug('update: pipeline.options', response);
|
|
134
156
|
__classPrivateFieldGet(this, _PipelineContext_options, "f").setValue(response.options);
|
|
@@ -140,7 +162,10 @@ export class PipelineContext extends UmbControllerBase {
|
|
|
140
162
|
async saveOptions() {
|
|
141
163
|
if (!__classPrivateFieldGet(this, _PipelineContext_pipeline, "f").value)
|
|
142
164
|
return;
|
|
143
|
-
const
|
|
165
|
+
const pipelineId = __classPrivateFieldGet(this, _PipelineContext_pipeline, "f").value;
|
|
166
|
+
const { data: response, error } = await __classPrivateFieldGet(this, _PipelineContext_instances, "m", _PipelineContext_retryAfterSignIn).call(this, () => __classPrivateFieldGet(this, _PipelineContext_repository, "f").tryUpdate(pipelineId, __classPrivateFieldGet(this, _PipelineContext_options, "f").value));
|
|
167
|
+
if (error)
|
|
168
|
+
await __classPrivateFieldGet(this, _PipelineContext_instances, "m", _PipelineContext_setConflict).call(this, error);
|
|
144
169
|
if (response?.options) {
|
|
145
170
|
console.debug('save:pipeline.options', response);
|
|
146
171
|
__classPrivateFieldGet(this, _PipelineContext_options, "f").setValue(response.options);
|
|
@@ -217,7 +242,7 @@ export class PipelineContext extends UmbControllerBase {
|
|
|
217
242
|
__classPrivateFieldGet(this, _PipelineContext_pending, "f").setValue(__classPrivateFieldGet(this, _PipelineContext_options, "f").value);
|
|
218
243
|
}
|
|
219
244
|
}
|
|
220
|
-
_PipelineContext_repository = new WeakMap(), _PipelineContext_hubContext = new WeakMap(), _PipelineContext_pipeline = new WeakMap(), _PipelineContext_state = new WeakMap(), _PipelineContext_options = new WeakMap(), _PipelineContext_results = new WeakMap(), _PipelineContext_message = new WeakMap(), _PipelineContext_backgroundState = new WeakMap(), _PipelineContext_clientId = new WeakMap(), _PipelineContext_hasConnectedBefore = new WeakMap(), _PipelineContext_trigger = new WeakMap(), _PipelineContext_close = new WeakMap(), _PipelineContext_finalized = new WeakMap(), _PipelineContext_valid = new WeakMap(), _PipelineContext_pending = new WeakMap(), _PipelineContext_instances = new WeakSet(), _PipelineContext_onClientIdChanged =
|
|
245
|
+
_PipelineContext_repository = new WeakMap(), _PipelineContext_hubContext = new WeakMap(), _PipelineContext_pipeline = new WeakMap(), _PipelineContext_state = new WeakMap(), _PipelineContext_options = new WeakMap(), _PipelineContext_results = new WeakMap(), _PipelineContext_message = new WeakMap(), _PipelineContext_backgroundState = new WeakMap(), _PipelineContext_conflict = new WeakMap(), _PipelineContext_clientId = new WeakMap(), _PipelineContext_hasConnectedBefore = new WeakMap(), _PipelineContext_trigger = new WeakMap(), _PipelineContext_close = new WeakMap(), _PipelineContext_finalized = new WeakMap(), _PipelineContext_valid = new WeakMap(), _PipelineContext_pending = new WeakMap(), _PipelineContext_instances = new WeakSet(), _PipelineContext_onClientIdChanged =
|
|
221
246
|
/**
|
|
222
247
|
* @description called whenever the SignalR connection id changes - which includes the very
|
|
223
248
|
* first connect, where there's nothing to reattach yet, and a reconnect, where a pipeline
|
|
@@ -255,6 +280,64 @@ async function _PipelineContext_onClientIdChanged(clientId) {
|
|
|
255
280
|
default:
|
|
256
281
|
return false;
|
|
257
282
|
}
|
|
283
|
+
}, _PipelineContext_setConflict =
|
|
284
|
+
/**
|
|
285
|
+
* @description records a 409 "already running", with the holder's status when the user can
|
|
286
|
+
* see it.
|
|
287
|
+
* @param error the error from a create, process or update.
|
|
288
|
+
* @returns true when it was a conflict.
|
|
289
|
+
*/
|
|
290
|
+
async function _PipelineContext_setConflict(error) {
|
|
291
|
+
const conflict = getPipelineConflict(error);
|
|
292
|
+
if (!conflict)
|
|
293
|
+
return false;
|
|
294
|
+
__classPrivateFieldGet(this, _PipelineContext_conflict, "f").setValue(conflict);
|
|
295
|
+
const holder = await __classPrivateFieldGet(this, _PipelineContext_repository, "f").getRunStatus(conflict.runningPipelineId);
|
|
296
|
+
if (holder)
|
|
297
|
+
__classPrivateFieldGet(this, _PipelineContext_conflict, "f").setValue({ ...conflict, holder });
|
|
298
|
+
return true;
|
|
299
|
+
}, _PipelineContext_retryAfterSignIn =
|
|
300
|
+
/**
|
|
301
|
+
* @description makes a create, update or process call, and makes it again if it hit a session
|
|
302
|
+
* timeout. From Umbraco 19 a 401 on a POST opens the login modal but, unlike a GET, the
|
|
303
|
+
* request is not retried - so a run would drop back to waiting mid-pipeline, or carry on with
|
|
304
|
+
* options that were never saved. The 401 comes from the auth middleware, before our
|
|
305
|
+
* controller runs, so once the user has signed back in it is safe to send the same call again.
|
|
306
|
+
* @param call the request, made again on each attempt.
|
|
307
|
+
* @returns the first result that isn't a 401.
|
|
308
|
+
*/
|
|
309
|
+
async function _PipelineContext_retryAfterSignIn(call) {
|
|
310
|
+
let result = await call();
|
|
311
|
+
while (result.error && (await __classPrivateFieldGet(this, _PipelineContext_instances, "m", _PipelineContext_waitForSignIn).call(this, result.error))) {
|
|
312
|
+
result = await call();
|
|
313
|
+
}
|
|
314
|
+
return result;
|
|
315
|
+
}, _PipelineContext_waitForSignIn =
|
|
316
|
+
/**
|
|
317
|
+
* @description waits for the user to sign back in after a session timeout.
|
|
318
|
+
* @param error the error from a request.
|
|
319
|
+
* @returns true once the user has signed back in after a 401, false for any other error.
|
|
320
|
+
*/
|
|
321
|
+
async function _PipelineContext_waitForSignIn(error) {
|
|
322
|
+
// the generated client doesn't throw, so this is usually the problem details themselves
|
|
323
|
+
// rather than an UmbApiError - check both, as getPipelineConflict does.
|
|
324
|
+
const status = UmbApiError.isUmbApiError(error)
|
|
325
|
+
? error.status
|
|
326
|
+
: error?.status;
|
|
327
|
+
if (status !== 401)
|
|
328
|
+
return false;
|
|
329
|
+
const authContext = await this.getContext(UMB_AUTH_CONTEXT);
|
|
330
|
+
if (!authContext)
|
|
331
|
+
return false;
|
|
332
|
+
// the 401 clears the session, but the request can land before that does - so wait for
|
|
333
|
+
// the session to be gone and then back, rather than taking a still-signed-in value.
|
|
334
|
+
let signedOut = false;
|
|
335
|
+
await firstValueFrom(authContext.isAuthorized.pipe(filter((isAuthorized) => {
|
|
336
|
+
if (!isAuthorized)
|
|
337
|
+
signedOut = true;
|
|
338
|
+
return isAuthorized && signedOut;
|
|
339
|
+
})));
|
|
340
|
+
return true;
|
|
258
341
|
};
|
|
259
342
|
export default PipelineContext;
|
|
260
343
|
export const JUMOO_PROCESSING_CONTEXT = new UmbContextToken('JumooPipelineContext');
|
|
@@ -9,6 +9,8 @@ import { ProcessingOptions } from '@jumoo/processing';
|
|
|
9
9
|
* @fires pipeline-waiting - when the pipeline is waiting for user input
|
|
10
10
|
* @fires pipeline-last-action - when the pipeline is on the last action
|
|
11
11
|
* @fires pipeline-status - the current status of the pipeline.
|
|
12
|
+
* @fires pipeline-conflict - when the pipeline can't be created or resumed because another
|
|
13
|
+
* process is already running for the same target (detail is the PipelineConflict).
|
|
12
14
|
*/
|
|
13
15
|
export declare class JumooPipelineElement extends UmbLitElement {
|
|
14
16
|
private context?;
|
|
@@ -35,6 +37,7 @@ export declare class JumooPipelineElement extends UmbLitElement {
|
|
|
35
37
|
private results;
|
|
36
38
|
private message;
|
|
37
39
|
private backgroundState;
|
|
40
|
+
private conflict;
|
|
38
41
|
constructor();
|
|
39
42
|
connectedCallback(): void;
|
|
40
43
|
private process;
|
|
@@ -57,6 +60,14 @@ export declare class JumooPipelineElement extends UmbLitElement {
|
|
|
57
60
|
*/
|
|
58
61
|
private onLastAction;
|
|
59
62
|
render(): import("lit-html").TemplateResult<1>;
|
|
63
|
+
private renderConflict;
|
|
64
|
+
/**
|
|
65
|
+
* @description the time worth showing for the holder: when it was queued, when it last
|
|
66
|
+
* moved, how long it has been paused, or when it is due.
|
|
67
|
+
* @param holder the holder's status.
|
|
68
|
+
* @returns the label key and the time, or undefined when there is nothing to show.
|
|
69
|
+
*/
|
|
70
|
+
private conflictTime;
|
|
60
71
|
private renderDebug;
|
|
61
72
|
private renderStatus;
|
|
62
73
|
private renderBackground;
|
|
@@ -7,6 +7,24 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { css, customElement, html, nothing, property, state, } from '@umbraco-cms/backoffice/external/lit';
|
|
8
8
|
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
|
9
9
|
import { PipelineStatus, JUMOO_PROCESSING_CONTEXT, } from '@jumoo/processing';
|
|
10
|
+
import { PipelineRunState } from '../api/index.js';
|
|
11
|
+
/**
|
|
12
|
+
* @description the suffix on the conflict panel's headline and message keys for the holder's
|
|
13
|
+
* status. A holder that isn't moving or waiting gets the general wording.
|
|
14
|
+
* @param status the holder's status.
|
|
15
|
+
* @returns the key suffix.
|
|
16
|
+
*/
|
|
17
|
+
function conflictSuffix(status) {
|
|
18
|
+
switch (status) {
|
|
19
|
+
case PipelineRunState.RUNNING:
|
|
20
|
+
case PipelineRunState.QUEUED:
|
|
21
|
+
case PipelineRunState.WAITING:
|
|
22
|
+
case PipelineRunState.SCHEDULED:
|
|
23
|
+
return status;
|
|
24
|
+
default:
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
10
28
|
/**
|
|
11
29
|
* @property {string} processor - the processor to use for the pipeline
|
|
12
30
|
* @property {string} strategy - the strategy to use for the pipeline
|
|
@@ -16,6 +34,8 @@ import { PipelineStatus, JUMOO_PROCESSING_CONTEXT, } from '@jumoo/processing';
|
|
|
16
34
|
* @fires pipeline-waiting - when the pipeline is waiting for user input
|
|
17
35
|
* @fires pipeline-last-action - when the pipeline is on the last action
|
|
18
36
|
* @fires pipeline-status - the current status of the pipeline.
|
|
37
|
+
* @fires pipeline-conflict - when the pipeline can't be created or resumed because another
|
|
38
|
+
* process is already running for the same target (detail is the PipelineConflict).
|
|
19
39
|
*/
|
|
20
40
|
let JumooPipelineElement = class JumooPipelineElement extends UmbLitElement {
|
|
21
41
|
constructor() {
|
|
@@ -73,6 +93,17 @@ let JumooPipelineElement = class JumooPipelineElement extends UmbLitElement {
|
|
|
73
93
|
this.observe(this.context.results, (results) => {
|
|
74
94
|
this.results = results;
|
|
75
95
|
});
|
|
96
|
+
this.observe(this.context.conflict, (conflict) => {
|
|
97
|
+
const isNew = conflict && !this.conflict;
|
|
98
|
+
this.conflict = conflict;
|
|
99
|
+
if (isNew) {
|
|
100
|
+
this.dispatchEvent(new CustomEvent('pipeline-conflict', {
|
|
101
|
+
bubbles: true,
|
|
102
|
+
composed: true,
|
|
103
|
+
detail: conflict,
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
76
107
|
});
|
|
77
108
|
}
|
|
78
109
|
connectedCallback() {
|
|
@@ -139,7 +170,68 @@ let JumooPipelineElement = class JumooPipelineElement extends UmbLitElement {
|
|
|
139
170
|
this.fireEvent('pipeline-last-action');
|
|
140
171
|
}
|
|
141
172
|
render() {
|
|
142
|
-
return html `${this.renderStatus()}${this.renderDebug()}`;
|
|
173
|
+
return html `${this.conflict ? this.renderConflict(this.conflict) : this.renderStatus()}${this.renderDebug()}`;
|
|
174
|
+
}
|
|
175
|
+
renderConflict(conflict) {
|
|
176
|
+
const holder = conflict.holder;
|
|
177
|
+
const suffix = holder ? conflictSuffix(holder.status) : '';
|
|
178
|
+
const time = holder ? this.conflictTime(holder) : undefined;
|
|
179
|
+
const dateFormat = {
|
|
180
|
+
dateStyle: 'medium',
|
|
181
|
+
timeStyle: 'short',
|
|
182
|
+
};
|
|
183
|
+
return html `<div class="conflict">
|
|
184
|
+
<div class="header">
|
|
185
|
+
<uui-icon name="icon-alert"></uui-icon>
|
|
186
|
+
<h3>${this.localize.term(`jumooProcConflict_headline${suffix}`)}</h3>
|
|
187
|
+
</div>
|
|
188
|
+
<p>${this.localize.term(`jumooProcConflict_message${suffix}`)}</p>
|
|
189
|
+
${holder
|
|
190
|
+
? html `<dl>
|
|
191
|
+
<dt><umb-localize key="jumooProcConflict_status">Status</umb-localize></dt>
|
|
192
|
+
<dd>${this.localize.term(`jumooProcConflict_state${holder.status}`)}</dd>
|
|
193
|
+
${holder.startedByName
|
|
194
|
+
? html `<dt>
|
|
195
|
+
<umb-localize key="jumooProcConflict_startedBy"
|
|
196
|
+
>Started by</umb-localize
|
|
197
|
+
>
|
|
198
|
+
</dt>
|
|
199
|
+
<dd>${holder.startedByName}</dd>`
|
|
200
|
+
: nothing}
|
|
201
|
+
${time
|
|
202
|
+
? html `<dt>${this.localize.term(time.label)}</dt>
|
|
203
|
+
<dd>${this.localize.date(time.value, dateFormat)}</dd>`
|
|
204
|
+
: nothing}
|
|
205
|
+
${holder.status === PipelineRunState.RUNNING
|
|
206
|
+
? html `<dt>
|
|
207
|
+
<umb-localize key="jumooProcConflict_progress"
|
|
208
|
+
>Progress</umb-localize
|
|
209
|
+
>
|
|
210
|
+
</dt>
|
|
211
|
+
<dd>${Math.round(holder.progress.percentage)}%</dd>`
|
|
212
|
+
: nothing}
|
|
213
|
+
</dl>`
|
|
214
|
+
: nothing}
|
|
215
|
+
</div>`;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* @description the time worth showing for the holder: when it was queued, when it last
|
|
219
|
+
* moved, how long it has been paused, or when it is due.
|
|
220
|
+
* @param holder the holder's status.
|
|
221
|
+
* @returns the label key and the time, or undefined when there is nothing to show.
|
|
222
|
+
*/
|
|
223
|
+
conflictTime(holder) {
|
|
224
|
+
const pick = (label, value) => value ? { label: `jumooProcConflict_${label}`, value } : undefined;
|
|
225
|
+
switch (holder.status) {
|
|
226
|
+
case PipelineRunState.QUEUED:
|
|
227
|
+
return pick('queued', holder.created);
|
|
228
|
+
case PipelineRunState.WAITING:
|
|
229
|
+
return pick('waitingSince', holder.modified);
|
|
230
|
+
case PipelineRunState.SCHEDULED:
|
|
231
|
+
return pick('scheduledFor', holder.scheduledFor);
|
|
232
|
+
default:
|
|
233
|
+
return pick('lastActivity', holder.modified ?? holder.created);
|
|
234
|
+
}
|
|
143
235
|
}
|
|
144
236
|
renderDebug() {
|
|
145
237
|
return this.debug
|
|
@@ -155,6 +247,7 @@ let JumooPipelineElement = class JumooPipelineElement extends UmbLitElement {
|
|
|
155
247
|
case PipelineStatus.RUNNING:
|
|
156
248
|
return this.renderSteps();
|
|
157
249
|
case PipelineStatus.FAILED:
|
|
250
|
+
case PipelineStatus.CANCELLED:
|
|
158
251
|
return this.renderFail();
|
|
159
252
|
case PipelineStatus.BACKGROUND:
|
|
160
253
|
return this.renderBackground();
|
|
@@ -219,6 +312,40 @@ JumooPipelineElement.styles = css `
|
|
|
219
312
|
umb-extension-slot {
|
|
220
313
|
height: 100%;
|
|
221
314
|
}
|
|
315
|
+
|
|
316
|
+
.conflict {
|
|
317
|
+
padding: var(--uui-size-space-5);
|
|
318
|
+
border: 1px solid var(--uui-color-warning-standalone);
|
|
319
|
+
border-left-width: 4px;
|
|
320
|
+
border-radius: var(--uui-border-radius);
|
|
321
|
+
background-color: var(--uui-color-surface);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.conflict .header {
|
|
325
|
+
display: flex;
|
|
326
|
+
align-items: center;
|
|
327
|
+
gap: var(--uui-size-space-3);
|
|
328
|
+
color: var(--uui-color-warning-standalone);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.conflict h3 {
|
|
332
|
+
margin: 0;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.conflict dl {
|
|
336
|
+
display: grid;
|
|
337
|
+
grid-template-columns: max-content 1fr;
|
|
338
|
+
gap: var(--uui-size-space-2) var(--uui-size-space-5);
|
|
339
|
+
margin: var(--uui-size-space-4) 0;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.conflict dt {
|
|
343
|
+
font-weight: bold;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.conflict dd {
|
|
347
|
+
margin: 0;
|
|
348
|
+
}
|
|
222
349
|
`;
|
|
223
350
|
__decorate([
|
|
224
351
|
property({ type: String })
|
|
@@ -253,6 +380,9 @@ __decorate([
|
|
|
253
380
|
__decorate([
|
|
254
381
|
state()
|
|
255
382
|
], JumooPipelineElement.prototype, "backgroundState", void 0);
|
|
383
|
+
__decorate([
|
|
384
|
+
state()
|
|
385
|
+
], JumooPipelineElement.prototype, "conflict", void 0);
|
|
256
386
|
JumooPipelineElement = __decorate([
|
|
257
387
|
customElement('jumoo-pipeline')
|
|
258
388
|
], JumooPipelineElement);
|
|
@@ -14,6 +14,47 @@ export declare class PipelineRepository extends UmbControllerBase {
|
|
|
14
14
|
getProcessors(): Promise<string[] | undefined>;
|
|
15
15
|
Get(id: string): Promise<import("@jumoo/processing").Pipeline | undefined>;
|
|
16
16
|
create(processor: string, strategy: string, options: ProcessingOptions | undefined): Promise<string | undefined>;
|
|
17
|
+
/**
|
|
18
|
+
* @description create, returning the whole response rather than just the id, so the caller
|
|
19
|
+
* can tell a 409 "already running" (see getPipelineConflict) from any other failure.
|
|
20
|
+
*/
|
|
21
|
+
tryCreate(processor: string, strategy: string, options: ProcessingOptions | undefined): Promise<import("@umbraco-cms/backoffice/resources").UmbApiResponse<({
|
|
22
|
+
data: string;
|
|
23
|
+
error: undefined;
|
|
24
|
+
} | {
|
|
25
|
+
data: undefined;
|
|
26
|
+
error: unknown;
|
|
27
|
+
}) & {
|
|
28
|
+
request?: Request;
|
|
29
|
+
response?: Response;
|
|
30
|
+
}>>;
|
|
31
|
+
/**
|
|
32
|
+
* @description process, returning the whole response - see tryCreate.
|
|
33
|
+
*/
|
|
34
|
+
tryProcess(id: string, clientId: string | null): Promise<import("@umbraco-cms/backoffice/resources").UmbApiResponse<({
|
|
35
|
+
data: import("@jumoo/processing").Pipeline;
|
|
36
|
+
error: undefined;
|
|
37
|
+
} | {
|
|
38
|
+
data: undefined;
|
|
39
|
+
error: unknown;
|
|
40
|
+
}) & {
|
|
41
|
+
request?: Request;
|
|
42
|
+
response?: Response;
|
|
43
|
+
}>>;
|
|
44
|
+
/**
|
|
45
|
+
* @description update, returning the whole response - see tryCreate.
|
|
46
|
+
*/
|
|
47
|
+
tryUpdate(id: string, body: ProcessingOptions | undefined): Promise<import("@umbraco-cms/backoffice/resources").UmbApiResponse<({
|
|
48
|
+
data: import("@jumoo/processing").ProcessingOptionsResults;
|
|
49
|
+
error: undefined;
|
|
50
|
+
} | {
|
|
51
|
+
data: undefined;
|
|
52
|
+
error: unknown;
|
|
53
|
+
}) & {
|
|
54
|
+
request?: Request;
|
|
55
|
+
response?: Response;
|
|
56
|
+
}>>;
|
|
57
|
+
getRunStatus(id: string): Promise<import("@jumoo/processing").PipelineRunStatus | undefined>;
|
|
17
58
|
complete(id: string): Promise<void>;
|
|
18
59
|
getResults(id: string): Promise<import("@jumoo/processing").ProcessingResults | undefined>;
|
|
19
60
|
listPipelines(status: PipelineStatus): Promise<import("@jumoo/processing").Pipeline[] | undefined>;
|
|
@@ -29,6 +29,28 @@ export class PipelineRepository extends UmbControllerBase {
|
|
|
29
29
|
async create(processor, strategy, options) {
|
|
30
30
|
return (await this.source.create(processor, strategy, options)).data;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* @description create, returning the whole response rather than just the id, so the caller
|
|
34
|
+
* can tell a 409 "already running" (see getPipelineConflict) from any other failure.
|
|
35
|
+
*/
|
|
36
|
+
async tryCreate(processor, strategy, options) {
|
|
37
|
+
return await this.source.create(processor, strategy, options);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* @description process, returning the whole response - see tryCreate.
|
|
41
|
+
*/
|
|
42
|
+
async tryProcess(id, clientId) {
|
|
43
|
+
return await this.source.process(id, clientId);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* @description update, returning the whole response - see tryCreate.
|
|
47
|
+
*/
|
|
48
|
+
async tryUpdate(id, body) {
|
|
49
|
+
return await this.source.update(id, body);
|
|
50
|
+
}
|
|
51
|
+
async getRunStatus(id) {
|
|
52
|
+
return (await this.source.getRunStatus(id)).data;
|
|
53
|
+
}
|
|
32
54
|
async complete(id) {
|
|
33
55
|
console.debug('complete', id);
|
|
34
56
|
}
|