Skip to content

Commit af39060

Browse files
committed
Improved error handling in case of partial creation
1 parent 5ead24b commit af39060

File tree

2 files changed

+94
-17
lines changed

2 files changed

+94
-17
lines changed

src/lib/common/errors.js

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,63 @@ export async function responseError(response) {
1414
* Used for example to handle the displaying of the error alert when using the ConfirmActionButton.
1515
*/
1616
export class AlertError extends Error {
17+
/** @type {null | { loc: string[], msg: string }} */
18+
simpleValidationMessage;
19+
1720
/**
1821
* @param {any} reason
22+
* @param {number|null} statusCode
1923
*/
20-
constructor(reason) {
24+
constructor(reason, statusCode = null) {
2125
super();
2226
this.reason = reason;
27+
this.simpleValidationMessage = getSimpleValidationMessage(reason, statusCode);
28+
}
29+
30+
/**
31+
* @param {string[]} loc expected location of the validation message
32+
* @returns {string | null} the validation message, if found
33+
*/
34+
getSimpleValidationMessage(...loc) {
35+
if (!this.simpleValidationMessage) {
36+
return null;
37+
}
38+
if (this.simpleValidationMessage.loc.length !== loc.length) {
39+
return null;
40+
}
41+
for (let i = 0; i < loc.length; i++) {
42+
if (this.simpleValidationMessage.loc[i] !== loc[i]) {
43+
return null;
44+
}
45+
}
46+
return this.simpleValidationMessage.msg;
47+
}
48+
}
49+
50+
/**
51+
* Detects if the error message is a simple validation message for one field.
52+
*
53+
* @param {any} reason
54+
* @param {number | null} statusCode
55+
* @returns {null | { loc: string[], msg: string }}
56+
*/
57+
function getSimpleValidationMessage(reason, statusCode) {
58+
if (
59+
statusCode !== 422 ||
60+
!('detail' in reason) ||
61+
!Array.isArray(reason.detail) ||
62+
reason.detail.length !== 1
63+
) {
64+
return null;
65+
}
66+
const err = reason.detail[0];
67+
if (!Array.isArray(err.loc) || !err.msg || err.type !== 'value_error') {
68+
return null;
2369
}
70+
return {
71+
loc: err.loc.length > 1 && err.loc[0] === 'body' ? err.loc.slice(1) : err.loc,
72+
msg: err.msg
73+
};
2474
}
2575

2676
/**

src/lib/components/projects/CreateUpdateDatasetModal.svelte

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { AlertError } from '$lib/common/errors';
44
import { onMount, tick } from 'svelte';
55
import Modal from '../common/Modal.svelte';
6+
import { error } from '@sveltejs/kit';
67
78
/** @type {(dataset: (import('$lib/types').Dataset)) => void} */
89
export let createDatasetCallback;
@@ -20,10 +21,11 @@
2021
let datasetType = '';
2122
let customDatasetType = '';
2223
let readonly = false;
23-
/** @type {Array<{ id: number | null, path: string, editing: boolean, error: boolean }>} */
24+
/** @type {Array<{ id: number | null, path: string, editing: boolean, error: string }>} */
2425
let resources = [getNewResource()];
2526
let submitted = false;
2627
let saving = false;
28+
let creatingDataset = false;
2729
2830
// Used for the update
2931
/** @type {import('$lib/types').Dataset} */
@@ -75,6 +77,7 @@
7577
readonly = false;
7678
resources = [getNewResource()];
7779
submitted = false;
80+
creatingDataset = false;
7881
modal.show();
7982
}
8083
@@ -83,7 +86,7 @@
8386
id: /** @type {number | null} */ null,
8487
path: '',
8588
editing: true,
86-
error: false
89+
error: ''
8790
};
8891
}
8992
@@ -93,18 +96,19 @@
9396
datasetName = dataset.name;
9497
if (!dataset.type || datasetTypes.indexOf(dataset.type) !== -1) {
9598
datasetTypeOption = 'standard';
96-
datasetType = dataset.type || "";
99+
datasetType = dataset.type || '';
97100
customDatasetType = '';
98101
} else {
99102
datasetTypeOption = 'custom';
100103
datasetType = '';
101-
customDatasetType = dataset.type || "";
104+
customDatasetType = dataset.type || '';
102105
}
103106
readonly = dataset.readonly;
104107
resources = dataset.resource_list.map((r) => {
105-
return { id: r.id, path: r.path, editing: false, error: false };
108+
return { id: r.id, path: r.path, editing: false, error: '' };
106109
});
107110
submitted = false;
111+
creatingDataset = false;
108112
modal.show();
109113
}
110114
@@ -121,12 +125,19 @@
121125
}
122126
123127
async function save() {
128+
// reset resources errors
129+
resources = resources.map((r) => {
130+
return { ...r, error: '' };
131+
});
132+
124133
try {
125134
if (datasetId === null) {
135+
creatingDataset = true;
126136
originalDataset = await callCreateDataset();
127137
datasetId = originalDataset.id;
128138
createDatasetCallback(originalDataset);
129139
} else {
140+
creatingDataset = false;
130141
originalDataset = await callUpdateDataset();
131142
updateDatasetCallback(originalDataset);
132143
}
@@ -196,13 +207,17 @@
196207
modal.hideErrorAlert();
197208
198209
const resource = resources[index];
199-
resource.error = false;
210+
resource.error = '';
200211
if (!resource.path) {
201212
return false;
202213
}
203214
204215
if (datasetId === null) {
205216
try {
217+
if (!fieldsAreValid()) {
218+
return false;
219+
}
220+
creatingDataset = true;
206221
originalDataset = await callCreateDataset();
207222
datasetId = originalDataset.id;
208223
createDatasetCallback(originalDataset);
@@ -224,8 +239,14 @@
224239
setResourceEditable(index, false);
225240
updateDatasetCallback(getUpdatedDataset());
226241
} catch (err) {
227-
resource.error = true;
228-
modal.displayErrorAlert(err);
242+
const validationMsg =
243+
err instanceof AlertError ? err.getSimpleValidationMessage('path') : null;
244+
if (validationMsg) {
245+
resource.error = validationMsg;
246+
} else {
247+
resource.error = 'An error happened saving the resource';
248+
modal.displayErrorAlert(err);
249+
}
229250
} finally {
230251
resources = resources.filter((r, i) => (i === index ? resource : r));
231252
}
@@ -251,7 +272,7 @@
251272
const result = await response.json();
252273
if (!response.ok) {
253274
console.log('Dataset resource creation failed', result);
254-
throw new AlertError(result);
275+
throw new AlertError(result, response.status);
255276
}
256277
return result;
257278
}
@@ -365,7 +386,7 @@
365386
return false;
366387
}
367388
}
368-
return datasetName;
389+
return !!datasetName;
369390
}
370391
371392
function getDatasetType() {
@@ -390,6 +411,16 @@
390411
</h4>
391412
</svelte:fragment>
392413
<svelte:fragment slot="body">
414+
{#if creatingDataset && resources.filter((r) => r.error !== '').length > 0}
415+
<div class="alert alert-warning alert-dismissible fade show" role="alert">
416+
<span>
417+
<i class="bi bi-exclamation-triangle" />
418+
<strong>Warning</strong>: Dataset has been created but the creation of some of its
419+
resources failed.
420+
</span>
421+
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close" />
422+
</div>
423+
{/if}
393424
<span id="errorAlert-createUpdateDatasetModal" />
394425
<form class="row" on:submit|preventDefault={handleSave} id="create-update-dataset-form">
395426
<div class="col">
@@ -438,11 +469,7 @@
438469
<div class="row mb-3">
439470
<div class="col-10 offset-2">
440471
{#if datasetTypeOption === 'standard'}
441-
<select
442-
id="datasetType"
443-
bind:value={datasetType}
444-
class="form-control"
445-
>
472+
<select id="datasetType" bind:value={datasetType} class="form-control">
446473
<option value="">Select...</option>
447474
{#each datasetTypes as allowedType}
448475
<option>{allowedType}</option>
@@ -521,7 +548,7 @@
521548
<div class="invalid-feedback">Required field</div>
522549
{/if}
523550
{#if submitted && resource.error}
524-
<div class="invalid-feedback">An error happened saving the resource</div>
551+
<div class="invalid-feedback">{resource.error}</div>
525552
{/if}
526553
</div>
527554
</div>

0 commit comments

Comments
 (0)