@emartech/program-executor 3.12.0 → 3.13.0

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.
@@ -0,0 +1,8 @@
1
+ name: Dependabot Reviewer Poker
2
+ on:
3
+ pull_request:
4
+ types: [opened, reopened]
5
+ jobs:
6
+ assign-random-reviewer:
7
+ uses: Emartech/IntegrationCloud-DevOps-Workstream/.github/workflows/dependabot-random-review-poker.yml@main
8
+ secrets: inherit
package/package.json CHANGED
@@ -19,8 +19,8 @@
19
19
  "author": "team-shopify@emarsys.com",
20
20
  "license": "ISC",
21
21
  "dependencies": {
22
- "@emartech/json-logger": "3.4.0",
23
- "@emartech/pubsub-client-js": "^1.2.5",
22
+ "@emartech/json-logger": "9.1.0",
23
+ "@emartech/pubsub-client-js": "^1.2.7",
24
24
  "@emartech/rabbitmq-client": "5.9.1",
25
25
  "camelcase-keys": "^6.2.2",
26
26
  "graphql": "^16.11.0"
@@ -39,5 +39,5 @@
39
39
  "pg": "^8.16.3",
40
40
  "semantic-release": "^24.2.0"
41
41
  },
42
- "version": "3.12.0"
42
+ "version": "3.13.0"
43
43
  }
@@ -11,18 +11,56 @@ const { db } = require('./test-helper/get-test-db-config');
11
11
  const customerId = 1234;
12
12
  const topicName = 'program-executor';
13
13
 
14
+ class SuccessfulJob {
15
+ static get name() {
16
+ return 'successful_job';
17
+ }
18
+
19
+ static create(programData) {
20
+ return new SuccessfulJob(programData);
21
+ }
22
+
23
+ // eslint-disable-next-line no-unused-vars
24
+ constructor(programData) {
25
+ /// ... initialize member variables based on programData if needed
26
+ }
27
+
28
+ async execute(message, jobDataHandler) {
29
+ // eslint-disable-next-line no-unused-vars
30
+ const jobSpecificData = await jobDataHandler.get();
31
+ }
32
+ }
33
+
34
+ class ProblematicJob {
35
+ static get name() {
36
+ return 'problematic_job';
37
+ }
38
+
39
+ static create(programData) {
40
+ return new ProblematicJob(programData);
41
+ }
42
+
43
+ // eslint-disable-next-line no-unused-vars
44
+ constructor(programData) {
45
+ }
46
+
47
+ // eslint-disable-next-line no-unused-vars
48
+ async execute(message, jobDataHandler) {
49
+ throw new Error('Some error happened in the job');
50
+ }
51
+ }
52
+
53
+
14
54
  const testJobLibrary = {
15
55
  firstJob: {},
16
- secondJob: {}
17
- };
18
-
19
- const waitForMessages = (timeout) => {
20
- let timeOutLength = timeout;
21
- return new Promise((resolve) => setTimeout(resolve, timeOutLength));
56
+ secondJob: {},
57
+ successful_job: SuccessfulJob,
58
+ problematic_job: ProblematicJob
22
59
  };
23
60
 
24
61
  describe('ProgramExecutor', function () {
25
62
  let config;
63
+ let configFlowControl;
26
64
  let dbConnection;
27
65
  let mockConstructorSpy;
28
66
  let publisher;
@@ -48,7 +86,6 @@ describe('ProgramExecutor', function () {
48
86
 
49
87
  publisher = undefined;
50
88
  jest.clearAllMocks();
51
- jest.resetModules();
52
89
  });
53
90
 
54
91
 
@@ -60,6 +97,16 @@ describe('ProgramExecutor', function () {
60
97
  topicName: topicName,
61
98
  projectId: 'some-project-id'
62
99
  };
100
+
101
+ configFlowControl = {
102
+ knex: dbConnection,
103
+ tableName: 'programs',
104
+ topicName: topicName,
105
+ projectId: 'some-project-id',
106
+ pubSubMaxExtensionMinutes: 20,
107
+ pubSubMinAckDeadline: 10,
108
+ pubSubMaxAckDeadline: {"millis":600000}
109
+ };
63
110
  const result = await e2eTestReuse.beforeEach(topicName);
64
111
  mockConstructorSpy = result.mockConstructorSpy;
65
112
  publisher = result.publisher;
@@ -67,7 +114,7 @@ describe('ProgramExecutor', function () {
67
114
  });
68
115
 
69
116
  describe('#Program Executor', function () {
70
- it('should execute program and maintain status in DB', async function () {
117
+ it('should execute program and maintain status in DB: two skipped jobs', async function () {
71
118
  const runId = await ProgramExecutor.create(config).createProgram(
72
119
  {
73
120
  programData: {
@@ -78,7 +125,30 @@ describe('ProgramExecutor', function () {
78
125
  );
79
126
  await ProgramExecutor.create(config).processPrograms(testJobLibrary);
80
127
 
81
- await waitForMessages(1000);
128
+ await e2eTestReuse.waitForMessages(1000);
129
+
130
+ const result = await dbConnection('programs').where({ run_id: runId }).first();
131
+
132
+ expect(result.run_id).toBe(runId);
133
+ expect(result.jobs).toEqual(['current_program', 'next_program']);
134
+ expect(result.step).toEqual(1);
135
+ expect(result.errored_at).toEqual(null);
136
+ expect(result.finished_at).not.toEqual(null);
137
+ expect(result.program_data).toEqual({ customerId: 1234 });
138
+ });
139
+
140
+ it('should execute program and maintain status in DB: two skipped jobs (with flow control config)', async function () {
141
+ const runId = await ProgramExecutor.create(configFlowControl).createProgram(
142
+ {
143
+ programData: {
144
+ customerId: customerId
145
+ },
146
+ jobs: ['current_program', 'next_program']
147
+ }
148
+ );
149
+ await ProgramExecutor.create(configFlowControl).processPrograms(testJobLibrary);
150
+
151
+ await e2eTestReuse.waitForMessages(1000);
82
152
 
83
153
  const result = await dbConnection('programs').where({ run_id: runId }).first();
84
154
 
@@ -89,6 +159,51 @@ describe('ProgramExecutor', function () {
89
159
  expect(result.finished_at).not.toEqual(null);
90
160
  expect(result.program_data).toEqual({ customerId: 1234 });
91
161
  });
92
- });
93
162
 
163
+ it('should execute program and maintain status in DB: one executed job', async function () {
164
+ const runId = await ProgramExecutor.create(config).createProgram(
165
+ {
166
+ programData: {
167
+ customerId: customerId
168
+ },
169
+ jobs: ['successful_job']
170
+ }
171
+ );
172
+ await ProgramExecutor.create(config).processPrograms(testJobLibrary);
173
+
174
+ await e2eTestReuse.waitForMessages(1000);
175
+
176
+ const result = await dbConnection('programs').where({ run_id: runId }).first();
177
+
178
+ expect(result.run_id).toBe(runId);
179
+ expect(result.jobs).toEqual(['successful_job']);
180
+ expect(result.step).toEqual(0);
181
+ expect(result.errored_at).toEqual(null);
182
+ expect(result.finished_at).not.toEqual(null);
183
+ expect(result.program_data).toEqual({ customerId: 1234 });
184
+ });
185
+
186
+ it('should execute program and maintain status in DB: one failed job', async function () {
187
+ const runId = await ProgramExecutor.create(config).createProgram(
188
+ {
189
+ programData: {
190
+ customerId: customerId
191
+ },
192
+ jobs: ['problematic_job']
193
+ }
194
+ );
195
+ await ProgramExecutor.create(config).processPrograms(testJobLibrary);
196
+
197
+ await e2eTestReuse.waitForMessages(1000);
198
+
199
+ const result = await dbConnection('programs').where({ run_id: runId }).first();
200
+
201
+ expect(result.run_id).toBe(runId);
202
+ expect(result.jobs).toEqual(['problematic_job']);
203
+ expect(result.step).toEqual(0);
204
+ expect(result.errored_at).not.toEqual(null);
205
+ expect(result.finished_at).toEqual(null);
206
+ expect(result.program_data).toEqual({ customerId: 1234 });
207
+ });
208
+ });
94
209
  });
@@ -14,6 +14,9 @@ class ProgramExecutor extends EventEmitter {
14
14
  * @param {string} config.projectId - GCP Project ID
15
15
  * @param {string} config.tableName - Table name for bookkeeping
16
16
  * @param {string} config.topicName - Topic name to publish to
17
+ * @param {string} config.pubSubMaxExtensionMinutes - Pubsub lease management: max extension minutes
18
+ * @param {string} config.pubSubMinAckDeadline - Pubsub lease management: Min Ack Dead line
19
+ * @param {string} config.pubSubMaxAckDeadline - Pubsub lease management: Max Ack Dead line
17
20
  */
18
21
  constructor(config) {
19
22
  super();
@@ -48,6 +51,9 @@ class ProgramExecutor extends EventEmitter {
48
51
  logger: `${this._config.topicName}-consumer`,
49
52
  MaxStreams: 1,
50
53
  MaxMessages: 1,
54
+ MaxExtensionMinutes:this._config.pubSubMaxExtensionMinutes || 60,
55
+ MinAckDeadline:this._config.pubSubMinAckDeadline || undefined,
56
+ MaxAckDeadline:this._config.pubSubMaxAckDeadline || undefined,
51
57
  onMessage: async (message) => {
52
58
  try {
53
59
  await programExecutorProcessor.process(message);
@@ -20,7 +20,10 @@ describe('ProgramExecutor', function () {
20
20
  knex: this.db,
21
21
  tableName: 'programs',
22
22
  topicName: 'program-executor',
23
- projectId: 'amqp://guest:guest@localhost:9999'
23
+ projectId: 'amqp://guest:guest@localhost:9999',
24
+ pubSubMaxExtensionMinutes: 60,
25
+ pubSubMinAckDeadline: {'millis':600000},
26
+ pubSubMaxAckDeadline: {'millis':600000}
24
27
  };
25
28
 
26
29
 
@@ -82,6 +85,9 @@ describe('ProgramExecutor', function () {
82
85
  expect(Consumer.create.mock.calls[0][1]).toEqual(expect.objectContaining({
83
86
  MaxMessages: 1,
84
87
  MaxStreams: 1,
88
+ MaxExtensionMinutes: 60,
89
+ MinAckDeadline:{'millis':600000},
90
+ MaxAckDeadline:{'millis':600000},
85
91
  logger: 'program-executor-consumer' }));
86
92
  });
87
93
 
@@ -1,7 +1,12 @@
1
1
  'use strict';
2
2
 
3
- const logger = require('@emartech/json-logger')('program-executor');
4
- const devLogger = require('@emartech/json-logger')('dev-program-executor');
3
+ const { createLogger } = require('@emartech/json-logger');
4
+ createLogger.configure({
5
+ outputFormat: 'legacy'
6
+ });
7
+
8
+ const logger = createLogger('program-executor');
9
+ const devLogger = createLogger('dev-program-executor');
5
10
  const RetryableError = require('../retryable-error');
6
11
  const JobHandler = require('../job-data-handler');
7
12
 
@@ -19,9 +24,11 @@ class ProgramExecutorProcessor {
19
24
  try {
20
25
  await this._executeNextJob(message);
21
26
  } catch (error) {
22
- devLogger.error('error in job', { run_id: runId, OSPID: process.pid,
27
+ devLogger.error('error in job', {
28
+ run_id: runId, OSPID: process.pid,
23
29
  error, errorMessage: error.message, errorCode: error.code,
24
- errorRetryable: error.retryable, errorIgnorable: error.ignorable });
30
+ errorRetryable: error.retryable, errorIgnorable: error.ignorable
31
+ });
25
32
  if (error.retryable) {
26
33
  await this._programHandler.incrementStepRetryCount(runId);
27
34
  await this._programHandler.setJobRetriableErrorMessage(runId, error.message);
@@ -71,8 +78,10 @@ class ProgramExecutorProcessor {
71
78
 
72
79
  log(action, { programData, jobs, runId, currentJob, level = 'info' }) {
73
80
  logger[level](action,
74
- { customer_id: programData.customerId, run_id: runId, OSPID: process.pid, msg: currentJob,
75
- executor: { current_job: currentJob, program_data: programData, jobs } });
81
+ {
82
+ customer_id: programData.customerId, run_id: runId, OSPID: process.pid, msg: currentJob,
83
+ executor: { current_job: currentJob, program_data: programData, jobs }
84
+ });
76
85
  }
77
86
 
78
87
  static create(programHandler, queueManager, jobLibrary) {
@@ -1,6 +1,11 @@
1
1
  'use strict';
2
2
 
3
- const devLogger = require('@emartech/json-logger')('dev-program-executor');
3
+ const { createLogger } = require('@emartech/json-logger');
4
+ createLogger.configure({
5
+ outputFormat: 'legacy'
6
+ });
7
+
8
+ const devLogger = createLogger('dev-program-executor');
4
9
  const RunIdGenerator = require('../runid-generator');
5
10
 
6
11
  class ProgramHandler {
@@ -1,7 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  const RabbitMq = require('@emartech/rabbitmq-client').RabbitMq;
4
- const logger = require('@emartech/json-logger')('program-executor-queue-manager');
4
+ const { createLogger } = require('@emartech/json-logger');
5
+ createLogger.configure({
6
+ outputFormat: 'legacy'
7
+ });
8
+
9
+ const logger = createLogger('program-executor-queue-manager');
5
10
 
6
11
  class QueueManager {
7
12
  constructor(amqpUrl, queueName) {
@@ -1,7 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  const PubSubClient = require('@emartech/pubsub-client-js').PubSubClient;
4
- const logger = require('@emartech/json-logger')('program-executor-queue-manager-pubsub');
4
+ const { createLogger } = require('@emartech/json-logger');
5
+ createLogger.configure({
6
+ outputFormat: 'legacy'
7
+ });
8
+
9
+ const logger = createLogger('program-executor-queue-manager-pubsub');
5
10
 
6
11
  class QueueManager {
7
12
  constructor(topicName, projectId, gcpKeyFileName) {