@forge/events 1.0.3 → 2.0.0-next.1

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +32 -26
  3. package/out/__test__/appEvents.test.d.ts +2 -0
  4. package/out/__test__/appEvents.test.d.ts.map +1 -0
  5. package/out/__test__/appEvents.test.js +137 -0
  6. package/out/__test__/jobProgress.test.js +3 -5
  7. package/out/__test__/queue.test.js +88 -39
  8. package/out/__test__/utils.js +1 -1
  9. package/out/appEvents.d.ts +22 -0
  10. package/out/appEvents.d.ts.map +1 -0
  11. package/out/appEvents.js +50 -0
  12. package/out/errors.d.ts +17 -19
  13. package/out/errors.d.ts.map +1 -1
  14. package/out/errors.js +21 -34
  15. package/out/index.d.ts +3 -1
  16. package/out/index.d.ts.map +1 -1
  17. package/out/index.js +3 -1
  18. package/out/jobProgress.d.ts +8 -3
  19. package/out/jobProgress.d.ts.map +1 -1
  20. package/out/jobProgress.js +2 -2
  21. package/out/queries.d.ts +1 -2
  22. package/out/queries.d.ts.map +1 -1
  23. package/out/queue.d.ts +2 -2
  24. package/out/queue.d.ts.map +1 -1
  25. package/out/queue.js +7 -12
  26. package/out/text.d.ts +2 -0
  27. package/out/text.d.ts.map +1 -1
  28. package/out/text.js +2 -0
  29. package/out/types.d.ts +24 -9
  30. package/out/types.d.ts.map +1 -1
  31. package/out/validators.d.ts +4 -4
  32. package/out/validators.d.ts.map +1 -1
  33. package/out/validators.js +22 -11
  34. package/package.json +1 -1
  35. package/src/__test__/appEvents.test.ts +201 -0
  36. package/src/__test__/jobProgress.test.ts +3 -5
  37. package/src/__test__/queue.test.ts +102 -43
  38. package/src/__test__/utils.ts +1 -1
  39. package/src/appEvents.ts +107 -0
  40. package/src/errors.ts +20 -43
  41. package/src/index.ts +11 -1
  42. package/src/jobProgress.ts +11 -5
  43. package/src/queries.ts +2 -2
  44. package/src/queue.ts +9 -15
  45. package/src/text.ts +2 -0
  46. package/src/types.ts +30 -7
  47. package/src/validators.ts +27 -10
  48. package/tsconfig.tsbuildinfo +1 -1
@@ -4,7 +4,7 @@ import { JobProgress } from '../jobProgress';
4
4
  import { __requestAtlassianAsApp } from '@forge/api';
5
5
 
6
6
  jest.mock('@forge/api', () => ({
7
- __requestAtlassianAsApp: getMockFetchMethod('done', 200)
7
+ __requestAtlassianAsApp: getMockFetchMethod({ done: true }, 200)
8
8
  }));
9
9
 
10
10
  const getJobProgress = (jobId: string, apiClientMock?: any) => new JobProgress(jobId, apiClientMock);
@@ -21,8 +21,7 @@ describe('JobProgress methods', () => {
21
21
  200
22
22
  );
23
23
  const jobProgress = getJobProgress('test-queue-name#test-job-id', apiClientMock);
24
- const response = await jobProgress.getStats();
25
- const { success, inProgress, failed } = await response.json();
24
+ const { success, inProgress, failed } = await jobProgress.getStats();
26
25
  verifyApiClientCalledWith(
27
26
  apiClientMock,
28
27
  '/webhook/queue/stats/{contextAri}/{environmentId}/{appId}/{appVersion}',
@@ -100,7 +99,7 @@ describe('JobProgress methods', () => {
100
99
  it('should call the queue/cancel endpoint', async () => {
101
100
  const apiClientMock = getMockFetchMethod({}, 204);
102
101
  const jobProgress = getJobProgress('test-queue-name#test-job-id', apiClientMock);
103
- const response = await jobProgress.cancel();
102
+ await jobProgress.cancel();
104
103
  verifyApiClientCalledWith(
105
104
  apiClientMock,
106
105
  '/webhook/queue/cancel/{contextAri}/{environmentId}/{appId}/{appVersion}',
@@ -109,7 +108,6 @@ describe('JobProgress methods', () => {
109
108
  jobId: 'test-job-id'
110
109
  }
111
110
  );
112
- expect(response.status).toEqual(204);
113
111
  });
114
112
 
115
113
  it('should throw JobDoesNotExistError', async () => {
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  InternalServerError,
3
3
  InvalidQueueNameError,
4
+ InvalidPayloadError,
4
5
  NoEventsToPushError,
5
6
  PartialSuccessError,
6
7
  PayloadTooBigError,
@@ -30,21 +31,57 @@ describe('Queue methods', () => {
30
31
  });
31
32
  });
32
33
 
34
+ const PAYLOAD = { body: { page: 1 } } as const;
35
+
33
36
  describe('push', () => {
34
- it('should call the queue/publish endpoint', async () => {
37
+ it.each([
38
+ ['single payload', PAYLOAD, [PAYLOAD]],
39
+ ['payload array', [PAYLOAD, PAYLOAD], [PAYLOAD, PAYLOAD]]
40
+ ])('should call the queue/publish endpoint when given %s', async (_, payload, expectedPayload) => {
41
+ const apiClientMock = getMockFetchMethod();
42
+ const queue = getQueue('name', apiClientMock);
43
+ await queue.push(payload);
44
+ verifyApiClientCalledPushPathWith(apiClientMock, expectedPayload, 'name');
45
+ });
46
+
47
+ it.each([
48
+ ['number', 1],
49
+ ['string', 'test'],
50
+ ['boolean', true],
51
+ ['array', [1, 2, 3]],
52
+ ['null', null],
53
+ ['undefined', undefined],
54
+ ['array with non-object', [1]],
55
+ ['array with an array', [[2, 3]]]
56
+ ])('rejects non-object (%s) event', async (_, payload) => {
57
+ const apiClientMock = getMockFetchMethod();
58
+ const queue = getQueue('name', apiClientMock);
59
+ // @ts-expect-error Testing invalid payload type on purpose
60
+ await expect(queue.push(payload)).rejects.toThrow(new InvalidPayloadError(`Event must be an object.`));
61
+ expect(apiClientMock).toHaveBeenCalledTimes(0);
62
+ });
63
+
64
+ it.each([
65
+ ['number', 1],
66
+ ['string', 'test'],
67
+ ['boolean', true],
68
+ ['array', [1, 2, 3]],
69
+ ['null', null],
70
+ ['undefined', undefined],
71
+ ['array with non-object', [1]],
72
+ ['array with an array', [[2, 3]]]
73
+ ])('rejects non-object (%s) event body', async (_, body) => {
35
74
  const apiClientMock = getMockFetchMethod();
36
75
  const queue = getQueue('name', apiClientMock);
37
- const payload = {
38
- page: 1
39
- };
40
- await queue.push([payload]);
41
- verifyApiClientCalledPushPathWith(apiClientMock, [payload], 'name');
76
+ // @ts-expect-error Testing invalid payload type on purpose
77
+ await expect(queue.push({ body })).rejects.toThrow(new InvalidPayloadError(`Event body must be an object.`));
78
+ expect(apiClientMock).toHaveBeenCalledTimes(0);
42
79
  });
43
80
 
44
81
  it('should throw InvalidPushSettingsError for delay', async () => {
45
82
  const apiClientMock = getMockFetchMethod();
46
83
  const queue = getQueue('name', apiClientMock);
47
- await expect(queue.push(1, { delayInSeconds: 901 })).rejects.toThrow(
84
+ await expect(queue.push({ ...PAYLOAD, delayInSeconds: 901 })).rejects.toThrow(
48
85
  new InvalidPushSettingsError(`The delayInSeconds setting must be between 0 and 900.`)
49
86
  );
50
87
  expect(apiClientMock).toHaveBeenCalledTimes(0);
@@ -60,7 +97,7 @@ describe('Queue methods', () => {
60
97
  it('should throw TooManyEventsError', async () => {
61
98
  const apiClientMock = getMockFetchMethod();
62
99
  const queue = getQueue('name', apiClientMock);
63
- await expect(queue.push([...Array(51).keys()])).rejects.toThrow(
100
+ await expect(queue.push(Array(51).fill(PAYLOAD))).rejects.toThrow(
64
101
  new TooManyEventsError(`This push contains more than the 50 events allowed.`)
65
102
  );
66
103
  expect(apiClientMock).toHaveBeenCalledTimes(0);
@@ -69,7 +106,7 @@ describe('Queue methods', () => {
69
106
  it('should throw PayloadTooBigError', async () => {
70
107
  const apiClientMock = getMockFetchMethod();
71
108
  const queue = getQueue('name', apiClientMock);
72
- await expect(queue.push('x'.repeat(201 * 1024))).rejects.toThrow(
109
+ await expect(queue.push({ body: { content: 'x'.repeat(201 * 1024) } })).rejects.toThrow(
73
110
  new PayloadTooBigError(`The maximum payload size is 200KB.`)
74
111
  );
75
112
  expect(apiClientMock).toHaveBeenCalledTimes(0);
@@ -78,19 +115,17 @@ describe('Queue methods', () => {
78
115
  it('should throw RateLimitError', async () => {
79
116
  const apiClientMock = getMockFetchMethod({}, 429);
80
117
  const queue = getQueue('name', apiClientMock);
81
- const payload = [...Array(10).keys()];
82
- await expect(queue.push(payload)).rejects.toThrow(new RateLimitError(`Too many requests.`));
83
- verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
118
+ await expect(queue.push(PAYLOAD)).rejects.toThrow(new RateLimitError(`Too many requests.`));
119
+ verifyApiClientCalledPushPathWith(apiClientMock, [PAYLOAD], 'name');
84
120
  });
85
121
 
86
122
  it('should throw InvocationLimitReachedError', async () => {
87
123
  const apiClientMock = getMockFetchMethod({}, 405);
88
124
  const queue = getQueue('name', apiClientMock);
89
- const payload = [...Array(5).keys()];
90
- await expect(queue.push(payload)).rejects.toThrow(
125
+ await expect(queue.push(PAYLOAD)).rejects.toThrow(
91
126
  new InvocationLimitReachedError(`The limit on cyclic invocation has been reached.`)
92
127
  );
93
- verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
128
+ verifyApiClientCalledPushPathWith(apiClientMock, [PAYLOAD], 'name');
94
129
  });
95
130
 
96
131
  it('should throw PartialSuccessError when there are failed events', async () => {
@@ -112,27 +147,37 @@ describe('Queue methods', () => {
112
147
  const queue = getQueue('name', apiClientMock);
113
148
  const payload = [
114
149
  {
115
- content: 'payload-1'
150
+ body: {
151
+ content: 'payload-1'
152
+ }
116
153
  },
117
154
  {
118
- content: 'payload-2'
155
+ body: {
156
+ content: 'payload-2'
157
+ }
119
158
  },
120
159
  {
121
- content: 'payload-3'
160
+ body: {
161
+ content: 'payload-3'
162
+ }
122
163
  }
123
164
  ];
124
165
  await expect(queue.push(payload)).rejects.toThrow(
125
- new PartialSuccessError(`Failed to process 2 event(s).`, [
166
+ new PartialSuccessError(`Failed to process 2 event(s).`, { jobId: 'some-job-id' }, [
126
167
  {
127
168
  errorMessage: 'failed-1',
128
- payload: {
129
- content: 'payload-1'
169
+ event: {
170
+ body: {
171
+ content: 'payload-1'
172
+ }
130
173
  }
131
174
  },
132
175
  {
133
176
  errorMessage: 'failed-3',
134
- payload: {
135
- content: 'payload-3'
177
+ event: {
178
+ body: {
179
+ content: 'payload-3'
180
+ }
136
181
  }
137
182
  }
138
183
  ])
@@ -150,17 +195,23 @@ describe('Queue methods', () => {
150
195
  const queue = getQueue('name', apiClientMock);
151
196
  const payload = [
152
197
  {
153
- content: 'payload-1'
198
+ body: {
199
+ content: 'payload-1'
200
+ }
154
201
  },
155
202
  {
156
- content: 'payload-2'
203
+ body: {
204
+ content: 'payload-2'
205
+ }
157
206
  },
158
207
  {
159
- content: 'payload-3'
208
+ body: {
209
+ content: 'payload-3'
210
+ }
160
211
  }
161
212
  ];
162
213
  await expect(queue.push(payload)).rejects.toThrow(
163
- new PartialSuccessError(`Failed to create stats for job name#12345`, [])
214
+ new PartialSuccessError(`Failed to create stats for job name#12345`, { jobId: 'some-job-id' }, [])
164
215
  );
165
216
  verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
166
217
  });
@@ -181,21 +232,31 @@ describe('Queue methods', () => {
181
232
  const queue = getQueue('name', apiClientMock);
182
233
  const payload = [
183
234
  {
184
- content: 'payload-1'
235
+ body: {
236
+ content: 'payload-1'
237
+ }
185
238
  },
186
239
  {
187
- content: 'payload-2'
240
+ body: {
241
+ content: 'payload-2'
242
+ }
188
243
  }
189
244
  ];
190
245
  await expect(queue.push(payload)).rejects.toThrow(
191
- new PartialSuccessError(`Failed to process 1 event(s). Failed to create stats for job name#12345`, [
192
- {
193
- errorMessage: 'failed-1',
194
- payload: {
195
- content: 'payload-1'
246
+ new PartialSuccessError(
247
+ `Failed to process 1 event(s). Failed to create stats for job name#12345`,
248
+ { jobId: 'some-job-id' },
249
+ [
250
+ {
251
+ errorMessage: 'failed-1',
252
+ event: {
253
+ body: {
254
+ content: 'payload-1'
255
+ }
256
+ }
196
257
  }
197
- }
198
- ])
258
+ ]
259
+ )
199
260
  );
200
261
  verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
201
262
  });
@@ -210,28 +271,26 @@ describe('Queue methods', () => {
210
271
  500
211
272
  );
212
273
  const queue = getQueue('name', apiClientMock);
213
- const payload = [...Array(9).keys()];
214
- await expect(queue.push(payload)).rejects.toThrow(
274
+ await expect(queue.push(PAYLOAD)).rejects.toThrow(
215
275
  new InternalServerError(
216
276
  `500 Status Text: AWS SQS timed out`,
217
277
  500,
218
278
  'The request processing has failed because of an unknown error, exception or failure'
219
279
  )
220
280
  );
221
- verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
281
+ verifyApiClientCalledPushPathWith(apiClientMock, [PAYLOAD], 'name');
222
282
  });
223
283
 
224
284
  it('should throw InternalServerError for error response without response body', async () => {
225
285
  const apiClientMock = getApiClientMockWithoutResponseBody(504, 'Gateway Timeout');
226
286
  const queue = getQueue('name', apiClientMock);
227
- const payload = [1];
228
- await expect(queue.push(1)).rejects.toThrow(new InternalServerError(`504 Gateway Timeout`, 504));
229
- verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
287
+ await expect(queue.push(PAYLOAD)).rejects.toThrow(new InternalServerError(`504 Gateway Timeout`, 504));
288
+ verifyApiClientCalledPushPathWith(apiClientMock, [PAYLOAD], 'name');
230
289
  });
231
290
 
232
291
  it('requests stargate if no api client is provided', async () => {
233
292
  const queue = getQueue('queue');
234
- await queue.push({ test: 'stargate' });
293
+ await queue.push({ body: { test: 'stargate' } });
235
294
  expect(__requestAtlassianAsApp).toHaveBeenCalledTimes(1);
236
295
  });
237
296
  });
@@ -40,7 +40,7 @@ export const verifyApiClientCalledPushPathWith = (
40
40
  ) => {
41
41
  verifyApiClientCalledWith(apiClientMock, '/webhook/queue/publish/{contextAri}/{environmentId}/{appId}/{appVersion}', {
42
42
  queueName: name,
43
- schema: 'ari:cloud:ecosystem::forge/app-event',
43
+ schema: 'ari:cloud:ecosystem::forge/app-event-2',
44
44
  type: 'avi:forge:app:event',
45
45
  payload: payloads
46
46
  });
@@ -0,0 +1,107 @@
1
+ import { post } from './queries';
2
+ import { __requestAtlassianAsApp } from '@forge/api';
3
+
4
+ export interface AppEvent {
5
+ key: string;
6
+ }
7
+
8
+ export type AppEventPublishResult = AppEventPublishSuccess | AppEventPublishError;
9
+
10
+ /**
11
+ * Represents the successful result of the publishing operation.
12
+ *
13
+ * It may happen that publishing some of the events fails even though the request was generally successful.
14
+ * Inspect the `failedEvents` field to see which events failed to be published and why
15
+ * (if the list is empty then the operation was fully successful).
16
+ */
17
+ export type AppEventPublishSuccess = {
18
+ type: 'success';
19
+ /** A list of events that failed to be published along with error messages. */
20
+ failedEvents: AppEventPublishFailure[];
21
+ };
22
+
23
+ /**
24
+ * A publishing failure of a specific event.
25
+ */
26
+ export interface AppEventPublishFailure {
27
+ event: AppEvent;
28
+ errorMessage: string;
29
+ }
30
+
31
+ /**
32
+ * A general error of the event publishing operation.
33
+ * No events were published if this is returned.
34
+ */
35
+ export interface AppEventPublishError {
36
+ type: 'error';
37
+ errorType: AppEventErrorType;
38
+ errorMessage: string;
39
+ }
40
+
41
+ export type AppEventErrorType =
42
+ | 'VALIDATION_ERROR'
43
+ | 'AUTHENTICATION_ERROR'
44
+ | 'AUTHORIZATION_ERROR'
45
+ | 'RATE_LIMIT'
46
+ | 'SERVICE_ERROR'
47
+ | 'SERVICE_UNAVAILABLE'
48
+ | `OTHER`;
49
+
50
+ interface AppEventErrorResponse {
51
+ errorMessages: string[];
52
+ errors: Map<string, string>;
53
+ }
54
+
55
+ interface AppEventSuccessResponse {
56
+ failedEvents: AppEventPublishFailure[];
57
+ }
58
+
59
+ const errorTypes: Record<number, AppEventErrorType> = {
60
+ 400: 'VALIDATION_ERROR',
61
+ 401: 'AUTHENTICATION_ERROR',
62
+ 403: 'AUTHORIZATION_ERROR',
63
+ 429: 'RATE_LIMIT',
64
+ 500: 'SERVICE_ERROR',
65
+ 503: 'SERVICE_UNAVAILABLE'
66
+ };
67
+
68
+ const endpoint = '/forge/events/v1/app-events';
69
+
70
+ export const appEvents = {
71
+ async publish(events: AppEvent | AppEvent[]): Promise<AppEventPublishResult> {
72
+ const eventsArray = Array.isArray(events) ? events : [events];
73
+ const body = {
74
+ events: eventsArray.map((e) => ({
75
+ key: e.key
76
+ }))
77
+ };
78
+
79
+ const response = await post(endpoint, body, __requestAtlassianAsApp);
80
+ const responseBody = await response.json();
81
+
82
+ if (!response.ok) {
83
+ return {
84
+ type: 'error',
85
+ errorType: errorTypes[response.status] ?? 'OTHER',
86
+ errorMessage: getErrorMessage(responseBody)
87
+ };
88
+ }
89
+
90
+ return {
91
+ type: 'success',
92
+ failedEvents: (responseBody as AppEventSuccessResponse).failedEvents ?? []
93
+ };
94
+ }
95
+ };
96
+
97
+ function getErrorMessage(responseBody: AppEventErrorResponse) {
98
+ if (responseBody.errorMessages && responseBody.errorMessages.length > 0) {
99
+ return responseBody.errorMessages.join(', ');
100
+ } else if (responseBody.errors) {
101
+ return Object.entries(responseBody.errors)
102
+ .map(([key, value]) => `${key}: ${value}`)
103
+ .join(', ');
104
+ } else {
105
+ return JSON.stringify(responseBody);
106
+ }
107
+ }
package/src/errors.ts CHANGED
@@ -1,51 +1,36 @@
1
- import { FailedEvent } from './types';
1
+ import { FailedEvent, PushResult } from './types';
2
2
 
3
- export class InvalidPushSettingsError extends Error {
3
+ export class EventsError extends Error {
4
4
  constructor(message: string) {
5
5
  super(message);
6
6
  }
7
7
  }
8
8
 
9
- export class InvalidQueueNameError extends Error {
10
- constructor(message: string) {
11
- super(message);
12
- }
13
- }
9
+ export class InvalidPushSettingsError extends EventsError {}
14
10
 
15
- export class TooManyEventsError extends Error {
16
- constructor(message: string) {
17
- super(message);
18
- }
19
- }
11
+ export class InvalidQueueNameError extends EventsError {}
20
12
 
21
- export class PayloadTooBigError extends Error {
22
- constructor(message: string) {
23
- super(message);
24
- }
25
- }
13
+ export class InvalidPayloadError extends EventsError {}
26
14
 
27
- export class NoEventsToPushError extends Error {
28
- constructor(message: string) {
29
- super(message);
30
- }
31
- }
15
+ export class TooManyEventsError extends EventsError {}
32
16
 
33
- export class RateLimitError extends Error {
34
- constructor(message: string) {
35
- super(message);
36
- }
37
- }
17
+ export class PayloadTooBigError extends EventsError {}
38
18
 
39
- export class PartialSuccessError extends Error {
40
- constructor(message: string, failedEvents: FailedEvent[]) {
19
+ export class NoEventsToPushError extends EventsError {}
20
+
21
+ export class RateLimitError extends EventsError {}
22
+
23
+ export class PartialSuccessError extends EventsError {
24
+ constructor(
25
+ message: string,
26
+ public result: PushResult,
27
+ public failedEvents: FailedEvent[]
28
+ ) {
41
29
  super(message);
42
- this.failedEvents = failedEvents;
43
30
  }
44
-
45
- failedEvents: FailedEvent[];
46
31
  }
47
32
 
48
- export class InternalServerError extends Error {
33
+ export class InternalServerError extends EventsError {
49
34
  constructor(message: string, errorCode?: number, details?: string) {
50
35
  super(message);
51
36
  this.errorCode = errorCode;
@@ -56,14 +41,6 @@ export class InternalServerError extends Error {
56
41
  details?: string;
57
42
  }
58
43
 
59
- export class JobDoesNotExistError extends Error {
60
- constructor(message: string) {
61
- super(message);
62
- }
63
- }
44
+ export class JobDoesNotExistError extends EventsError {}
64
45
 
65
- export class InvocationLimitReachedError extends Error {
66
- constructor(message: string) {
67
- super(message);
68
- }
69
- }
46
+ export class InvocationLimitReachedError extends EventsError {}
package/src/index.ts CHANGED
@@ -11,8 +11,18 @@ export {
11
11
  InvalidPushSettingsError,
12
12
  InvocationLimitReachedError
13
13
  } from './errors';
14
- export { JobProgress } from './jobProgress';
14
+ export { JobProgress, JobStats } from './jobProgress';
15
15
  export { QueueResponse } from './queueResponse';
16
16
  export { InvocationError } from './invocationError';
17
17
  export { InvocationErrorCode } from './invocationErrorCode';
18
18
  export { RetryOptions } from './retryOptions';
19
+ export { AsyncEvent, PushEvent, PushResult } from './types';
20
+ export {
21
+ appEvents,
22
+ AppEvent,
23
+ AppEventPublishResult,
24
+ AppEventPublishSuccess,
25
+ AppEventPublishFailure,
26
+ AppEventPublishError,
27
+ AppEventErrorType
28
+ } from './appEvents';
@@ -1,4 +1,4 @@
1
- import { __requestAtlassianAsApp, FetchMethod, APIResponse } from '@forge/api';
1
+ import { __requestAtlassianAsApp, FetchMethod } from '@forge/api';
2
2
  import { CANCEL_JOB_PATH, GET_STATS_PATH, post } from './queries';
3
3
  import {
4
4
  validateCancelJobAPIResponse,
@@ -8,13 +8,19 @@ import {
8
8
  } from './validators';
9
9
  import { CancelJobRequest, GetStatsRequest } from './types';
10
10
 
11
+ export type JobStats = {
12
+ success: number;
13
+ inProgress: number;
14
+ failed: number;
15
+ };
16
+
11
17
  export class JobProgress {
12
18
  constructor(
13
19
  private readonly id: string,
14
20
  private readonly apiClient: FetchMethod = __requestAtlassianAsApp
15
21
  ) {}
16
22
 
17
- async getStats(): Promise<APIResponse> {
23
+ async getStats(): Promise<JobStats> {
18
24
  const [queueName, jobId] = this.id.split('#');
19
25
  const getStatsRequest: GetStatsRequest = {
20
26
  queueName: queueName,
@@ -26,10 +32,11 @@ export class JobProgress {
26
32
 
27
33
  const response = await post(GET_STATS_PATH, getStatsRequest, this.apiClient);
28
34
  await validateGetStatsAPIResponse(response, getStatsRequest);
29
- return response;
35
+ const { success, inProgress, failed } = await response.json();
36
+ return { success, inProgress, failed };
30
37
  }
31
38
 
32
- async cancel(): Promise<APIResponse> {
39
+ async cancel(): Promise<void> {
33
40
  const [queueName, jobId] = this.id.split('#');
34
41
  const cancelJobRequest: CancelJobRequest = {
35
42
  queueName: queueName,
@@ -41,6 +48,5 @@ export class JobProgress {
41
48
 
42
49
  const response = await post(CANCEL_JOB_PATH, cancelJobRequest, this.apiClient);
43
50
  await validateCancelJobAPIResponse(response, cancelJobRequest);
44
- return response;
45
51
  }
46
52
  }
package/src/queries.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { APIResponse, FetchMethod } from '@forge/api';
2
- import { APIRequest } from './types';
2
+
3
3
  export const PUSH_PATH = '/webhook/queue/publish/{contextAri}/{environmentId}/{appId}/{appVersion}';
4
4
  export const GET_STATS_PATH = '/webhook/queue/stats/{contextAri}/{environmentId}/{appId}/{appVersion}';
5
5
  export const CANCEL_JOB_PATH = '/webhook/queue/cancel/{contextAri}/{environmentId}/{appId}/{appVersion}';
6
6
 
7
- export const post = async (endpoint: string, body: APIRequest, apiClient: FetchMethod): Promise<APIResponse> => {
7
+ export const post = async (endpoint: string, body: unknown, apiClient: FetchMethod): Promise<APIResponse> => {
8
8
  const request = {
9
9
  method: 'POST',
10
10
  body: JSON.stringify(body),
package/src/queue.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { FetchMethod, __requestAtlassianAsApp } from '@forge/api';
2
2
  import { PUSH_PATH, post } from './queries';
3
- import { validatePushAPIResponse, validatePushPayloads, validateQueueKey, validatePushSettings } from './validators';
4
- import { Payload, QueueParams, PushSettings, PushRequest } from './types';
3
+ import { validatePushAPIResponse, validatePushEvents, validateQueueKey } from './validators';
4
+ import { QueueParams, PushEvent, PushRequest, PushResult } from './types';
5
5
  import { v4 as uuid } from 'uuid';
6
6
  import { JobProgress } from './jobProgress';
7
7
 
@@ -13,8 +13,8 @@ export class Queue {
13
13
  validateQueueKey(this.queueParams.key);
14
14
  }
15
15
 
16
- async push(payloads: Payload | Payload[], pushSettings?: PushSettings): Promise<string> {
17
- validatePushPayloads(payloads);
16
+ async push(events: PushEvent | PushEvent[]): Promise<PushResult> {
17
+ const validEvents = validatePushEvents(events);
18
18
  const queueName = this.queueParams.key;
19
19
  const jobId = uuid();
20
20
 
@@ -22,21 +22,15 @@ export class Queue {
22
22
  queueName: queueName,
23
23
  jobId: jobId,
24
24
  type: 'avi:forge:app:event',
25
- schema: 'ari:cloud:ecosystem::forge/app-event',
26
- payload: Array.isArray(payloads) ? payloads : [payloads],
25
+ schema: 'ari:cloud:ecosystem::forge/app-event-2',
26
+ payload: validEvents,
27
27
  time: new Date().toISOString()
28
28
  };
29
29
 
30
- if (pushSettings) {
31
- validatePushSettings(pushSettings);
32
- if (pushSettings.delayInSeconds) {
33
- pushRequest.delayInSeconds = pushSettings.delayInSeconds;
34
- }
35
- }
36
-
37
30
  const response = await post(PUSH_PATH, pushRequest, this.apiClient);
38
- await validatePushAPIResponse(response, pushRequest);
39
- return `${queueName}#${jobId}`;
31
+ const result = { jobId };
32
+ await validatePushAPIResponse(pushRequest, response, result);
33
+ return result;
40
34
  }
41
35
 
42
36
  getJob(jobId: string): JobProgress {
package/src/text.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export const Text = {
2
2
  error: {
3
3
  invalidQueueName: `Queue names can only contain alphanumeric characters, dashes and underscores.`,
4
+ invalidEvent: `Event must be an object.`,
5
+ invalidEventBody: `Event body must be an object.`,
4
6
  invalidDelayInSecondsSetting: `The delayInSeconds setting must be between 0 and 900.`,
5
7
  maxEventsAllowed: (maxEventsCount: number): string =>
6
8
  `This push contains more than the ${maxEventsCount} events allowed.`,