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.
- package/.eslintrc +4 -0
- package/.scratch.md +2 -0
- package/CHANGELOG.md +96 -3
- package/README.md +1 -1
- package/index.js +108 -3
- package/lib/moog-require.js +23 -0
- package/lib/moog.js +1 -0
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +30 -14
- package/modules/@apostrophecms/area/lib/custom-tags/area.js +1 -1
- package/modules/@apostrophecms/area/lib/custom-tags/widget.js +1 -1
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +2 -2
- package/modules/@apostrophecms/asset/index.js +77 -13
- package/modules/@apostrophecms/attachment/index.js +1 -0
- package/modules/@apostrophecms/db/index.js +5 -6
- package/modules/@apostrophecms/doc/index.js +2 -0
- package/modules/@apostrophecms/doc-type/index.js +39 -16
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +13 -1
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +3 -0
- package/modules/@apostrophecms/i18n/i18n/en.json +19 -6
- package/modules/@apostrophecms/i18n/i18n/es.json +382 -0
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +379 -0
- package/modules/@apostrophecms/i18n/i18n/sk.json +380 -0
- package/modules/@apostrophecms/i18n/index.js +10 -1
- package/modules/@apostrophecms/image/index.js +2 -1
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +6 -3
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +2 -1
- package/modules/@apostrophecms/image-widget/index.js +2 -1
- package/modules/@apostrophecms/image-widget/views/widget.html +12 -2
- package/modules/@apostrophecms/job/index.js +164 -212
- package/modules/@apostrophecms/login/index.js +1 -16
- package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +5 -0
- package/modules/@apostrophecms/migration/index.js +1 -1
- package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +151 -61
- package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +6 -2
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +8 -6
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +12 -15
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +6 -0
- package/modules/@apostrophecms/notification/index.js +116 -8
- package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +89 -11
- package/modules/@apostrophecms/notification/ui/apos/components/TheAposNotifications.vue +1 -1
- package/modules/@apostrophecms/page/index.js +37 -30
- package/modules/@apostrophecms/permission/index.js +1 -1
- package/modules/@apostrophecms/permission/ui/apos/components/AposInputRole.vue +4 -2
- package/modules/@apostrophecms/piece-type/index.js +178 -61
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +179 -47
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +1 -3
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +138 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +35 -6
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Classes.js +1 -3
- package/modules/@apostrophecms/schema/index.js +97 -20
- package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +1 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +4 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +11 -160
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +24 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +24 -6
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +0 -4
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +0 -7
- package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +25 -3
- package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +10 -2
- package/modules/@apostrophecms/task/index.js +2 -2
- package/modules/@apostrophecms/template/index.js +63 -36
- package/modules/@apostrophecms/template/lib/custom-tags/component.js +1 -1
- package/modules/@apostrophecms/template/lib/custom-tags/render.js +6 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +5 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +205 -0
- package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +16 -2
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +4 -3
- package/modules/@apostrophecms/util/index.js +2 -2
- package/modules/@apostrophecms/util/ui/src/http.js +12 -8
- package/modules/@apostrophecms/util/ui/src/util.js +15 -0
- package/modules/@apostrophecms/widget-type/index.js +1 -1
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +1 -0
- package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +15 -7
- package/package.json +3 -3
- package/test/extra_node_modules/improve-global/index.js +7 -0
- package/test/extra_node_modules/improve-piece-type/index.js +7 -0
- package/test/improve-overrides.js +30 -0
- package/test/job.js +224 -0
- package/test/modules/@apostrophecms/global/index.js +8 -0
- package/test/modules/fragment-all/views/aux-test.html +7 -0
- package/test/modules/fragment-all/views/fragment.html +5 -0
- package/test/package.json +5 -4
- package/test/pieces.js +34 -0
- package/test/reverse-relationship.js +170 -0
- package/test/templates.js +7 -1
- package/test-lib/test.js +23 -12
- package/test-lib/util.js +33 -0
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
const
|
|
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
|
-
//
|
|
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`
|
|
18
|
-
//
|
|
19
|
-
//
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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 `
|
|
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
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
148
|
+
self.success(job);
|
|
144
149
|
results[id] = result;
|
|
145
150
|
} catch (err) {
|
|
146
|
-
self.
|
|
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 `
|
|
156
|
-
// however unlike `
|
|
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.
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
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
|
-
//
|
|
193
|
-
|
|
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
|
-
|
|
192
|
+
let notification;
|
|
193
|
+
let total;
|
|
194
|
+
|
|
201
195
|
try {
|
|
202
|
-
|
|
196
|
+
job = await self.start(options);
|
|
203
197
|
run();
|
|
204
|
-
|
|
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
|
-
|
|
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
|
-
|
|
228
|
+
success (n) {
|
|
226
229
|
n = n || 1;
|
|
227
|
-
return self.
|
|
230
|
+
return self.success(job, n);
|
|
228
231
|
},
|
|
229
|
-
|
|
232
|
+
failure (n) {
|
|
230
233
|
n = n || 1;
|
|
231
|
-
return self.
|
|
234
|
+
return self.failure(job, n);
|
|
232
235
|
},
|
|
233
|
-
setTotal
|
|
236
|
+
setTotal (n) {
|
|
237
|
+
total = n;
|
|
234
238
|
return self.setTotal(job, n);
|
|
235
239
|
},
|
|
236
|
-
setResults
|
|
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 `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
|
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 {
|