apostrophe 3.6.0 → 3.9.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.
- package/.eslintrc +4 -0
- package/.github/workflows/main.yml +45 -0
- package/CHANGELOG.md +92 -3
- package/README.md +2 -3
- package/index.js +104 -3
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +5 -1
- package/modules/@apostrophecms/area/ui/apos/apps/AposAreas.js +15 -10
- package/modules/@apostrophecms/asset/index.js +105 -15
- package/modules/@apostrophecms/attachment/index.js +1 -4
- 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 +0 -1
- package/modules/@apostrophecms/i18n/i18n/en.json +23 -4
- package/modules/@apostrophecms/i18n/i18n/es.json +1 -2
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +0 -1
- package/modules/@apostrophecms/i18n/i18n/sk.json +3 -4
- package/modules/@apostrophecms/i18n/index.js +36 -6
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +6 -3
- 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 +165 -220
- package/modules/@apostrophecms/login/index.js +0 -15
- 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/AposModalConfirm.vue +8 -6
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +12 -15
- package/modules/@apostrophecms/module/index.js +1 -4
- 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 +84 -52
- package/modules/@apostrophecms/page-type/index.js +5 -1
- package/modules/@apostrophecms/piece-type/index.js +183 -61
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +180 -50
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +1 -3
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +141 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +35 -6
- package/modules/@apostrophecms/schema/index.js +81 -25
- package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +9 -3
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +11 -160
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputPassword.vue +11 -4
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRange.vue +2 -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/AposInputWrapper.vue +0 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposLogo.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposLogoIcon.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposLogoPadless.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +0 -1
- package/modules/@apostrophecms/search/index.js +53 -33
- package/modules/@apostrophecms/task/index.js +7 -3
- package/modules/@apostrophecms/template/index.js +7 -11
- package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +5 -0
- 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/components/AposMinMaxCount.vue +9 -3
- package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +16 -2
- package/modules/@apostrophecms/ui/ui/apos/mixins/AposPublishMixin.js +3 -2
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +4 -3
- 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/mixins/AposWidgetMixin.js +5 -19
- package/package.json +2 -2
- package/test/job.js +224 -0
- package/test/pieces.js +34 -0
- package/test-lib/util.js +32 -0
- package/.circleci/config.yml +0 -94
- package/.scratch.md +0 -2
|
@@ -10,11 +10,20 @@ export default {
|
|
|
10
10
|
install(Vue, options) {
|
|
11
11
|
const i18n = options.i18n;
|
|
12
12
|
|
|
13
|
+
const fallbackLng = [ i18n.defaultLocale ];
|
|
14
|
+
// In case the default locale also has inadequate admin UI phrases
|
|
15
|
+
if (fallbackLng[0] !== 'en') {
|
|
16
|
+
fallbackLng.push('en');
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
i18next.init({
|
|
14
20
|
lng: i18n.locale,
|
|
15
|
-
fallbackLng
|
|
21
|
+
fallbackLng,
|
|
16
22
|
resources: {},
|
|
17
|
-
debug: i18n.debug
|
|
23
|
+
debug: i18n.debug,
|
|
24
|
+
interpolation: {
|
|
25
|
+
escapeValue: false
|
|
26
|
+
}
|
|
18
27
|
});
|
|
19
28
|
|
|
20
29
|
for (const [ ns, phrases ] of Object.entries(i18n.i18n[i18n.locale])) {
|
|
@@ -25,6 +34,11 @@ export default {
|
|
|
25
34
|
i18next.addResourceBundle(i18n.defaultLocale, ns, phrases, true, true);
|
|
26
35
|
}
|
|
27
36
|
}
|
|
37
|
+
if ((i18n.locale !== 'en') && (i18n.defaultLocale !== 'en')) {
|
|
38
|
+
for (const [ ns, phrases ] of Object.entries(i18n.i18n.en)) {
|
|
39
|
+
i18next.addResourceBundle('en', ns, phrases, true, true);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
28
42
|
|
|
29
43
|
// Like standard i18next $t, but also with support
|
|
30
44
|
// for just one object argument with at least a `key`
|
|
@@ -49,9 +49,10 @@ export default {
|
|
|
49
49
|
if ((e.name === 'invalid') && e.body && e.body.data && e.body.data.unpublishedAncestors) {
|
|
50
50
|
if (await apos.confirm({
|
|
51
51
|
heading: 'apostrophe:unpublishedParent',
|
|
52
|
-
description: 'apostrophe:unpublishedParentDescription'
|
|
52
|
+
description: 'apostrophe:unpublishedParentDescription'
|
|
53
|
+
}, {
|
|
53
54
|
interpolate: {
|
|
54
|
-
unpublishedParents:
|
|
55
|
+
unpublishedParents: e.body.data.unpublishedAncestors.map(page => page.title).join(this.$t('apostrophe:listJoiner'))
|
|
55
56
|
}
|
|
56
57
|
})) {
|
|
57
58
|
try {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
.apos-table__header {
|
|
7
7
|
margin-bottom: $spacing-base;
|
|
8
|
-
padding: 12.5px
|
|
8
|
+
padding: 12.5px 15px;
|
|
9
9
|
border-bottom: 1px solid var(--a-base-8);
|
|
10
10
|
color: var(--a-base-3);
|
|
11
11
|
text-align: left;
|
|
@@ -52,12 +52,13 @@ span.apos-table__header-label:hover {
|
|
|
52
52
|
@include apos-transition(all, 0.05s);
|
|
53
53
|
}
|
|
54
54
|
.apos-table__cell {
|
|
55
|
-
padding: 5px;
|
|
55
|
+
padding: 5px 15px;
|
|
56
56
|
border-bottom: 1px solid var(--a-base-10);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
.apos-table__cell--context-menu {
|
|
60
|
-
|
|
60
|
+
padding-right: 0;
|
|
61
|
+
padding-left: 0;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
.apos-table__cell-field--context-menu {
|
|
@@ -257,6 +257,21 @@ export default () => {
|
|
|
257
257
|
return path + '.' + file.extension;
|
|
258
258
|
};
|
|
259
259
|
|
|
260
|
+
// Given an asset path such as `/modules/modulename/images/file.png`, this
|
|
261
|
+
// method will return a URL for it. This is used when frontend JavaScript
|
|
262
|
+
// code needs to access static assets shipped in the `public` subdirectory of
|
|
263
|
+
// individual modules. Currently `path` must begin with `/modules/` followed
|
|
264
|
+
// by a module name; other namespaces may exist later. The remainder of the
|
|
265
|
+
// path, such as `/images/file.png` in the above example, must currespond
|
|
266
|
+
// to a file that exists in the `public` subdirectory of the named module.
|
|
267
|
+
//
|
|
268
|
+
// Asset paths of this type are also automatically supported by CSS and
|
|
269
|
+
// SCSS files in the project when using `url()`.
|
|
270
|
+
|
|
271
|
+
apos.util.assetUrl = function(path) {
|
|
272
|
+
return apos.assetBaseUrl + path;
|
|
273
|
+
};
|
|
274
|
+
|
|
260
275
|
// Returns true if the uri references the same site (same host and port) as the
|
|
261
276
|
// current page. Cross-browser implementation, valid at least back to IE11.
|
|
262
277
|
// Regarding port numbers, this will match as long as the URIs are consistent
|
|
@@ -156,7 +156,7 @@ module.exports = {
|
|
|
156
156
|
self.schema = self.apos.schema.compose({
|
|
157
157
|
addFields: self.apos.schema.fieldsToArray(`Module ${self.__meta.name}`, self.fields),
|
|
158
158
|
arrangeFields: self.apos.schema.groupsToArray(self.fieldsGroups)
|
|
159
|
-
});
|
|
159
|
+
}, self);
|
|
160
160
|
const forbiddenFields = [
|
|
161
161
|
'_id',
|
|
162
162
|
'type'
|
|
@@ -22,16 +22,11 @@ export default {
|
|
|
22
22
|
},
|
|
23
23
|
data() {
|
|
24
24
|
return {
|
|
25
|
-
rendered: '...'
|
|
26
|
-
playerOpts: null
|
|
25
|
+
rendered: '...'
|
|
27
26
|
};
|
|
28
27
|
},
|
|
29
28
|
mounted() {
|
|
30
29
|
this.renderContent();
|
|
31
|
-
this.playerOpts = apos.util.widgetPlayers[this.type] || null;
|
|
32
|
-
},
|
|
33
|
-
updated () {
|
|
34
|
-
this.runPlayer();
|
|
35
30
|
},
|
|
36
31
|
computed: {
|
|
37
32
|
moduleOptions() {
|
|
@@ -40,6 +35,7 @@ export default {
|
|
|
40
35
|
},
|
|
41
36
|
methods: {
|
|
42
37
|
async renderContent() {
|
|
38
|
+
apos.bus.$emit('widget-rendering');
|
|
43
39
|
const parameters = {
|
|
44
40
|
_docId: this.docId,
|
|
45
41
|
widget: this.value,
|
|
@@ -49,7 +45,6 @@ export default {
|
|
|
49
45
|
try {
|
|
50
46
|
if (this.rendering && (isEqual(this.rendering.parameters, parameters))) {
|
|
51
47
|
this.rendered = this.rendering.html;
|
|
52
|
-
this.runPlayer();
|
|
53
48
|
} else {
|
|
54
49
|
this.rendered = '...';
|
|
55
50
|
this.rendered = await apos.http.post(`${apos.area.action}/render-widget?aposEdit=1&aposMode=draft`, {
|
|
@@ -57,9 +52,9 @@ export default {
|
|
|
57
52
|
body: parameters
|
|
58
53
|
});
|
|
59
54
|
}
|
|
60
|
-
// Wait for reactivity to
|
|
61
|
-
//
|
|
62
|
-
//
|
|
55
|
+
// Wait for reactivity to render v-html so that markup is
|
|
56
|
+
// in the DOM before hinting that it might be time to prepare
|
|
57
|
+
// sub-area editors and run players
|
|
63
58
|
setTimeout(function() {
|
|
64
59
|
apos.bus.$emit('widget-rendered');
|
|
65
60
|
}, 0);
|
|
@@ -68,15 +63,6 @@ export default {
|
|
|
68
63
|
console.error('Unable to render widget. Possibly the schema has been changed and the existing widget does not pass validation.', e);
|
|
69
64
|
}
|
|
70
65
|
},
|
|
71
|
-
runPlayer() {
|
|
72
|
-
if (!this.playerOpts) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
const el = this.$el.querySelector(this.playerOpts.selector);
|
|
76
|
-
if (el && this.playerOpts.player) {
|
|
77
|
-
this.playerOpts.player(el);
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
66
|
clicked(e) {
|
|
81
67
|
// If you do not want a particular click to swap to the edit view
|
|
82
68
|
// for your widget, you should make sure it does not bubble
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apostrophe",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.0",
|
|
4
4
|
"description": "The Apostrophe Content Management System.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"pretest": "npm run lint
|
|
7
|
+
"pretest": "npm run lint",
|
|
8
8
|
"test": "nyc --reporter=html mocha -t 10000",
|
|
9
9
|
"lint": "eslint . && node scripts/lint-i18n"
|
|
10
10
|
},
|
package/test/job.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
const t = require('../test-lib/test.js');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
const Promise = require('bluebird');
|
|
4
|
+
let apos;
|
|
5
|
+
|
|
6
|
+
describe('Job module', function() {
|
|
7
|
+
|
|
8
|
+
this.timeout(t.timeout);
|
|
9
|
+
|
|
10
|
+
after(async function() {
|
|
11
|
+
return t.destroy(apos);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
let jobModule;
|
|
15
|
+
|
|
16
|
+
it('should be a property of the apos object', async function() {
|
|
17
|
+
this.timeout(t.timeout);
|
|
18
|
+
this.slow(2000);
|
|
19
|
+
|
|
20
|
+
apos = await t.create({
|
|
21
|
+
root: module,
|
|
22
|
+
modules: {
|
|
23
|
+
article: {
|
|
24
|
+
extend: '@apostrophecms/piece-type'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
jobModule = apos.modules['@apostrophecms/job'];
|
|
29
|
+
assert(apos.modules['@apostrophecms/job']);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('has a related database collection', async function () {
|
|
33
|
+
assert(jobModule.db);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
let jobOne;
|
|
37
|
+
|
|
38
|
+
it('should create a new job', async function () {
|
|
39
|
+
jobOne = await jobModule.start({});
|
|
40
|
+
|
|
41
|
+
assert(jobOne._id);
|
|
42
|
+
|
|
43
|
+
const found = await jobModule.db.findOne({ _id: jobOne._id });
|
|
44
|
+
|
|
45
|
+
assert(found);
|
|
46
|
+
assert(found.status === 'running');
|
|
47
|
+
assert(found.ended === false);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should end a job and mark it as successful', async function () {
|
|
51
|
+
const result = await jobModule.end(jobOne, 'success', { testing: 'testing' });
|
|
52
|
+
|
|
53
|
+
assert(result.result.nModified === 1);
|
|
54
|
+
|
|
55
|
+
const found = await jobModule.db.findOne({ _id: jobOne._id });
|
|
56
|
+
|
|
57
|
+
assert(found);
|
|
58
|
+
assert(found.status === 'completed');
|
|
59
|
+
assert(found.ended === true);
|
|
60
|
+
});
|
|
61
|
+
let jar;
|
|
62
|
+
it('should get admin jar', async () => {
|
|
63
|
+
await t.createAdmin(apos);
|
|
64
|
+
|
|
65
|
+
jar = await t.getUserJar(apos);
|
|
66
|
+
|
|
67
|
+
assert(jar);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should access a job via REST API GET request', async function () {
|
|
71
|
+
const job = await apos.http.get(`/api/v1/@apostrophecms/job/${jobOne._id}`, {
|
|
72
|
+
jar
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
assert(job._id === jobOne._id);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
let articleIds;
|
|
79
|
+
|
|
80
|
+
it('can insert many test articles', async function () {
|
|
81
|
+
const req = apos.task.getReq();
|
|
82
|
+
|
|
83
|
+
const promises = [];
|
|
84
|
+
|
|
85
|
+
for (let i = 1; i <= 500; i++) {
|
|
86
|
+
promises.push(insert(req, apos.modules.article, 'article', {}, i));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const inserted = await Promise.all(promises);
|
|
90
|
+
articleIds = inserted.map(doc => doc._id);
|
|
91
|
+
|
|
92
|
+
assert(inserted.length === 500);
|
|
93
|
+
assert(!!inserted[0]._id);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
let jobTwo;
|
|
97
|
+
it('can run a batch job', async function () {
|
|
98
|
+
const req = apos.task.getReq();
|
|
99
|
+
|
|
100
|
+
jobTwo = await jobModule.runBatch(
|
|
101
|
+
req,
|
|
102
|
+
articleIds,
|
|
103
|
+
async function(req, id) {
|
|
104
|
+
await apos.doc.db.updateOne({
|
|
105
|
+
_id: id
|
|
106
|
+
}, {
|
|
107
|
+
$set: {
|
|
108
|
+
checked: true
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
assert(!!jobTwo.jobId);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('can follow the second job as it works', async function () {
|
|
118
|
+
const { completed } = await pollJob({
|
|
119
|
+
route: `${jobModule.action}/${jobTwo.jobId}`
|
|
120
|
+
}, {
|
|
121
|
+
jar
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
assert(completed === articleIds.length);
|
|
125
|
+
const index = Math.floor(Math.random() * (articleIds.length - 1));
|
|
126
|
+
|
|
127
|
+
const article = await apos.http.get(`/api/v1/article/${articleIds[index]}`, {
|
|
128
|
+
jar
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
assert(article.checked === true);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const logged = [];
|
|
135
|
+
|
|
136
|
+
let jobThree;
|
|
137
|
+
|
|
138
|
+
it('can run a generic job', async function () {
|
|
139
|
+
const req = apos.task.getReq();
|
|
140
|
+
|
|
141
|
+
jobThree = await jobModule.run(
|
|
142
|
+
req,
|
|
143
|
+
async function(req, reporters) {
|
|
144
|
+
let count = 1;
|
|
145
|
+
reporters.setTotal(articleIds.length);
|
|
146
|
+
|
|
147
|
+
for (const id of articleIds) {
|
|
148
|
+
await Promise.delay(3);
|
|
149
|
+
logged.push(id);
|
|
150
|
+
if (count % 2) {
|
|
151
|
+
reporters.success();
|
|
152
|
+
} else {
|
|
153
|
+
reporters.failure();
|
|
154
|
+
}
|
|
155
|
+
count++;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
assert(!!jobThree.jobId);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('can follow the third job as it works', async function () {
|
|
164
|
+
const route = `${jobModule.action}/${jobThree.jobId}`;
|
|
165
|
+
const { total } = await apos.http.get(route, { jar });
|
|
166
|
+
// Tests setTotal()
|
|
167
|
+
assert(total === articleIds.length);
|
|
168
|
+
|
|
169
|
+
const {
|
|
170
|
+
completed,
|
|
171
|
+
good,
|
|
172
|
+
bad
|
|
173
|
+
} = await pollJob({
|
|
174
|
+
route
|
|
175
|
+
}, {
|
|
176
|
+
jar
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
assert(completed === articleIds.length);
|
|
180
|
+
// Tests success()
|
|
181
|
+
assert(good === (articleIds.length / 2));
|
|
182
|
+
// Tests failure()
|
|
183
|
+
assert(bad === (articleIds.length / 2));
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
function padInteger (i, places) {
|
|
188
|
+
let s = i + '';
|
|
189
|
+
while (s.length < places) {
|
|
190
|
+
s = '0' + s;
|
|
191
|
+
}
|
|
192
|
+
return s;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function insert (req, pieceModule, title, data, i) {
|
|
196
|
+
const docData = Object.assign(pieceModule.newInstance(), {
|
|
197
|
+
title: `${title} #${padInteger(i, 5)}`,
|
|
198
|
+
slug: `${title}-${padInteger(i, 5)}`,
|
|
199
|
+
...data
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
return pieceModule.insert(req, docData);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
async function pollJob(job, { jar }) {
|
|
206
|
+
const {
|
|
207
|
+
processed,
|
|
208
|
+
total,
|
|
209
|
+
good,
|
|
210
|
+
bad
|
|
211
|
+
} = await apos.http.get(job.route, { jar });
|
|
212
|
+
|
|
213
|
+
if (processed < total) {
|
|
214
|
+
Promise.delay(100);
|
|
215
|
+
|
|
216
|
+
return await pollJob(job, { jar });
|
|
217
|
+
} else {
|
|
218
|
+
return {
|
|
219
|
+
completed: processed,
|
|
220
|
+
good,
|
|
221
|
+
bad
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
package/test/pieces.js
CHANGED
|
@@ -768,6 +768,23 @@ describe('Pieces', function() {
|
|
|
768
768
|
relatedArticleId = response._articles[0]._id;
|
|
769
769
|
});
|
|
770
770
|
|
|
771
|
+
it('can GET a single product using projections', async () => {
|
|
772
|
+
const response = await apos.http.get(`/api/v1/product/${relatedProductId}`, {
|
|
773
|
+
qs: {
|
|
774
|
+
project: {
|
|
775
|
+
_id: 1,
|
|
776
|
+
title: 1
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
const keys = Object.keys(response);
|
|
782
|
+
|
|
783
|
+
assert(response);
|
|
784
|
+
assert(keys.length === 2);
|
|
785
|
+
assert(keys.every((key) => [ '_id', 'title' ].includes(key)));
|
|
786
|
+
});
|
|
787
|
+
|
|
771
788
|
it('can GET a single article with reverse relationships', async () => {
|
|
772
789
|
const response = await apos.http.get(`/api/v1/article/${relatedArticleId}`);
|
|
773
790
|
assert(response);
|
|
@@ -1246,4 +1263,21 @@ describe('Pieces', function() {
|
|
|
1246
1263
|
assert(fs.readFileSync(path.join(__dirname, 'public', resume.attachment._url), 'utf8') === fs.readFileSync(path.join(__dirname, '/public/static-test.txt'), 'utf8'));
|
|
1247
1264
|
});
|
|
1248
1265
|
|
|
1266
|
+
it('should convert a piece keeping only the present fields', async () => {
|
|
1267
|
+
const req = apos.task.getReq();
|
|
1268
|
+
|
|
1269
|
+
const inputPiece = {
|
|
1270
|
+
title: 'new product name'
|
|
1271
|
+
};
|
|
1272
|
+
|
|
1273
|
+
const existingPiece = {
|
|
1274
|
+
color: 'red'
|
|
1275
|
+
};
|
|
1276
|
+
|
|
1277
|
+
await apos.modules.product.convert(req, inputPiece, existingPiece, { presentFieldsOnly: true });
|
|
1278
|
+
|
|
1279
|
+
assert(Object.keys(existingPiece).length === 2);
|
|
1280
|
+
assert(existingPiece.title === 'new product name');
|
|
1281
|
+
assert(existingPiece.color === 'red');
|
|
1282
|
+
});
|
|
1249
1283
|
});
|
package/test-lib/util.js
CHANGED
|
@@ -57,8 +57,40 @@ async function create(options) {
|
|
|
57
57
|
return require('../index.js')(config);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// Create an admin user. By default the username and password are both 'admin'
|
|
61
|
+
async function createAdmin(apos, { username, password } = {}) {
|
|
62
|
+
const user = apos.user.newInstance();
|
|
63
|
+
const name = username || 'admin';
|
|
64
|
+
|
|
65
|
+
user.title = name;
|
|
66
|
+
user.username = name;
|
|
67
|
+
user.password = password || 'admin';
|
|
68
|
+
user.email = `${name}@admin.io`;
|
|
69
|
+
user.role = 'admin';
|
|
70
|
+
|
|
71
|
+
return await apos.user.insert(apos.task.getReq(), user);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function getUserJar(apos, { username, password } = {}) {
|
|
75
|
+
const jar = apos.http.jar();
|
|
76
|
+
|
|
77
|
+
// Log in
|
|
78
|
+
await apos.http.post('/api/v1/@apostrophecms/login/login', {
|
|
79
|
+
body: {
|
|
80
|
+
username: username || 'admin',
|
|
81
|
+
password: password || 'admin',
|
|
82
|
+
session: true
|
|
83
|
+
},
|
|
84
|
+
jar
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return jar;
|
|
88
|
+
}
|
|
89
|
+
|
|
60
90
|
module.exports = {
|
|
61
91
|
destroy,
|
|
62
92
|
create,
|
|
93
|
+
createAdmin,
|
|
94
|
+
getUserJar,
|
|
63
95
|
timeout: (process.env.TEST_TIMEOUT && parseInt(process.env.TEST_TIMEOUT)) || 20000
|
|
64
96
|
};
|
package/.circleci/config.yml
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
version: 2
|
|
2
|
-
jobs:
|
|
3
|
-
build-node14-mongo5:
|
|
4
|
-
docker:
|
|
5
|
-
- image: circleci/node:14-browsers
|
|
6
|
-
- image: mongo:5.0
|
|
7
|
-
steps:
|
|
8
|
-
- checkout
|
|
9
|
-
- run:
|
|
10
|
-
name: update-npm
|
|
11
|
-
command: 'sudo npm install -g npm@7'
|
|
12
|
-
- restore_cache:
|
|
13
|
-
key: dependency-cache-{{ checksum "package.json" }}
|
|
14
|
-
- run:
|
|
15
|
-
name: install-npm-wee
|
|
16
|
-
command: npm install
|
|
17
|
-
- save_cache:
|
|
18
|
-
key: dependency-cache-{{ checksum "package.json" }}
|
|
19
|
-
paths:
|
|
20
|
-
- ./node_modules
|
|
21
|
-
- run:
|
|
22
|
-
name: test
|
|
23
|
-
command: npm test
|
|
24
|
-
build-node14-mongo44:
|
|
25
|
-
docker:
|
|
26
|
-
- image: circleci/node:14-browsers
|
|
27
|
-
- image: mongo:4.4
|
|
28
|
-
steps:
|
|
29
|
-
- checkout
|
|
30
|
-
- run:
|
|
31
|
-
name: update-npm
|
|
32
|
-
command: 'sudo npm install -g npm@7'
|
|
33
|
-
- restore_cache:
|
|
34
|
-
key: dependency-cache-{{ checksum "package.json" }}
|
|
35
|
-
- run:
|
|
36
|
-
name: install-npm-wee
|
|
37
|
-
command: npm install
|
|
38
|
-
- save_cache:
|
|
39
|
-
key: dependency-cache-{{ checksum "package.json" }}
|
|
40
|
-
paths:
|
|
41
|
-
- ./node_modules
|
|
42
|
-
- run:
|
|
43
|
-
name: test
|
|
44
|
-
command: npm test
|
|
45
|
-
build-node14-mongo42:
|
|
46
|
-
docker:
|
|
47
|
-
- image: circleci/node:14-browsers
|
|
48
|
-
- image: mongo:4.2
|
|
49
|
-
steps:
|
|
50
|
-
- checkout
|
|
51
|
-
- run:
|
|
52
|
-
name: update-npm
|
|
53
|
-
command: 'sudo npm install -g npm@7'
|
|
54
|
-
- restore_cache:
|
|
55
|
-
key: dependency-cache-{{ checksum "package.json" }}
|
|
56
|
-
- run:
|
|
57
|
-
name: install-npm-wee
|
|
58
|
-
command: npm install
|
|
59
|
-
- save_cache:
|
|
60
|
-
key: dependency-cache-{{ checksum "package.json" }}
|
|
61
|
-
paths:
|
|
62
|
-
- ./node_modules
|
|
63
|
-
- run:
|
|
64
|
-
name: test
|
|
65
|
-
command: npm test
|
|
66
|
-
build-node12:
|
|
67
|
-
docker:
|
|
68
|
-
- image: circleci/node:12-browsers
|
|
69
|
-
- image: mongo:3.6.11
|
|
70
|
-
steps:
|
|
71
|
-
- checkout
|
|
72
|
-
- run:
|
|
73
|
-
name: update-npm
|
|
74
|
-
command: "sudo npm install -g npm"
|
|
75
|
-
- restore_cache:
|
|
76
|
-
key: dependency-cache-{{ .Branch }}-{{ checksum "package-lock.json" }}
|
|
77
|
-
- run:
|
|
78
|
-
name: install-npm-wee
|
|
79
|
-
command: npm install
|
|
80
|
-
- save_cache:
|
|
81
|
-
key: dependency-cache-{{ .Branch }}-{{ checksum "package-lock.json" }}
|
|
82
|
-
paths:
|
|
83
|
-
- ./node_modules
|
|
84
|
-
- run:
|
|
85
|
-
name: test
|
|
86
|
-
command: npm test
|
|
87
|
-
workflows:
|
|
88
|
-
version: 2
|
|
89
|
-
build:
|
|
90
|
-
jobs:
|
|
91
|
-
- build-node14-mongo5
|
|
92
|
-
- build-node14-mongo44
|
|
93
|
-
- build-node14-mongo42
|
|
94
|
-
- build-node12
|
package/.scratch.md
DELETED