@raketa-cloud/admin 1.9.3 → 1.9.5
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/dist/application.css +3 -7
- package/package.json +6 -1
- package/src/HonoAdapter.js +32 -6
- package/src/cloud/RaketaClient.js +11 -0
- package/src/cloud/Resources.js +76 -22
- package/src/cloud/documents/contentTasksDocuments.js +1 -1
- package/src/cloud/documents/resourcesDocuments.js +9 -15
- package/src/handlers/resources/TranslationsHandler.js +51 -37
- package/src/routeCategoriesModule.js +1 -1
- package/src/tests/HonoAdapter.coverage.test.js +31 -4
- package/src/tests/RaketaClient.test.js +40 -0
- package/src/tests/handlers/content_tasks/ContentTasksHandler.test/index.test.js +16 -12
- package/src/tests/handlers/resources/TranslationsHandler.test/create.test.js +127 -71
- package/src/tests/handlers/resources/TranslationsHandler.test/edge_cases.test.js +51 -8
- package/src/tests/routeCategoriesModule.test.js +1 -1
- package/src/tests/support/factory.js +12 -10
- package/src/views/_auth/login.ejs +11 -31
- package/src/views/_shared/_content_tasks.ejs +25 -11
- package/src/views/layouts/header.ejs +4 -5
- package/src/views/resources/show.ejs +1 -1
- package/src/views/resources/translations/create_form.ejs +19 -3
- package/src/views/resources/translations/index.ejs +3 -7
|
@@ -68,6 +68,72 @@ describe("Resource Translations Handler", () => {
|
|
|
68
68
|
});
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
test("create fails with locale errors segmented by locale", async () => {
|
|
72
|
+
const resource = Factory.build("resource", {
|
|
73
|
+
translations: {},
|
|
74
|
+
categories: [{ id: "cat1" }],
|
|
75
|
+
});
|
|
76
|
+
const params = {
|
|
77
|
+
languages: ["fr", "es"],
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const ctx = buildCtx(params, resource);
|
|
81
|
+
|
|
82
|
+
jest.spyOn(ctx.client.resources, "find").mockImplementation(() => resource);
|
|
83
|
+
|
|
84
|
+
const localeErrors = [
|
|
85
|
+
{
|
|
86
|
+
locale: "fr",
|
|
87
|
+
errors: [{ key: "title", message: "cannot be blank" }],
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
locale: "es",
|
|
91
|
+
errors: [{ key: "slug", message: "already taken" }],
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
jest
|
|
96
|
+
.spyOn(ctx.client.resources, "multiTranslate")
|
|
97
|
+
.mockImplementation(() => ({
|
|
98
|
+
resources: null,
|
|
99
|
+
errors: null,
|
|
100
|
+
localeErrors,
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
const result = await TranslationsHandler.create(ctx);
|
|
104
|
+
|
|
105
|
+
expect(result).toEqual({
|
|
106
|
+
assigns: {
|
|
107
|
+
resource: {
|
|
108
|
+
title: resource.title,
|
|
109
|
+
tags: [],
|
|
110
|
+
visibility: "private",
|
|
111
|
+
slug: resource.slug,
|
|
112
|
+
settings: resource.settings,
|
|
113
|
+
widgets: resource.widgets,
|
|
114
|
+
categories: ["cat1"],
|
|
115
|
+
translations: {},
|
|
116
|
+
languages: ["fr", "es"],
|
|
117
|
+
},
|
|
118
|
+
blueprint: resource,
|
|
119
|
+
targetLocale: "en",
|
|
120
|
+
errors: {
|
|
121
|
+
resource: {},
|
|
122
|
+
localeErrors: [
|
|
123
|
+
{
|
|
124
|
+
locale: "fr",
|
|
125
|
+
errors: { title: "cannot be blank" },
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
locale: "es",
|
|
129
|
+
errors: { slug: "already taken" },
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
71
137
|
test("create fails with translation errors", async () => {
|
|
72
138
|
const resource = Factory.build("resource", {
|
|
73
139
|
translations: {},
|
|
@@ -84,14 +150,12 @@ describe("Resource Translations Handler", () => {
|
|
|
84
150
|
.mockImplementation(() => resource);
|
|
85
151
|
|
|
86
152
|
const translationErrors = [{ key: "title", message: "cannot be blank" }];
|
|
87
|
-
const
|
|
88
|
-
.spyOn(ctx.client.resources, "
|
|
89
|
-
.mockImplementation((
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
})),
|
|
94
|
-
);
|
|
153
|
+
const mockMultiTranslate = jest
|
|
154
|
+
.spyOn(ctx.client.resources, "multiTranslate")
|
|
155
|
+
.mockImplementation(() => ({
|
|
156
|
+
resources: null,
|
|
157
|
+
errors: translationErrors,
|
|
158
|
+
}));
|
|
95
159
|
|
|
96
160
|
const mockFormatErrors = jest
|
|
97
161
|
.spyOn(ctx.client.resources, "formatErrors")
|
|
@@ -100,7 +164,7 @@ describe("Resource Translations Handler", () => {
|
|
|
100
164
|
const result = await TranslationsHandler.create(ctx);
|
|
101
165
|
|
|
102
166
|
expect(mockFind).toHaveBeenCalledWith(resource.id, "en", "article");
|
|
103
|
-
expect(
|
|
167
|
+
expect(mockMultiTranslate).toHaveBeenCalled();
|
|
104
168
|
expect(mockFormatErrors).toHaveBeenCalledWith(translationErrors);
|
|
105
169
|
|
|
106
170
|
expect(result).toEqual({
|
|
@@ -117,7 +181,7 @@ describe("Resource Translations Handler", () => {
|
|
|
117
181
|
languages: ["fr"],
|
|
118
182
|
},
|
|
119
183
|
blueprint: resource,
|
|
120
|
-
targetLocale: "
|
|
184
|
+
targetLocale: "en",
|
|
121
185
|
errors: { resource: { title: "cannot be blank" } },
|
|
122
186
|
},
|
|
123
187
|
});
|
|
@@ -139,14 +203,14 @@ describe("Resource Translations Handler", () => {
|
|
|
139
203
|
.spyOn(ctx.client.resources, "find")
|
|
140
204
|
.mockImplementation(() => resource);
|
|
141
205
|
|
|
142
|
-
const
|
|
143
|
-
.spyOn(ctx.client.resources, "
|
|
144
|
-
.mockImplementation((parentId, locales) =>
|
|
145
|
-
locales.map((locale) => ({
|
|
146
|
-
|
|
147
|
-
errors: null,
|
|
206
|
+
const mockMultiTranslate = jest
|
|
207
|
+
.spyOn(ctx.client.resources, "multiTranslate")
|
|
208
|
+
.mockImplementation((parentId, locales) => ({
|
|
209
|
+
resources: locales.map((locale) => ({
|
|
210
|
+
id: `translated-resource-${locale}`,
|
|
148
211
|
})),
|
|
149
|
-
|
|
212
|
+
errors: null,
|
|
213
|
+
}));
|
|
150
214
|
|
|
151
215
|
const mockCreateRevisionBatch = jest
|
|
152
216
|
.spyOn(ctx.client.resourceRevisions, "createBatch")
|
|
@@ -161,8 +225,8 @@ describe("Resource Translations Handler", () => {
|
|
|
161
225
|
|
|
162
226
|
expect(mockFind).toHaveBeenCalledWith(resource.id, "en", "article");
|
|
163
227
|
|
|
164
|
-
expect(
|
|
165
|
-
expect(
|
|
228
|
+
expect(mockMultiTranslate).toHaveBeenCalledTimes(1);
|
|
229
|
+
expect(mockMultiTranslate).toHaveBeenCalledWith(
|
|
166
230
|
resource.id,
|
|
167
231
|
["fr", "es"],
|
|
168
232
|
expect.objectContaining({
|
|
@@ -173,6 +237,7 @@ describe("Resource Translations Handler", () => {
|
|
|
173
237
|
}),
|
|
174
238
|
"article",
|
|
175
239
|
undefined,
|
|
240
|
+
"en",
|
|
176
241
|
);
|
|
177
242
|
|
|
178
243
|
expect(mockCreateRevisionBatch).toHaveBeenCalledTimes(1);
|
|
@@ -210,22 +275,12 @@ describe("Resource Translations Handler", () => {
|
|
|
210
275
|
.spyOn(ctx.client.resources, "find")
|
|
211
276
|
.mockImplementation(() => resource);
|
|
212
277
|
|
|
213
|
-
const
|
|
214
|
-
.spyOn(ctx.client.resources, "
|
|
215
|
-
.mockImplementation(() =>
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
slug: "resource-slug",
|
|
220
|
-
visibility: "public",
|
|
221
|
-
},
|
|
222
|
-
errors: null,
|
|
223
|
-
},
|
|
224
|
-
{
|
|
225
|
-
resource: null,
|
|
226
|
-
errors: [{ key: "title", message: "invalid for spanish" }],
|
|
227
|
-
},
|
|
228
|
-
]);
|
|
278
|
+
const mockMultiTranslate = jest
|
|
279
|
+
.spyOn(ctx.client.resources, "multiTranslate")
|
|
280
|
+
.mockImplementation(() => ({
|
|
281
|
+
resources: null,
|
|
282
|
+
errors: [{ key: "title", message: "invalid for spanish" }],
|
|
283
|
+
}));
|
|
229
284
|
|
|
230
285
|
jest
|
|
231
286
|
.spyOn(ctx.client.resources, "formatErrors")
|
|
@@ -235,7 +290,7 @@ describe("Resource Translations Handler", () => {
|
|
|
235
290
|
|
|
236
291
|
expect(mockFind).toHaveBeenCalledWith(resource.id, "en", "article");
|
|
237
292
|
|
|
238
|
-
expect(
|
|
293
|
+
expect(mockMultiTranslate).toHaveBeenCalledWith(
|
|
239
294
|
resource.id,
|
|
240
295
|
["fr", "es"],
|
|
241
296
|
expect.objectContaining({
|
|
@@ -246,6 +301,7 @@ describe("Resource Translations Handler", () => {
|
|
|
246
301
|
}),
|
|
247
302
|
"article",
|
|
248
303
|
undefined,
|
|
304
|
+
"en",
|
|
249
305
|
);
|
|
250
306
|
|
|
251
307
|
expect(result).toEqual({
|
|
@@ -258,17 +314,11 @@ describe("Resource Translations Handler", () => {
|
|
|
258
314
|
settings: resource.settings,
|
|
259
315
|
widgets: resource.widgets,
|
|
260
316
|
categories: ["some-category-uno"],
|
|
261
|
-
translations: {
|
|
262
|
-
fr: {
|
|
263
|
-
id: "translated-resource-fr",
|
|
264
|
-
slug: resource.slug,
|
|
265
|
-
visibility: resource.visibility,
|
|
266
|
-
},
|
|
267
|
-
},
|
|
317
|
+
translations: {},
|
|
268
318
|
languages: ["fr", "es"],
|
|
269
319
|
},
|
|
270
320
|
blueprint: resource,
|
|
271
|
-
targetLocale: "
|
|
321
|
+
targetLocale: "en",
|
|
272
322
|
errors: { resource: { title: "invalid for spanish" } },
|
|
273
323
|
},
|
|
274
324
|
});
|
|
@@ -288,17 +338,17 @@ describe("Resource Translations Handler", () => {
|
|
|
288
338
|
jest.spyOn(ctx.client.resources, "find").mockImplementation(() => resource);
|
|
289
339
|
|
|
290
340
|
jest
|
|
291
|
-
.spyOn(ctx.client.resources, "
|
|
292
|
-
.mockImplementation(() =>
|
|
293
|
-
|
|
294
|
-
|
|
341
|
+
.spyOn(ctx.client.resources, "multiTranslate")
|
|
342
|
+
.mockImplementation(() => ({
|
|
343
|
+
resources: [
|
|
344
|
+
{
|
|
295
345
|
id: "translated-resource-fr",
|
|
296
346
|
slug: "resource-slug",
|
|
297
347
|
visibility: "public",
|
|
298
348
|
},
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
349
|
+
],
|
|
350
|
+
errors: null,
|
|
351
|
+
}));
|
|
302
352
|
|
|
303
353
|
const revisionErrors = [{ key: "base", message: "something went wrong" }];
|
|
304
354
|
const mockCreateRevisionBatch = jest
|
|
@@ -365,12 +415,14 @@ describe("Resource Translations Handler", () => {
|
|
|
365
415
|
const mockQuery = jest
|
|
366
416
|
.spyOn(ctx.client.resources, "query")
|
|
367
417
|
.mockImplementation(() => ({
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
418
|
+
multiTranslateResource: {
|
|
419
|
+
resources: [
|
|
420
|
+
{
|
|
421
|
+
id: "translated-resource-fr",
|
|
422
|
+
slug: "resource-slug",
|
|
423
|
+
visibility: "public",
|
|
424
|
+
},
|
|
425
|
+
],
|
|
374
426
|
errors: null,
|
|
375
427
|
},
|
|
376
428
|
}));
|
|
@@ -378,23 +430,27 @@ describe("Resource Translations Handler", () => {
|
|
|
378
430
|
const result = await TranslationsHandler.create(ctx);
|
|
379
431
|
|
|
380
432
|
expect(mockQuery).toHaveBeenCalledWith(expect.any(String), {
|
|
381
|
-
|
|
382
|
-
locale: "fr",
|
|
383
|
-
resource: {
|
|
384
|
-
categories: ["cat1"],
|
|
385
|
-
resourceType: "article",
|
|
386
|
-
settings:
|
|
387
|
-
'{"layout":"default","meta":{"title":"Open graph","description":"Open graph???","image":{}}}',
|
|
388
|
-
slug: "resource-slug",
|
|
389
|
-
tags: [],
|
|
390
|
-
title: "Resource Title",
|
|
391
|
-
visibility: "private",
|
|
392
|
-
widgets: "[]",
|
|
393
|
-
},
|
|
433
|
+
input: {
|
|
394
434
|
sourceResourceId: "2",
|
|
435
|
+
translations: [
|
|
436
|
+
{
|
|
437
|
+
locale: "fr",
|
|
438
|
+
resource: {
|
|
439
|
+
categories: ["cat1"],
|
|
440
|
+
resourceType: "article",
|
|
441
|
+
settings:
|
|
442
|
+
'{"layout":"default","meta":{"title":"Open graph","description":"Open graph???","image":{}}}',
|
|
443
|
+
slug: "resource-slug",
|
|
444
|
+
tags: [],
|
|
445
|
+
title: "Resource Title",
|
|
446
|
+
visibility: "private",
|
|
447
|
+
widgets: "[]",
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
],
|
|
395
451
|
workflow: {
|
|
396
452
|
previewUrl: expect.stringMatching(
|
|
397
|
-
/^https:\/\/example\.com\/
|
|
453
|
+
/^https:\/\/example\.com\/en\/magic-preview\/articles\/[A-Za-z0-9_-]+$/,
|
|
398
454
|
),
|
|
399
455
|
type: "LOKALISE_TT_AI",
|
|
400
456
|
},
|
|
@@ -114,21 +114,25 @@ describe("Resource Translations Handler create", () => {
|
|
|
114
114
|
});
|
|
115
115
|
|
|
116
116
|
jest.spyOn(ctx.client.resources, "find").mockImplementation(() => resource);
|
|
117
|
-
const
|
|
118
|
-
.spyOn(ctx.client.resources, "
|
|
119
|
-
.mockImplementation(() =>
|
|
117
|
+
const mockMultiTranslate = jest
|
|
118
|
+
.spyOn(ctx.client.resources, "multiTranslate")
|
|
119
|
+
.mockImplementation(() => ({
|
|
120
|
+
resources: [{ id: "fr-1" }],
|
|
121
|
+
errors: null,
|
|
122
|
+
}));
|
|
120
123
|
jest
|
|
121
124
|
.spyOn(ctx.client.resourceRevisions, "createBatch")
|
|
122
125
|
.mockImplementation(() => [{ errors: null }]);
|
|
123
126
|
|
|
124
127
|
const result = await TranslationsHandler.create(ctx);
|
|
125
128
|
|
|
126
|
-
expect(
|
|
129
|
+
expect(mockMultiTranslate).toHaveBeenCalledWith(
|
|
127
130
|
resource.id,
|
|
128
131
|
["fr"],
|
|
129
132
|
expect.anything(),
|
|
130
133
|
"article",
|
|
131
134
|
undefined,
|
|
135
|
+
"en",
|
|
132
136
|
);
|
|
133
137
|
expect(result).toEqual({
|
|
134
138
|
redirect: `/en/articles/${resource.id}/translations?translations=fr:fr-1`,
|
|
@@ -150,8 +154,11 @@ describe("Resource Translations Handler create", () => {
|
|
|
150
154
|
.spyOn(ctx.client.resources, "find")
|
|
151
155
|
.mockImplementation(() => blueprint);
|
|
152
156
|
jest
|
|
153
|
-
.spyOn(ctx.client.resources, "
|
|
154
|
-
.mockImplementation(() =>
|
|
157
|
+
.spyOn(ctx.client.resources, "multiTranslate")
|
|
158
|
+
.mockImplementation(() => ({
|
|
159
|
+
resources: [{ id: "fr-1" }],
|
|
160
|
+
errors: null,
|
|
161
|
+
}));
|
|
155
162
|
const mockRevisions = jest.spyOn(
|
|
156
163
|
ctx.client.resourceRevisions,
|
|
157
164
|
"createBatch",
|
|
@@ -195,8 +202,8 @@ describe("Resource Translations Handler create", () => {
|
|
|
195
202
|
|
|
196
203
|
jest.spyOn(ctx.client.resources, "find").mockImplementation(() => resource);
|
|
197
204
|
jest
|
|
198
|
-
.spyOn(ctx.client.resources, "
|
|
199
|
-
.mockImplementation(() => null);
|
|
205
|
+
.spyOn(ctx.client.resources, "multiTranslate")
|
|
206
|
+
.mockImplementation(() => ({ resources: null, errors: null }));
|
|
200
207
|
const mockFormatErrors = jest
|
|
201
208
|
.spyOn(ctx.client.resources, "formatErrors")
|
|
202
209
|
.mockImplementation(() => ({ languages: "translation failed" }));
|
|
@@ -210,4 +217,40 @@ describe("Resource Translations Handler create", () => {
|
|
|
210
217
|
languages: "translation failed",
|
|
211
218
|
});
|
|
212
219
|
});
|
|
220
|
+
|
|
221
|
+
test("deduplicates languages when duplicate locales are provided in params", async () => {
|
|
222
|
+
const resource = Factory.build("resource", {
|
|
223
|
+
translations: {},
|
|
224
|
+
categories: [{ id: "cat1" }],
|
|
225
|
+
});
|
|
226
|
+
const ctx = buildCtx({
|
|
227
|
+
resource,
|
|
228
|
+
resourceParams: { resource: { languages: ["fr", "fr", "de"] } },
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
jest.spyOn(ctx.client.resources, "find").mockImplementation(() => resource);
|
|
232
|
+
const mockMultiTranslate = jest
|
|
233
|
+
.spyOn(ctx.client.resources, "multiTranslate")
|
|
234
|
+
.mockImplementation(() => ({
|
|
235
|
+
resources: [{ id: "fr-1" }, { id: "de-1" }],
|
|
236
|
+
errors: null,
|
|
237
|
+
}));
|
|
238
|
+
jest
|
|
239
|
+
.spyOn(ctx.client.resourceRevisions, "createBatch")
|
|
240
|
+
.mockImplementation(() => [{ errors: null }, { errors: null }]);
|
|
241
|
+
|
|
242
|
+
const result = await TranslationsHandler.create(ctx);
|
|
243
|
+
|
|
244
|
+
expect(mockMultiTranslate).toHaveBeenCalledWith(
|
|
245
|
+
resource.id,
|
|
246
|
+
["fr", "de"],
|
|
247
|
+
expect.anything(),
|
|
248
|
+
"article",
|
|
249
|
+
undefined,
|
|
250
|
+
"en",
|
|
251
|
+
);
|
|
252
|
+
expect(result).toEqual({
|
|
253
|
+
redirect: `/en/articles/${resource.id}/translations?translations=fr:fr-1;de:de-1`,
|
|
254
|
+
});
|
|
255
|
+
});
|
|
213
256
|
});
|
|
@@ -87,16 +87,18 @@ const CONTENT_TASK = {
|
|
|
87
87
|
failedAt: null,
|
|
88
88
|
insertedAt: "2024-08-30T19:05:00Z",
|
|
89
89
|
updatedAt: "2024-08-30T19:05:00Z",
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
90
|
+
resources: [
|
|
91
|
+
{
|
|
92
|
+
id: "123",
|
|
93
|
+
title: "Test Resource",
|
|
94
|
+
slug: "test-resource",
|
|
95
|
+
locale: "en",
|
|
96
|
+
visibility: "public",
|
|
97
|
+
resourceType: "page",
|
|
98
|
+
insertedAt: "2024-08-30T19:00:00Z",
|
|
99
|
+
updatedAt: "2024-08-30T19:05:00Z",
|
|
100
|
+
},
|
|
101
|
+
],
|
|
100
102
|
lokaliseWorkflow: null,
|
|
101
103
|
};
|
|
102
104
|
|