apostrophe 3.5.0 → 3.8.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 (88) hide show
  1. package/.eslintrc +4 -0
  2. package/.scratch.md +2 -0
  3. package/CHANGELOG.md +96 -3
  4. package/README.md +1 -1
  5. package/index.js +108 -3
  6. package/lib/moog-require.js +23 -0
  7. package/lib/moog.js +1 -0
  8. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +30 -14
  9. package/modules/@apostrophecms/area/lib/custom-tags/area.js +1 -1
  10. package/modules/@apostrophecms/area/lib/custom-tags/widget.js +1 -1
  11. package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +2 -2
  12. package/modules/@apostrophecms/asset/index.js +77 -13
  13. package/modules/@apostrophecms/attachment/index.js +1 -0
  14. package/modules/@apostrophecms/db/index.js +5 -6
  15. package/modules/@apostrophecms/doc/index.js +2 -0
  16. package/modules/@apostrophecms/doc-type/index.js +39 -16
  17. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +13 -1
  18. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +3 -0
  19. package/modules/@apostrophecms/i18n/i18n/en.json +19 -6
  20. package/modules/@apostrophecms/i18n/i18n/es.json +382 -0
  21. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +379 -0
  22. package/modules/@apostrophecms/i18n/i18n/sk.json +380 -0
  23. package/modules/@apostrophecms/i18n/index.js +10 -1
  24. package/modules/@apostrophecms/image/index.js +2 -1
  25. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +6 -3
  26. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +2 -1
  27. package/modules/@apostrophecms/image-widget/index.js +2 -1
  28. package/modules/@apostrophecms/image-widget/views/widget.html +12 -2
  29. package/modules/@apostrophecms/job/index.js +164 -212
  30. package/modules/@apostrophecms/login/index.js +1 -16
  31. package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +5 -0
  32. package/modules/@apostrophecms/migration/index.js +1 -1
  33. package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +151 -61
  34. package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +6 -2
  35. package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +8 -6
  36. package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +12 -15
  37. package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +6 -0
  38. package/modules/@apostrophecms/notification/index.js +116 -8
  39. package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +89 -11
  40. package/modules/@apostrophecms/notification/ui/apos/components/TheAposNotifications.vue +1 -1
  41. package/modules/@apostrophecms/page/index.js +37 -30
  42. package/modules/@apostrophecms/permission/index.js +1 -1
  43. package/modules/@apostrophecms/permission/ui/apos/components/AposInputRole.vue +4 -2
  44. package/modules/@apostrophecms/piece-type/index.js +178 -61
  45. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +179 -47
  46. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +1 -3
  47. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +138 -0
  48. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +35 -6
  49. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Classes.js +1 -3
  50. package/modules/@apostrophecms/schema/index.js +97 -20
  51. package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +1 -0
  52. package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +4 -1
  53. package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +11 -160
  54. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +24 -2
  55. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +24 -6
  56. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +0 -4
  57. package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +0 -7
  58. package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +25 -3
  59. package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +10 -2
  60. package/modules/@apostrophecms/task/index.js +2 -2
  61. package/modules/@apostrophecms/template/index.js +63 -36
  62. package/modules/@apostrophecms/template/lib/custom-tags/component.js +1 -1
  63. package/modules/@apostrophecms/template/lib/custom-tags/render.js +6 -2
  64. package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +5 -2
  65. package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +1 -1
  66. package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +205 -0
  67. package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +16 -2
  68. package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +4 -3
  69. package/modules/@apostrophecms/util/index.js +2 -2
  70. package/modules/@apostrophecms/util/ui/src/http.js +12 -8
  71. package/modules/@apostrophecms/util/ui/src/util.js +15 -0
  72. package/modules/@apostrophecms/widget-type/index.js +1 -1
  73. package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +1 -0
  74. package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +15 -7
  75. package/package.json +3 -3
  76. package/test/extra_node_modules/improve-global/index.js +7 -0
  77. package/test/extra_node_modules/improve-piece-type/index.js +7 -0
  78. package/test/improve-overrides.js +30 -0
  79. package/test/job.js +224 -0
  80. package/test/modules/@apostrophecms/global/index.js +8 -0
  81. package/test/modules/fragment-all/views/aux-test.html +7 -0
  82. package/test/modules/fragment-all/views/fragment.html +5 -0
  83. package/test/package.json +5 -4
  84. package/test/pieces.js +34 -0
  85. package/test/reverse-relationship.js +170 -0
  86. package/test/templates.js +7 -1
  87. package/test-lib/test.js +23 -12
  88. package/test-lib/util.js +33 -0
@@ -1,22 +1,21 @@
1
- const _ = require('lodash');
1
+ const { stripIndent } = require('common-tags');
2
2
  // The `@apostrophecms/job` module runs long-running jobs in response
3
3
  // to user actions. Batch operations on pieces are a good example.
4
4
  //
5
5
  // The `@apostrophecms/job` module makes it simple to implement
6
- // progress display, avoid server timeouts during long operations,
7
- // and implement a "stop" button.
6
+ // progress display, and avoid server timeouts during long operations.
8
7
  //
9
8
  // See the `run` method for the easiest way to use this module.
10
9
  // The `run` method allows you to implement routes that are designed
11
10
  // to be paired with the `progress` method of this module on
12
11
  // the browser side. All you need to do is make sure the
13
- // ids are posted to your route and then write a function that
12
+ // IDs are posted to your route and then write a function that
14
13
  // can carry out the operation on one id.
15
14
  //
16
15
  // If you just want to add a new batch operation for pieces,
17
- // see the examples already in `@apostrophecms/piece-type` and those added
18
- // by the `@apostrophecms/workflow` module. You don't need to go
19
- // directly to this module for that case.
16
+ // see the examples already in `@apostrophecms/piece-type` (e.g.,
17
+ // `batchSimpleRoute`). You don't need to go directly to this module for that
18
+ // case.
20
19
  //
21
20
  // If your operation doesn't break down neatly into repeated operations
22
21
  // on single documents, look into calling the `start` method and friends
@@ -27,44 +26,55 @@ const _ = require('lodash');
27
26
 
28
27
  module.exports = {
29
28
  options: { collectionName: 'aposJobs' },
29
+ icons: {
30
+ 'database-export-icon': 'DatabaseExport'
31
+ },
30
32
  async init(self) {
31
33
  await self.ensureCollection();
34
+
35
+ self.enableBrowserData();
32
36
  },
33
- // TODO RESTify these
34
37
  apiRoutes(self) {
35
38
  return {
36
39
  post: {
37
- async cancel(req) {
38
- const _id = self.apos.launder.id(req.body._id);
39
- const count = await self.db.updateOne({ _id: _id }, { $set: { canceling: true } });
40
- if (!count) {
41
- throw self.apos.error('notfound');
42
- }
43
- },
44
40
  async progress(req) {
45
- const _id = self.apos.launder.id(req.body._id);
46
- const job = await self.db.findOne({ _id: _id });
47
- if (!job) {
48
- throw self.apos.error('notfound');
49
- }
50
- // % of completion rounded off to 2 decimal places
51
- if (!job.total) {
52
- job.percentage = 0;
53
- } else {
54
- job.percentage = (job.processed / job.total * 100).toFixed(2);
55
- }
56
- return job;
41
+ self.apos.util.warnDev(stripIndent`
42
+ The @apostrophecms/job module's "/progress" route is deprecated.
43
+ Use the RESTful registered "action" route on the module instead.
44
+ `);
45
+
46
+ return {};
57
47
  }
58
48
  }
59
49
  };
60
50
  },
51
+ restApiRoutes (self) {
52
+ return {
53
+ async getOne(req, _id) {
54
+ if (!self.apos.permission.can(req, 'view-draft')) {
55
+ throw self.apos.error('notfound');
56
+ }
57
+
58
+ const jobId = self.apos.launder.id(_id);
59
+ const job = await self.db.findOne({ _id: jobId });
60
+
61
+ if (!job) {
62
+ throw self.apos.error('notfound');
63
+ }
64
+
65
+ job.percentage = !job.total ? 0 : (job.processed / job.total * 100).toFixed(2);
66
+
67
+ return job;
68
+ }
69
+ };
70
+ },
61
71
  methods(self) {
62
72
  return {
63
73
  // Starts and supervises a long-running job such as a
64
74
  // batch operation on pieces. Call it to implement an API route
65
75
  // that runs a job involving carrying out the same action
66
76
  // repetitively on many things. If your job doesn't look like
67
- // that, check out `runNonBatch` instead.
77
+ // that, check out `run` instead.
68
78
  //
69
79
  // The `ids` to be processed should be provided via `req.body.ids`.
70
80
  //
@@ -73,9 +83,10 @@ module.exports = {
73
83
  // necessary; this is recorded as a bad item in the job, it does
74
84
  // not stop the job. `change` may optionally return a result object.
75
85
  //
76
- // Returns an object with `{ jobId: 'cxxxx' }` before the work actually
77
- // begins. This can then be used for progress monitoring via the
78
- // ApostropheJobMonitor Vue component.
86
+ // This method will return an object with a `jobId` identifier as soon as
87
+ // the job is ready to start, and the actual job will continue in the
88
+ // background afterwards. You can pass `jobId` to the `progress` API route
89
+ // of this module as `_id` on the request body to get job status info.
79
90
  //
80
91
  // The actual work continues in background after this method returns.
81
92
  //
@@ -86,122 +97,117 @@ module.exports = {
86
97
  // simpler wrapper for this method if you are implementing a batch operation
87
98
  // on a single type of piece.
88
99
  //
89
- // *Options*
90
- //
91
- // `options.label` should be passed as an object with
92
- // a `title` property, to title the progress modal.
93
- // A default is provided but it is not very informative.
94
- //
95
- // In addition, it may have `failed`, `completed` and
96
- // `running` properties to label the progress modal when the job
97
- // is in those states, and `good` and `bad` properties to label
98
- // the count of items that were successful or had errors.
99
- // All of these properties are optional and reasonable
100
- // defaults are supplied.
101
- async run(req, ids, change, options) {
100
+ // Notification messages should be included on a `req.body.messages` object. See `triggerNotification for details`.
101
+ async runBatch(req, ids, change, options = {}) {
102
102
  let job;
103
- let stopping = false;
103
+ let notification;
104
+ const total = ids.length;
105
+
104
106
  const results = {};
105
107
  const res = req.res;
106
108
  try {
107
109
  // sends a response with a jobId to the browser
108
- const job = await startJob();
110
+ job = await self.start(options);
111
+
112
+ self.setTotal(job, ids.length);
109
113
  // Runs after response is already sent
110
114
  run();
111
- return job;
115
+
116
+ // Trigger the "in progress" notification.
117
+ notification = await self.triggerNotification(req, 'progress', {
118
+ // It's only relevant to pass a job ID to the notification if
119
+ // the notification will show progress. Without a total number we
120
+ // can't show progress.
121
+ jobId: total && job._id,
122
+ ids,
123
+ action: options.action
124
+ });
125
+
126
+ return {
127
+ jobId: job._id
128
+ };
112
129
  } catch (err) {
113
130
  self.apos.util.error(err);
114
131
  if (!job) {
115
132
  return res.status(500).send('error');
116
133
  }
117
134
  try {
118
- await self.end(job, false);
135
+ return await self.end(job, false);
119
136
  } catch (err) {
120
137
  // Not a lot we can do about this since we already
121
138
  // stopped talking to the user
122
139
  self.apos.util.error(err);
123
140
  }
124
141
  }
125
- async function startJob() {
126
- job = await self.start(_.assign({}, options, {
127
- stop: function (job) {
128
- stopping = true;
129
- }
130
- }));
131
- self.setTotal(job, ids.length);
132
- return { jobId: job._id };
133
- }
134
142
  async function run() {
135
143
  let good = false;
136
144
  try {
137
145
  for (const id of ids) {
138
- if (stopping) {
139
- return;
140
- }
141
146
  try {
142
147
  const result = await change(req, id);
143
- self.good(job);
148
+ self.success(job);
144
149
  results[id] = result;
145
150
  } catch (err) {
146
- self.bad(job);
151
+ self.failure(job);
147
152
  }
148
153
  }
149
154
  good = true;
150
155
  } finally {
151
156
  await self.end(job, good, results);
157
+ // Trigger the completed notification.
158
+ await self.triggerNotification(req, 'completed', {
159
+ dismiss: true
160
+ });
161
+ // Dismiss the progress notification. It will delay 4 seconds
162
+ // because "completed" notification will dismiss in 5 and we want
163
+ // to maintain the feeling of process order for users.
164
+ await self.apos.notification.dismiss(req, notification.noteId, 4000);
152
165
  }
153
166
  }
154
167
  },
155
- // Similar to `run`, this method Starts and supervises a long-running job,
156
- // however unlike `run` the `doTheWork` function provided is invoked just
168
+ // Similar to `runBatch`, this method Starts and supervises a long-running job,
169
+ // however unlike `runBatch` the `doTheWork` function provided is invoked just
157
170
  // once, and when it completes the job is over. This is not the way to
158
171
  // implement a batch operation on pieces; see the `batchSimple` method
159
172
  // of that module.
160
173
  //
161
174
  // The `doTheWork` function receives `(req, reporting)` and may optionally invoke
162
- // `reporting.good()` and `reporting.bad()` to update the progress and error
175
+ // `reporting.success()` and `reporting.failure()` to update the progress and error
163
176
  // counters, and `reporting.setTotal()` to indicate the total number of
164
- // counts expected so a progress meter can be rendered. This is optional and
165
- // an indication of progress is still displayed without it.
166
- // `reporting.setResults(object)` may also be called to pass a
167
- // `results` object, which is displayed as a table by the
168
- // `ApostropheJobMonitor` Vue component on the browser side.
169
- //
170
- // This method will return `{ jobId: 'cxxxx' }` as soon as the job is
171
- // ready to start, and the actual job will continue in the background
172
- // afterwards. You should pass `jobId` as a prop to the
173
- // `ApostropheJobMonitor` Vue component (TODO: write this component
174
- // for 3.0).
175
- //
176
- // After the job status is `completed` that component will emit
177
- // suitable events and the `results` object, if any.
177
+ // counts expected so a progress meter can be rendered. This is optional
178
+ // and an indication of progress is still displayed without it.
178
179
  //
179
- // *Options*
180
- //
181
- // `options.label` should be passed as an object with
182
- // a `title` property, to title the progress modal.
183
- // A default is provided but it is not very informative.
180
+ // `reporting.setResults(object)` may also be called to pass a
181
+ // results object, which will be available on the finished job document.
184
182
  //
185
- // In addition, it may have `failed`, `completed` and
186
- // `running` properties to label the progress modal when the job
187
- // is in those states, and `good` and `bad` properties to label
188
- // the count of items that were successful or had errors.
189
- // All of these properties are optional and reasonable
190
- // defaults are supplied.
183
+ // This method will return an object with a `jobId` identifier as soon as
184
+ // the job is ready to start, and the actual job will continue in the
185
+ // background afterwards. You can pass `jobId` to the `progress` API route
186
+ // of this module as `_id` on the request body to get job status info.
191
187
  //
192
- // You may optionally provide `options.stop` or `options.cancel`.
193
- // These async functions will be invoked and awaited
194
- // after the user requests to stop the operation. `stop` must cease
195
- // all operations before resolving, while `cancel` must both cease
196
- // operations and reverse all changes made before resolving.
197
- async runNonBatch(req, doTheWork, options) {
188
+ // Notification messages should be included on a `req.body.messages` object. See `triggerNotification for details`.
189
+ async run(req, doTheWork, options = {}) {
198
190
  const res = req.res;
199
191
  let job;
200
- const canceling = false;
192
+ let notification;
193
+ let total;
194
+
201
195
  try {
202
- const info = await startJob();
196
+ job = await self.start(options);
203
197
  run();
204
- return info;
198
+
199
+ // Trigger the "in progress" notification.
200
+ notification = await self.triggerNotification(req, 'progress', {
201
+ // It's only relevant to pass a job ID to the notification if
202
+ // the notification will show progress. Without a total number we
203
+ // can't show progress.
204
+ jobId: total && job._id,
205
+ count: total
206
+ });
207
+
208
+ return {
209
+ jobId: job._id
210
+ };
205
211
  } catch (err) {
206
212
  self.apos.util.error(err);
207
213
  if (job) {
@@ -213,44 +219,81 @@ module.exports = {
213
219
  return res.status(500).send('error');
214
220
  }
215
221
  }
216
- async function startJob() {
217
- job = await self.start(options);
218
- return { jobId: job._id };
219
- }
222
+
220
223
  async function run() {
221
224
  let results;
222
225
  let good = false;
223
226
  try {
224
227
  await doTheWork(req, {
225
- good: function (n) {
228
+ success (n) {
226
229
  n = n || 1;
227
- return self.good(job, n);
230
+ return self.success(job, n);
228
231
  },
229
- bad: function (n) {
232
+ failure (n) {
230
233
  n = n || 1;
231
- return self.bad(job, n);
234
+ return self.failure(job, n);
232
235
  },
233
- setTotal: function (n) {
236
+ setTotal (n) {
237
+ total = n;
234
238
  return self.setTotal(job, n);
235
239
  },
236
- setResults: function (_results) {
240
+ setResults (_results) {
237
241
  results = _results;
238
- },
239
- isCanceling: function () {
240
- return canceling;
241
242
  }
242
243
  });
243
244
  good = true;
244
245
  } finally {
245
246
  await self.end(job, good, results);
247
+
248
+ // Trigger the completed notification.
249
+ await self.triggerNotification(req, 'completed', {
250
+ count: total,
251
+ dismiss: true
252
+ });
253
+ // Dismiss the progress notification. It will delay 4 seconds
254
+ // because "completed" notification will dismiss in 5 and we want
255
+ // to maintain the feeling of process order for users.
256
+ await self.apos.notification.dismiss(req, notification.noteId, 4000);
246
257
  }
247
258
  }
248
259
  },
260
+ // Job notification messages are passed to `triggerNotifications`
261
+ // through `req.body.messages` via `run` and `runBatch`. The
262
+ // `req.body.messages` object can include `progress` and `completed`
263
+ // properties, which will be used for notifications when the job starts
264
+ // (`progress`) and ends (`completed`). Those messages can include the
265
+ // following interpolation keys:
266
+ // - {{ type }}: The doc type, as passed to the job on req.body.type
267
+ // - {{ count }}: The count of total document IDs in the req.body._ids
268
+ // array
269
+ // No messages are required, but they provide helpful information to
270
+ // end users.
271
+ async triggerNotification(req, stage, options = {}) {
272
+ if (!req.body || !req.body.messages || !req.body.messages[stage]) {
273
+ return {};
274
+ }
275
+
276
+ return self.apos.notification.trigger(req, req.body.messages[stage], {
277
+ interpolate: {
278
+ count: options.count || (req.body._ids && req.body._ids.length),
279
+ type: req.body.type || req.t('apostrophe:document')
280
+ },
281
+ dismiss: options.dismiss,
282
+ job: {
283
+ _id: options.jobId,
284
+ action: options.action,
285
+ ids: options.ids
286
+ },
287
+ icon: req.body.messages.icon || 'database-export-icon',
288
+ type: options.type || 'success',
289
+ return: true
290
+ });
291
+ },
249
292
  // Start tracking a long-running job. Called by routes
250
293
  // that require progress display and/or the ability to take longer
251
294
  // than the server might permit a single HTTP request to last.
252
295
  // *You usually won't call this yourself. The easy way is usually
253
- // to call the `run` or `runNonBatch` methods.*
296
+ // to call the `runBatch` or `run` methods.*
254
297
  //
255
298
  // On success this method returns a `job` object.
256
299
  // You can then invoke the `setTotal`, `success`, `error`,
@@ -263,39 +306,6 @@ module.exports = {
263
306
  // to indicate the success or failure of one "row" or other item
264
307
  // processed, and you *may* call `setTotal(job, n)` to indicate
265
308
  // how many rows to expect for better progress display.
266
- //
267
- // *Canceling and stopping jobs*
268
- //
269
- // If `options.cancel` is passed, the user may cancel (undo) the job.
270
- // If they do `options.cancel` will be invoked with `(job)` and
271
- // it *must undo what was done*. `cancel` must be async and will
272
- // be awaited. If it throws an error, the job will be stopped
273
- // with no further progress in the undo operation.
274
- //
275
- // If `options.stop` is passed, the user may stop (halt) the operation.
276
- // If they do `options.stop` will be invoked with `(job)` and
277
- // it *must stop processing new items*. This function may be
278
- // async and it must not resolve until it is guaranteed that
279
- // *no more operations will run*.
280
- //
281
- // The difference between stop and cancel is the lack of undo with "stop".
282
- // Implement the one that is practical for you. Users like to be able to
283
- // undo things fully, of course.
284
- //
285
- // You should not offer both. If you do, only "Cancel" is presented
286
- // to the user.
287
- //
288
- // *Labeling the progress modal: `options.label`*
289
- //
290
- // `options.label` should be passed as an object with
291
- // a `title` property, to title the progress modal.
292
- //
293
- // In addition, it may have `failed`, `completed` and
294
- // `running` properties to label the progress modal when the job
295
- // is in those states, and `good` and `bad` properties to label
296
- // the count of items that were successful or had errors.
297
- // All of these properties are optional and reasonable
298
- // defaults are supplied.
299
309
  async start(options) {
300
310
  const job = {
301
311
  _id: self.apos.util.generateId(),
@@ -304,21 +314,13 @@ module.exports = {
304
314
  processed: 0,
305
315
  status: 'running',
306
316
  ended: false,
307
- canceling: false,
308
- labels: options.labels || {},
309
- when: new Date(),
310
- canCancel: !!options.cancel,
311
- canStop: !!options.stop
317
+ when: new Date()
312
318
  };
313
319
  const context = {
314
320
  _id: job._id,
315
321
  options: options
316
322
  };
317
- if (options.cancel || options.stop) {
318
- context.interval = setInterval(function () {
319
- self.checkStop(context);
320
- }, 250);
321
- }
323
+
322
324
  await self.db.insertOne(job);
323
325
  return context;
324
326
  },
@@ -330,7 +332,7 @@ module.exports = {
330
332
  //
331
333
  // No promise is returned as this method just updates
332
334
  // the job tracking information in the background.
333
- good(job, n) {
335
+ success(job, n) {
334
336
  n = n === undefined ? 1 : n;
335
337
  self.db.updateOne({ _id: job._id }, {
336
338
  $inc: {
@@ -351,7 +353,7 @@ module.exports = {
351
353
  //
352
354
  // No promise is returned as this method just updates
353
355
  // the job tracking information in the background.
354
- bad(job, n) {
356
+ failure(job, n) {
355
357
  n = n === undefined ? 1 : n;
356
358
  self.db.updateOne({ _id: job._id }, {
357
359
  $inc: {
@@ -407,60 +409,10 @@ module.exports = {
407
409
  async ensureCollection() {
408
410
  self.db = await self.apos.db.collection(self.options.collectionName);
409
411
  },
410
- // Periodically invoked to check whether
411
- // a request to cancel or stop the job has been made.
412
- // If it has we invoke options.cancel or options.stop to
413
- // actually cancel it, preferably the former. This method is invoked
414
- // by an interval timer installed by `self.start`.
415
- // This allows a possibly different apostrophe process
416
- // to request a cancellation by setting the `canceling` property;
417
- // the original process actually running the job
418
- // cancels and then acknowledges this by setting status to `canceled`
419
- // or `stopped` according to which operation is
420
- // actually supported by the job.
421
- async checkStop(context) {
422
- if (context.checkingStop || context.ended) {
423
- return;
424
- }
425
- context.checkingStop = true;
426
- let job;
427
- try {
428
- job = await self.db.findOne({ _id: context._id });
429
- if (!job) {
430
- self.apos.util.error('job never found');
431
- return;
432
- }
433
- if (job.canceling) {
434
- if (job.canCancel) {
435
- return await halt('cancel', 'canceled');
436
- } else if (job.canStop) {
437
- return await halt('stop', 'stopped');
438
- }
439
- }
440
- } catch (err) {
441
- self.apos.util.error(err);
442
- await self.db.updateOne({ _id: context._id }, {
443
- $set: {
444
- ended: true,
445
- canceling: false,
446
- status: 'failed'
447
- }
448
- });
449
- } finally {
450
- context.checkingStop = false;
451
- if (job && job.ended) {
452
- clearInterval(context.interval);
453
- }
454
- }
455
- async function halt(verb, status) {
456
- await context.options[verb](job);
457
- const $set = {
458
- ended: true,
459
- canceling: false,
460
- status: status
461
- };
462
- await self.db.updateOne({ _id: job._id }, { $set: $set });
463
- }
412
+ getBrowserData(req) {
413
+ return {
414
+ action: self.action
415
+ };
464
416
  }
465
417
  };
466
418
  }
@@ -365,21 +365,6 @@ module.exports = {
365
365
  return 1000 * 60 * 60 * (self.options.passwordResetHours || 48);
366
366
  },
367
367
 
368
- // Invoked by passport after an authentication strategy succeeds
369
- // and the user has been logged in. Invokes `loginAfterLogin` on
370
- // any modules that have one and redirects to `req.redirect` or,
371
- // if it is not set, to `/`.
372
-
373
- async afterLogin(req, res) {
374
- try {
375
- await self.emit('after', req);
376
- } catch (e) {
377
- self.apos.util.error(e);
378
- return res.redirect('/');
379
- }
380
- return res.redirect(req.redirect || '/');
381
- },
382
-
383
368
  getBrowserData(req) {
384
369
  return {
385
370
  action: self.action,
@@ -398,7 +383,7 @@ module.exports = {
398
383
  const adminReq = self.apos.task.getReq();
399
384
  const user = await self.apos.user.find(adminReq, {}).relationships(false).limit(1).toObject();
400
385
 
401
- if (!user) {
386
+ if (!user && !self.apos.options.test) {
402
387
  self.apos.util.warnDev('There are no users created for this installation of ApostropheCMS yet.');
403
388
  }
404
389
  },
@@ -37,6 +37,7 @@
37
37
  type="primary"
38
38
  label="apostrophe:login"
39
39
  button-type="submit"
40
+ class="apos-login__submit"
40
41
  :modifiers="['gradient-on-hover', 'block']"
41
42
  @click="submit"
42
43
  />
@@ -301,4 +302,8 @@ export default {
301
302
  margin-left: auto;
302
303
  }
303
304
  }
305
+
306
+ .apos-login__submit ::v-deep .apos-button {
307
+ height: 47px;
308
+ }
304
309
  </style>
@@ -260,7 +260,7 @@ module.exports = {
260
260
  // Intentionally emitted regardless of whether the site is new or not.
261
261
  //
262
262
  // This is the right time to park pages, for instance, because the
263
- // database is guaranteed to be in a sane state, whether because the
263
+ // database is guaranteed to be in a stable state, whether because the
264
264
  // site is new or because migrations ran successfully.
265
265
  await self.emit('after');
266
266
  } finally {