strapi-plugin-bulk-updater 1.0.0 → 1.1.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/README.md +41 -24
- package/dist/_chunks/{BulkUpdater-CooMo-Hc.js → BulkUpdater-BRcMQGP3.js} +507 -67
- package/dist/_chunks/BulkUpdater-BRcMQGP3.js.map +1 -0
- package/dist/_chunks/{BulkUpdater-R78pX-om.mjs → BulkUpdater-BSZP0U18.mjs} +514 -74
- package/dist/_chunks/BulkUpdater-BSZP0U18.mjs.map +1 -0
- package/dist/_chunks/ScopedQuotaSelectInput-ChWrQIBV.mjs +183 -0
- package/dist/_chunks/ScopedQuotaSelectInput-ChWrQIBV.mjs.map +1 -0
- package/dist/_chunks/ScopedQuotaSelectInput-s4oUMLgK.js +201 -0
- package/dist/_chunks/ScopedQuotaSelectInput-s4oUMLgK.js.map +1 -0
- package/dist/_chunks/SettingsPage-BRGTrW9H.js +43 -0
- package/dist/_chunks/SettingsPage-BRGTrW9H.js.map +1 -0
- package/dist/_chunks/SettingsPage-ieu-Out8.mjs +43 -0
- package/dist/_chunks/SettingsPage-ieu-Out8.mjs.map +1 -0
- package/dist/_chunks/ValidationRulesTab-BGcqM9cN.mjs +598 -0
- package/dist/_chunks/ValidationRulesTab-BGcqM9cN.mjs.map +1 -0
- package/dist/_chunks/ValidationRulesTab-BoqhoTjo.js +597 -0
- package/dist/_chunks/ValidationRulesTab-BoqhoTjo.js.map +1 -0
- package/dist/admin/index.js +19 -3
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs +19 -3
- package/dist/admin/index.mjs.map +1 -1
- package/dist/server/index.js +30491 -269
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +30481 -269
- package/dist/server/index.mjs.map +1 -1
- package/package.json +10 -3
- package/dist/_chunks/BulkUpdater-CooMo-Hc.js.map +0 -1
- package/dist/_chunks/BulkUpdater-R78pX-om.mjs.map +0 -1
- package/dist/_chunks/SettingsPage-C6QyloJe.js +0 -176
- package/dist/_chunks/SettingsPage-C6QyloJe.js.map +0 -1
- package/dist/_chunks/SettingsPage-Drb7s719.mjs +0 -176
- package/dist/_chunks/SettingsPage-Drb7s719.mjs.map +0 -1
|
@@ -5,8 +5,423 @@ const React = require("react");
|
|
|
5
5
|
const admin = require("@strapi/strapi/admin");
|
|
6
6
|
const designSystem = require("@strapi/design-system");
|
|
7
7
|
const icons = require("@strapi/icons");
|
|
8
|
+
const ValidationRulesTab = require("./ValidationRulesTab-BoqhoTjo.js");
|
|
8
9
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
9
10
|
const React__default = /* @__PURE__ */ _interopDefault(React);
|
|
11
|
+
function CsvManagerTab() {
|
|
12
|
+
const { get, post } = admin.useFetchClient();
|
|
13
|
+
const { toggleNotification } = admin.useNotification();
|
|
14
|
+
const [collections, setCollections] = React.useState([]);
|
|
15
|
+
const [loadingCollections, setLoadingCollections] = React.useState(true);
|
|
16
|
+
const [selectedUid, setSelectedUid] = React.useState("");
|
|
17
|
+
const [exportLimit, setExportLimit] = React.useState(1e3);
|
|
18
|
+
const [exportOnlyViolations, setExportOnlyViolations] = React.useState(false);
|
|
19
|
+
const [rules, setRules] = React.useState([]);
|
|
20
|
+
const [selectedRuleId, setSelectedRuleId] = React.useState("");
|
|
21
|
+
const [importMode, setImportMode] = React.useState("update");
|
|
22
|
+
const [publishOnImport, setPublishOnImport] = React.useState(true);
|
|
23
|
+
const [csvRawText, setCsvRawText] = React.useState("");
|
|
24
|
+
const [fileName, setFileName] = React.useState("");
|
|
25
|
+
const [parsedPreview, setParsedPreview] = React.useState([]);
|
|
26
|
+
const [importing, setImporting] = React.useState(false);
|
|
27
|
+
const [importResult, setImportResult] = React.useState(null);
|
|
28
|
+
React.useEffect(() => {
|
|
29
|
+
fetchInitialData();
|
|
30
|
+
}, []);
|
|
31
|
+
const fetchInitialData = async () => {
|
|
32
|
+
try {
|
|
33
|
+
setLoadingCollections(true);
|
|
34
|
+
const [modelsRes, rulesRes] = await Promise.all([
|
|
35
|
+
get("/bulk-updater/models"),
|
|
36
|
+
get("/bulk-updater/validation-rules")
|
|
37
|
+
]);
|
|
38
|
+
if (modelsRes.data?.collections) {
|
|
39
|
+
setCollections(modelsRes.data.collections);
|
|
40
|
+
if (modelsRes.data.collections.length > 0) {
|
|
41
|
+
setSelectedUid(modelsRes.data.collections[0].uid);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (rulesRes.data?.rules) {
|
|
45
|
+
setRules(rulesRes.data.rules);
|
|
46
|
+
if (rulesRes.data.rules.length > 0) {
|
|
47
|
+
setSelectedRuleId(rulesRes.data.rules[0].id);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} catch (err) {
|
|
51
|
+
toggleNotification({
|
|
52
|
+
type: "danger",
|
|
53
|
+
message: err.message || "Failed loading collections for CSV Manager"
|
|
54
|
+
});
|
|
55
|
+
} finally {
|
|
56
|
+
setLoadingCollections(false);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const handleDownloadTemplate = async () => {
|
|
60
|
+
if (!selectedUid) return;
|
|
61
|
+
try {
|
|
62
|
+
const res = await get(`/bulk-updater/csv/template/${selectedUid}`);
|
|
63
|
+
const csvStr = res.data?.csvContent || (typeof res.data === "string" ? res.data : "");
|
|
64
|
+
const filename = res.data?.filename || `${selectedUid.split(".").pop()}_template.csv`;
|
|
65
|
+
downloadCsvFile(csvStr, filename);
|
|
66
|
+
toggleNotification({
|
|
67
|
+
type: "success",
|
|
68
|
+
message: "CSV Template downloaded successfully"
|
|
69
|
+
});
|
|
70
|
+
} catch (err) {
|
|
71
|
+
toggleNotification({
|
|
72
|
+
type: "danger",
|
|
73
|
+
message: err.message || "Failed downloading CSV template"
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const handleExportCollection = async () => {
|
|
78
|
+
if (!selectedUid) return;
|
|
79
|
+
try {
|
|
80
|
+
const res = await get(`/bulk-updater/csv/export/${selectedUid}?limit=${exportLimit}`);
|
|
81
|
+
const csvStr = res.data?.csvContent || (typeof res.data === "string" ? res.data : "");
|
|
82
|
+
const filename = res.data?.filename || `${selectedUid.split(".").pop()}_export.csv`;
|
|
83
|
+
downloadCsvFile(csvStr, filename);
|
|
84
|
+
toggleNotification({
|
|
85
|
+
type: "success",
|
|
86
|
+
message: "Collection exported to CSV successfully"
|
|
87
|
+
});
|
|
88
|
+
} catch (err) {
|
|
89
|
+
toggleNotification({
|
|
90
|
+
type: "danger",
|
|
91
|
+
message: err.message || "Failed exporting collection to CSV"
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const handleExportViolations = async () => {
|
|
96
|
+
if (!selectedRuleId) return;
|
|
97
|
+
try {
|
|
98
|
+
const res = await get(`/bulk-updater/csv/export-violations/${selectedRuleId}`);
|
|
99
|
+
const csvStr = res.data?.csvContent || (typeof res.data === "string" ? res.data : "");
|
|
100
|
+
const filename = res.data?.filename || `violations_${selectedRuleId}.csv`;
|
|
101
|
+
downloadCsvFile(csvStr, filename);
|
|
102
|
+
toggleNotification({
|
|
103
|
+
type: "success",
|
|
104
|
+
message: "Rule violations exported to CSV successfully"
|
|
105
|
+
});
|
|
106
|
+
} catch (err) {
|
|
107
|
+
toggleNotification({
|
|
108
|
+
type: "danger",
|
|
109
|
+
message: err.message || "Failed exporting violations to CSV"
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const downloadCsvFile = (content, fallbackName) => {
|
|
114
|
+
if (!content) {
|
|
115
|
+
toggleNotification({
|
|
116
|
+
type: "warning",
|
|
117
|
+
message: "No CSV content to download"
|
|
118
|
+
});
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const blob = new Blob([content], { type: "text/csv;charset=utf-8;" });
|
|
122
|
+
const url = URL.createObjectURL(blob);
|
|
123
|
+
const link = document.createElement("a");
|
|
124
|
+
link.href = url;
|
|
125
|
+
link.setAttribute("download", fallbackName);
|
|
126
|
+
document.body.appendChild(link);
|
|
127
|
+
link.click();
|
|
128
|
+
document.body.removeChild(link);
|
|
129
|
+
URL.revokeObjectURL(url);
|
|
130
|
+
};
|
|
131
|
+
const handleFileUpload = (e) => {
|
|
132
|
+
const file = e.target.files?.[0];
|
|
133
|
+
if (!file) return;
|
|
134
|
+
setFileName(file.name);
|
|
135
|
+
setImportResult(null);
|
|
136
|
+
const reader = new FileReader();
|
|
137
|
+
reader.onload = (event) => {
|
|
138
|
+
const text = event.target?.result;
|
|
139
|
+
setCsvRawText(text);
|
|
140
|
+
try {
|
|
141
|
+
const lines = text.split(/\r?\n/).filter((l) => l.trim().length > 0);
|
|
142
|
+
if (lines.length > 0) {
|
|
143
|
+
const headers = lines[0].split(",").map((h) => h.replace(/^"|"$/g, "").trim());
|
|
144
|
+
const previewRows = [];
|
|
145
|
+
for (let i = 1; i < Math.min(lines.length, 6); i++) {
|
|
146
|
+
const vals = lines[i].split(",").map((v) => v.replace(/^"|"$/g, "").trim());
|
|
147
|
+
const row = {};
|
|
148
|
+
headers.forEach((h, idx) => {
|
|
149
|
+
row[h] = vals[idx] || "";
|
|
150
|
+
});
|
|
151
|
+
previewRows.push(row);
|
|
152
|
+
}
|
|
153
|
+
setParsedPreview(previewRows);
|
|
154
|
+
}
|
|
155
|
+
} catch (err) {
|
|
156
|
+
setParsedPreview([]);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
reader.readAsText(file);
|
|
160
|
+
};
|
|
161
|
+
const handleExecuteImport = async () => {
|
|
162
|
+
if (!selectedUid || !csvRawText) return;
|
|
163
|
+
try {
|
|
164
|
+
setImporting(true);
|
|
165
|
+
setImportResult(null);
|
|
166
|
+
const res = await post("/bulk-updater/csv/import", {
|
|
167
|
+
uid: selectedUid,
|
|
168
|
+
csvText: csvRawText,
|
|
169
|
+
mode: importMode,
|
|
170
|
+
publish: publishOnImport
|
|
171
|
+
});
|
|
172
|
+
if (res.data && res.data.success) {
|
|
173
|
+
setImportResult(res.data);
|
|
174
|
+
toggleNotification({
|
|
175
|
+
type: "success",
|
|
176
|
+
message: `CSV Import complete: ${res.data.stats.created} created, ${res.data.stats.updated} updated.`
|
|
177
|
+
});
|
|
178
|
+
} else {
|
|
179
|
+
throw new Error(res.data?.error || "Import failed");
|
|
180
|
+
}
|
|
181
|
+
} catch (err) {
|
|
182
|
+
toggleNotification({
|
|
183
|
+
type: "danger",
|
|
184
|
+
message: err.message || "Error processing CSV import"
|
|
185
|
+
});
|
|
186
|
+
} finally {
|
|
187
|
+
setImporting(false);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
const selectedCol = collections.find((c) => c.uid === selectedUid);
|
|
191
|
+
return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, background: "neutral0", hasRadius: true, shadow: "filterShadow", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
|
|
192
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
|
|
193
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
|
|
194
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", tag: "h2", fontWeight: "bold", children: "CSV Export & Import Center" }),
|
|
195
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: "Export all records, filtered subsets, or rule violations into CSV. Correct or add records in spreadsheets and re-upload with full schema & quota enforcement." })
|
|
196
|
+
] }),
|
|
197
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowClockwise, {}), onClick: fetchInitialData, children: "Refresh" })
|
|
198
|
+
] }),
|
|
199
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, alignItems: "flex-end", children: [
|
|
200
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { flex: 1 }, children: [
|
|
201
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 2, children: "Target Collection Type" }),
|
|
202
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
203
|
+
designSystem.SingleSelect,
|
|
204
|
+
{
|
|
205
|
+
value: selectedUid,
|
|
206
|
+
onChange: (val) => {
|
|
207
|
+
setSelectedUid(val);
|
|
208
|
+
setImportResult(null);
|
|
209
|
+
},
|
|
210
|
+
disabled: loadingCollections,
|
|
211
|
+
placeholder: "Select a collection...",
|
|
212
|
+
children: collections.map((col) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.SingleSelectOption, { value: col.uid, children: [
|
|
213
|
+
col.displayName,
|
|
214
|
+
" (",
|
|
215
|
+
col.totalEntries ?? 0,
|
|
216
|
+
" records)"
|
|
217
|
+
] }, col.uid))
|
|
218
|
+
}
|
|
219
|
+
)
|
|
220
|
+
] }),
|
|
221
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
222
|
+
designSystem.Button,
|
|
223
|
+
{
|
|
224
|
+
variant: "tertiary",
|
|
225
|
+
startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Download, {}),
|
|
226
|
+
onClick: handleDownloadTemplate,
|
|
227
|
+
disabled: !selectedUid,
|
|
228
|
+
children: "Download Sample CSV Template"
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
] }) }),
|
|
232
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 5, background: "neutral100", hasRadius: true, children: [
|
|
233
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h3", fontWeight: "bold", marginBottom: 2, children: "1. Export Data to CSV" }),
|
|
234
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", marginBottom: 4, children: "Extract entire collections or pinpoint records violating validation quotas." }),
|
|
235
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, wrap: "wrap", alignItems: "center", marginTop: 3, children: [
|
|
236
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
237
|
+
designSystem.Button,
|
|
238
|
+
{
|
|
239
|
+
variant: "default",
|
|
240
|
+
startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Download, {}),
|
|
241
|
+
onClick: handleExportCollection,
|
|
242
|
+
disabled: !selectedUid,
|
|
243
|
+
children: [
|
|
244
|
+
"Export All ",
|
|
245
|
+
selectedCol?.displayName || "Collection",
|
|
246
|
+
" Records (.csv)"
|
|
247
|
+
]
|
|
248
|
+
}
|
|
249
|
+
),
|
|
250
|
+
rules.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", style: { marginLeft: "auto" }, children: [
|
|
251
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
252
|
+
designSystem.SingleSelect,
|
|
253
|
+
{
|
|
254
|
+
value: selectedRuleId,
|
|
255
|
+
onChange: (val) => setSelectedRuleId(val),
|
|
256
|
+
placeholder: "Select Quota Rule...",
|
|
257
|
+
children: rules.map((r) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.SingleSelectOption, { value: r.id, children: [
|
|
258
|
+
"Rule: ",
|
|
259
|
+
r.name
|
|
260
|
+
] }, r.id))
|
|
261
|
+
}
|
|
262
|
+
),
|
|
263
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
264
|
+
designSystem.Button,
|
|
265
|
+
{
|
|
266
|
+
variant: "danger-light",
|
|
267
|
+
startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Download, {}),
|
|
268
|
+
onClick: handleExportViolations,
|
|
269
|
+
disabled: !selectedRuleId,
|
|
270
|
+
children: "Export Violators CSV"
|
|
271
|
+
}
|
|
272
|
+
)
|
|
273
|
+
] })
|
|
274
|
+
] })
|
|
275
|
+
] }),
|
|
276
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 5, background: "neutral100", hasRadius: true, children: [
|
|
277
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h3", fontWeight: "bold", marginBottom: 2, children: "2. Import & Batch Override CSV" }),
|
|
278
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", marginBottom: 4, children: "Upload an updated CSV file to bulk update existing records or insert new records." }),
|
|
279
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 6, marginBottom: 4, wrap: "wrap", children: [
|
|
280
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
|
|
281
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 2, children: "Operation Mode" }),
|
|
282
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, children: [
|
|
283
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 6, cursor: "pointer" }, children: [
|
|
284
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
285
|
+
"input",
|
|
286
|
+
{
|
|
287
|
+
type: "radio",
|
|
288
|
+
name: "importMode",
|
|
289
|
+
value: "update",
|
|
290
|
+
checked: importMode === "update",
|
|
291
|
+
onChange: () => setImportMode("update")
|
|
292
|
+
}
|
|
293
|
+
),
|
|
294
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: importMode === "update" ? "bold" : "normal", children: "Update Existing (Matches by documentId)" })
|
|
295
|
+
] }),
|
|
296
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 6, cursor: "pointer" }, children: [
|
|
297
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
298
|
+
"input",
|
|
299
|
+
{
|
|
300
|
+
type: "radio",
|
|
301
|
+
name: "importMode",
|
|
302
|
+
value: "create_or_override",
|
|
303
|
+
checked: importMode === "create_or_override",
|
|
304
|
+
onChange: () => setImportMode("create_or_override")
|
|
305
|
+
}
|
|
306
|
+
),
|
|
307
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: importMode === "create_or_override" ? "bold" : "normal", children: "Add / Override (Inserts new or replaces existing)" })
|
|
308
|
+
] })
|
|
309
|
+
] })
|
|
310
|
+
] }),
|
|
311
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { marginLeft: "auto" }, children: [
|
|
312
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", marginBottom: 2, children: "Publish Options" }),
|
|
313
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
314
|
+
designSystem.Checkbox,
|
|
315
|
+
{
|
|
316
|
+
checked: publishOnImport,
|
|
317
|
+
onValueChange: (val) => setPublishOnImport(val),
|
|
318
|
+
children: "Auto-publish on import"
|
|
319
|
+
}
|
|
320
|
+
)
|
|
321
|
+
] })
|
|
322
|
+
] }),
|
|
323
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
324
|
+
designSystem.Box,
|
|
325
|
+
{
|
|
326
|
+
padding: 6,
|
|
327
|
+
background: "neutral0",
|
|
328
|
+
hasRadius: true,
|
|
329
|
+
style: {
|
|
330
|
+
border: "2px dashed #dcdce4",
|
|
331
|
+
textAlign: "center",
|
|
332
|
+
cursor: "pointer"
|
|
333
|
+
},
|
|
334
|
+
children: [
|
|
335
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
336
|
+
"input",
|
|
337
|
+
{
|
|
338
|
+
type: "file",
|
|
339
|
+
accept: ".csv",
|
|
340
|
+
id: "csv-file-input",
|
|
341
|
+
style: { display: "none" },
|
|
342
|
+
onChange: handleFileUpload
|
|
343
|
+
}
|
|
344
|
+
),
|
|
345
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: "csv-file-input", style: { cursor: "pointer", display: "block" }, children: [
|
|
346
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.Upload, { style: { width: 32, height: 32, margin: "0 auto 8px", color: "#4945ff" } }),
|
|
347
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", fontWeight: "bold", display: "block", children: fileName ? `Selected: ${fileName}` : "Click here or drag a .csv file to upload" }),
|
|
348
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "UTF-8 encoded CSV matching collection schema headers" })
|
|
349
|
+
] })
|
|
350
|
+
]
|
|
351
|
+
}
|
|
352
|
+
),
|
|
353
|
+
parsedPreview.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginTop: 4, background: "neutral0", padding: 4, hasRadius: true, children: [
|
|
354
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 3, children: [
|
|
355
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "sigma", textColor: "neutral700", children: [
|
|
356
|
+
"FILE PREVIEW (FIRST ",
|
|
357
|
+
parsedPreview.length,
|
|
358
|
+
" ROWS)"
|
|
359
|
+
] }),
|
|
360
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "neutral150", textColor: "neutral800", children: "Ready to Import" })
|
|
361
|
+
] }),
|
|
362
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: Object.keys(parsedPreview[0]).length, rowCount: parsedPreview.length + 1, children: [
|
|
363
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tr, { children: Object.keys(parsedPreview[0]).map((h) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: h }) }, h)) }) }),
|
|
364
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: parsedPreview.map((row, rIdx) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tr, { children: Object.keys(parsedPreview[0]).map((h) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", children: row[h] || "-" }) }, h)) }, rIdx)) })
|
|
365
|
+
] }),
|
|
366
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", marginTop: 4, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
367
|
+
designSystem.Button,
|
|
368
|
+
{
|
|
369
|
+
variant: "default",
|
|
370
|
+
size: "L",
|
|
371
|
+
startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Check, {}),
|
|
372
|
+
onClick: handleExecuteImport,
|
|
373
|
+
loading: importing,
|
|
374
|
+
children: importMode === "update" ? "Execute Bulk Update" : "Execute Add / Override"
|
|
375
|
+
}
|
|
376
|
+
) })
|
|
377
|
+
] }),
|
|
378
|
+
importResult && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
379
|
+
designSystem.Alert,
|
|
380
|
+
{
|
|
381
|
+
variant: importResult.stats.errors.length > 0 ? "warning" : "success",
|
|
382
|
+
title: "CSV Import Results",
|
|
383
|
+
children: [
|
|
384
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "omega", display: "block", marginBottom: 1, children: [
|
|
385
|
+
"Successfully processed: ",
|
|
386
|
+
/* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
|
|
387
|
+
importResult.stats.created,
|
|
388
|
+
" created"
|
|
389
|
+
] }),
|
|
390
|
+
",",
|
|
391
|
+
" ",
|
|
392
|
+
/* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
|
|
393
|
+
importResult.stats.updated,
|
|
394
|
+
" updated"
|
|
395
|
+
] }),
|
|
396
|
+
",",
|
|
397
|
+
" ",
|
|
398
|
+
/* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
|
|
399
|
+
importResult.stats.skipped,
|
|
400
|
+
" skipped"
|
|
401
|
+
] }),
|
|
402
|
+
" out of ",
|
|
403
|
+
importResult.stats.total,
|
|
404
|
+
" total rows."
|
|
405
|
+
] }),
|
|
406
|
+
importResult.stats.errors.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginTop: 2, children: [
|
|
407
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "danger700", fontWeight: "bold", children: [
|
|
408
|
+
"Errors encountered (",
|
|
409
|
+
importResult.stats.errors.length,
|
|
410
|
+
"):"
|
|
411
|
+
] }),
|
|
412
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { style: { margin: "4px 0 0 16px", padding: 0 }, children: importResult.stats.errors.slice(0, 5).map((err, idx) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "danger600", children: [
|
|
413
|
+
"Row ",
|
|
414
|
+
err.row,
|
|
415
|
+
": ",
|
|
416
|
+
err.error
|
|
417
|
+
] }) }, idx)) })
|
|
418
|
+
] })
|
|
419
|
+
]
|
|
420
|
+
}
|
|
421
|
+
) })
|
|
422
|
+
] })
|
|
423
|
+
] }) });
|
|
424
|
+
}
|
|
10
425
|
function getAvailableColumnsForCollection(col, selectedAttribute) {
|
|
11
426
|
const cols = [
|
|
12
427
|
{
|
|
@@ -163,7 +578,7 @@ const BulkUpdater = () => {
|
|
|
163
578
|
try {
|
|
164
579
|
const params = new URLSearchParams(window.location.search);
|
|
165
580
|
const t = params.get("tab");
|
|
166
|
-
if (t === "audit" || t === "editor") return t;
|
|
581
|
+
if (t === "audit" || t === "editor" || t === "rules" || t === "csv") return t;
|
|
167
582
|
} catch {
|
|
168
583
|
}
|
|
169
584
|
return "editor";
|
|
@@ -182,7 +597,6 @@ const BulkUpdater = () => {
|
|
|
182
597
|
const [updating, setUpdating] = React.useState(false);
|
|
183
598
|
const [attributes, setAttributes] = React.useState([]);
|
|
184
599
|
const [collections, setCollections] = React.useState([]);
|
|
185
|
-
const [license, setLicense] = React.useState(null);
|
|
186
600
|
const [auditEnabled, setAuditEnabled] = React.useState(false);
|
|
187
601
|
const [executionWindow, setExecutionWindow] = React.useState(null);
|
|
188
602
|
const [showConfirmModal, setShowConfirmModal] = React.useState(false);
|
|
@@ -270,7 +684,6 @@ const BulkUpdater = () => {
|
|
|
270
684
|
setAttributes(res.data.attributes || []);
|
|
271
685
|
const cols = res.data.collections || [];
|
|
272
686
|
setCollections(cols);
|
|
273
|
-
setLicense(res.data.license || null);
|
|
274
687
|
setAuditEnabled(Boolean(res.data.auditEnabled));
|
|
275
688
|
if (res.data.executionWindow) {
|
|
276
689
|
setExecutionWindow(res.data.executionWindow);
|
|
@@ -618,13 +1031,6 @@ const BulkUpdater = () => {
|
|
|
618
1031
|
});
|
|
619
1032
|
return;
|
|
620
1033
|
}
|
|
621
|
-
if (useRegex && !license?.isPro) {
|
|
622
|
-
toggleNotification({
|
|
623
|
-
type: "warning",
|
|
624
|
-
message: "Regex replacement is a Pro feature. Please activate your license in Settings."
|
|
625
|
-
});
|
|
626
|
-
return;
|
|
627
|
-
}
|
|
628
1034
|
const totalSelected = Object.values(selectedEntries).filter(Boolean).length;
|
|
629
1035
|
if (totalSelected === 0) {
|
|
630
1036
|
toggleNotification({
|
|
@@ -881,14 +1287,6 @@ const BulkUpdater = () => {
|
|
|
881
1287
|
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
|
|
882
1288
|
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, alignItems: "center", children: [
|
|
883
1289
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", tag: "h1", fontWeight: "bold", children: "Bulk Attribute Updater" }),
|
|
884
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
885
|
-
designSystem.Badge,
|
|
886
|
-
{
|
|
887
|
-
backgroundColor: license?.isPro ? "success100" : "neutral200",
|
|
888
|
-
textColor: license?.isPro ? "success700" : "neutral700",
|
|
889
|
-
children: license?.isPro ? `${license.tier.toUpperCase()} EDITION` : "COMMUNITY EDITION"
|
|
890
|
-
}
|
|
891
|
-
),
|
|
892
1290
|
auditEnabled && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "secondary100", textColor: "secondary700", children: "AUDIT & ROLLBACK ACTIVE" })
|
|
893
1291
|
] }),
|
|
894
1292
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: "Cross-collection batch attribute management with declarative audit trail and one-click rollback." })
|
|
@@ -929,6 +1327,24 @@ const BulkUpdater = () => {
|
|
|
929
1327
|
] }) : auditHistory.length > 0 ? ` (${auditHistory.length})` : ""
|
|
930
1328
|
]
|
|
931
1329
|
}
|
|
1330
|
+
),
|
|
1331
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1332
|
+
designSystem.Button,
|
|
1333
|
+
{
|
|
1334
|
+
variant: activeTab === "rules" ? "default" : "tertiary",
|
|
1335
|
+
onClick: () => setActiveTab("rules"),
|
|
1336
|
+
size: "M",
|
|
1337
|
+
children: "Validation & Quota Rules"
|
|
1338
|
+
}
|
|
1339
|
+
),
|
|
1340
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1341
|
+
designSystem.Button,
|
|
1342
|
+
{
|
|
1343
|
+
variant: activeTab === "csv" ? "default" : "tertiary",
|
|
1344
|
+
onClick: () => setActiveTab("csv"),
|
|
1345
|
+
size: "M",
|
|
1346
|
+
children: "CSV Import / Export & Override"
|
|
1347
|
+
}
|
|
932
1348
|
)
|
|
933
1349
|
] }),
|
|
934
1350
|
executionWindow && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 2, alignItems: "center", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1108,38 +1524,24 @@ const BulkUpdater = () => {
|
|
|
1108
1524
|
}
|
|
1109
1525
|
) })
|
|
1110
1526
|
] }),
|
|
1111
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1112
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
)
|
|
1130
|
-
] }),
|
|
1131
|
-
useRegex && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 240 }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1132
|
-
designSystem.TextInput,
|
|
1133
|
-
{
|
|
1134
|
-
placeholder: "Regex pattern (e.g. \\b(OldName)\\b)...",
|
|
1135
|
-
value: regexPattern,
|
|
1136
|
-
onChange: (e) => setRegexPattern(e.target.value),
|
|
1137
|
-
disabled: !license?.isPro
|
|
1138
|
-
}
|
|
1139
|
-
) })
|
|
1140
|
-
] }),
|
|
1141
|
-
!license?.isPro && useRegex && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "warning700", children: "Regex replacement is locked to Pro license holders. Visit Settings > Bulk Updater to activate your key." }) })
|
|
1142
|
-
] }),
|
|
1527
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 4, padding: 4, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, children: [
|
|
1528
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 2, alignItems: "center", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1529
|
+
designSystem.Checkbox,
|
|
1530
|
+
{
|
|
1531
|
+
checked: useRegex,
|
|
1532
|
+
onCheckedChange: (checked) => setUseRegex(Boolean(checked)),
|
|
1533
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: "Enable Regex Find & Replace" })
|
|
1534
|
+
}
|
|
1535
|
+
) }),
|
|
1536
|
+
useRegex && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 240 }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1537
|
+
designSystem.TextInput,
|
|
1538
|
+
{
|
|
1539
|
+
placeholder: "Regex pattern (e.g. \\b(OldName)\\b)...",
|
|
1540
|
+
value: regexPattern,
|
|
1541
|
+
onChange: (e) => setRegexPattern(e.target.value)
|
|
1542
|
+
}
|
|
1543
|
+
) })
|
|
1544
|
+
] }) }),
|
|
1143
1545
|
selectedAttribute && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 4, padding: 3, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
|
|
1144
1546
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: "Attribute Info:" }),
|
|
1145
1547
|
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", children: [
|
|
@@ -1483,6 +1885,52 @@ const BulkUpdater = () => {
|
|
|
1483
1885
|
]
|
|
1484
1886
|
}
|
|
1485
1887
|
),
|
|
1888
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1889
|
+
designSystem.Button,
|
|
1890
|
+
{
|
|
1891
|
+
variant: "tertiary",
|
|
1892
|
+
size: "S",
|
|
1893
|
+
startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Download, {}),
|
|
1894
|
+
onClick: async () => {
|
|
1895
|
+
try {
|
|
1896
|
+
const params = new URLSearchParams();
|
|
1897
|
+
if (selectedAttribute) params.set("attribute", selectedAttribute);
|
|
1898
|
+
if (filterOperator && filterOperator !== "none") {
|
|
1899
|
+
params.set("filterOperator", filterOperator);
|
|
1900
|
+
if (filterQuery) params.set("filterQuery", filterQuery);
|
|
1901
|
+
}
|
|
1902
|
+
const res = await get(`/bulk-updater/csv/export/${col.uid}?${params.toString()}`);
|
|
1903
|
+
const csvStr = res.data?.csvContent || (typeof res.data === "string" ? res.data : "");
|
|
1904
|
+
if (!csvStr) {
|
|
1905
|
+
toggleNotification({
|
|
1906
|
+
type: "warning",
|
|
1907
|
+
message: "No CSV content returned for this collection"
|
|
1908
|
+
});
|
|
1909
|
+
return;
|
|
1910
|
+
}
|
|
1911
|
+
const blob = new Blob([csvStr], { type: "text/csv;charset=utf-8;" });
|
|
1912
|
+
const url = URL.createObjectURL(blob);
|
|
1913
|
+
const link = document.createElement("a");
|
|
1914
|
+
link.href = url;
|
|
1915
|
+
link.setAttribute("download", `${col.singularName || "export"}_entries.csv`);
|
|
1916
|
+
document.body.appendChild(link);
|
|
1917
|
+
link.click();
|
|
1918
|
+
document.body.removeChild(link);
|
|
1919
|
+
URL.revokeObjectURL(url);
|
|
1920
|
+
toggleNotification({
|
|
1921
|
+
type: "success",
|
|
1922
|
+
message: `Exported ${col.displayName} entries to CSV`
|
|
1923
|
+
});
|
|
1924
|
+
} catch (e) {
|
|
1925
|
+
toggleNotification({
|
|
1926
|
+
type: "danger",
|
|
1927
|
+
message: e.message || "Failed exporting CSV"
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
},
|
|
1931
|
+
children: "Export CSV"
|
|
1932
|
+
}
|
|
1933
|
+
),
|
|
1486
1934
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: col.isSingleType ? "1 single entry" : filterOperator !== "none" ? `${matchingColCount} matching of ${totalEntries} entries` : `${totalEntries} entries` })
|
|
1487
1935
|
] })
|
|
1488
1936
|
] }),
|
|
@@ -2009,6 +2457,8 @@ const BulkUpdater = () => {
|
|
|
2009
2457
|
]
|
|
2010
2458
|
}
|
|
2011
2459
|
),
|
|
2460
|
+
activeTab === "rules" && /* @__PURE__ */ jsxRuntime.jsx(ValidationRulesTab.ValidationRulesTab, {}),
|
|
2461
|
+
activeTab === "csv" && /* @__PURE__ */ jsxRuntime.jsx(CsvManagerTab, {}),
|
|
2012
2462
|
showConfirmModal && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2013
2463
|
"div",
|
|
2014
2464
|
{
|
|
@@ -2053,20 +2503,10 @@ const BulkUpdater = () => {
|
|
|
2053
2503
|
borderBottom: "1px solid rgba(120, 120, 150, 0.2)",
|
|
2054
2504
|
flexShrink: 0
|
|
2055
2505
|
},
|
|
2056
|
-
children: /* @__PURE__ */ jsxRuntime.
|
|
2057
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
] }),
|
|
2061
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2062
|
-
designSystem.Badge,
|
|
2063
|
-
{
|
|
2064
|
-
backgroundColor: license?.tier === "enterprise" ? "primary100" : "neutral200",
|
|
2065
|
-
textColor: license?.tier === "enterprise" ? "primary700" : "neutral700",
|
|
2066
|
-
children: license?.tier === "enterprise" ? "ENTERPRISE 4-EYES" : "STANDARD"
|
|
2067
|
-
}
|
|
2068
|
-
)
|
|
2069
|
-
] })
|
|
2506
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
|
|
2507
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", fontWeight: "bold", children: "Confirm Bulk Update" }),
|
|
2508
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", display: "block", children: "Review target attributes, affected counts, and server execution policy before proceeding." })
|
|
2509
|
+
] }) })
|
|
2070
2510
|
}
|
|
2071
2511
|
),
|
|
2072
2512
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -2210,7 +2650,7 @@ const BulkUpdater = () => {
|
|
|
2210
2650
|
children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
|
|
2211
2651
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", size: "M", onClick: () => setShowConfirmModal(false), children: "Cancel" }),
|
|
2212
2652
|
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
|
|
2213
|
-
|
|
2653
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2214
2654
|
designSystem.Button,
|
|
2215
2655
|
{
|
|
2216
2656
|
variant: executionWindow?.isBusinessHours ? "default" : "secondary",
|
|
@@ -2224,7 +2664,7 @@ const BulkUpdater = () => {
|
|
|
2224
2664
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2225
2665
|
designSystem.Button,
|
|
2226
2666
|
{
|
|
2227
|
-
variant:
|
|
2667
|
+
variant: "secondary",
|
|
2228
2668
|
size: "M",
|
|
2229
2669
|
onClick: () => executeBatchWithMode("queue_offpeak"),
|
|
2230
2670
|
loading: updating,
|
|
@@ -2271,4 +2711,4 @@ const BulkUpdater = () => {
|
|
|
2271
2711
|
exports.default = BulkUpdater;
|
|
2272
2712
|
exports.formatDisplayValue = formatDisplayValue;
|
|
2273
2713
|
exports.getAvailableColumnsForCollection = getAvailableColumnsForCollection;
|
|
2274
|
-
//# sourceMappingURL=BulkUpdater-
|
|
2714
|
+
//# sourceMappingURL=BulkUpdater-BRcMQGP3.js.map
|