@lightdash/cli 1.54.0 → 1.55.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/dist/.tsbuildinfo +1 -1
- package/dist/handlers/apps/appCodeFiles.d.ts +5 -0
- package/dist/handlers/apps/appCodeFiles.d.ts.map +1 -1
- package/dist/handlers/apps/appCodeFiles.js +41 -1
- package/dist/handlers/apps/appCodeFiles.test.js +60 -0
- package/dist/handlers/apps/appsDownload.d.ts +49 -0
- package/dist/handlers/apps/appsDownload.d.ts.map +1 -1
- package/dist/handlers/apps/appsDownload.js +90 -1
- package/dist/handlers/apps/appsDownload.test.js +231 -0
- package/dist/handlers/download.d.ts +16 -1
- package/dist/handlers/download.d.ts.map +1 -1
- package/dist/handlers/download.js +334 -198
- package/dist/handlers/download.test.js +92 -1
- package/dist/vendor/authoring/AGENTS.md.tmpl +1 -1
- package/dist/vendor/authoring/README.md.tmpl +1 -1
- package/dist/vendor/authoring/developing-data-apps-locally/SKILL.md +3 -3
- package/dist/vendor/template/package.json +1 -1
- package/package.json +3 -3
|
@@ -331,6 +331,15 @@ const extractChartSlugsFromDashboards = (dashboards) => dashboards.reduce((acc,
|
|
|
331
331
|
.filter((slug) => slug !== undefined);
|
|
332
332
|
return [...acc, ...slugs];
|
|
333
333
|
}, []);
|
|
334
|
+
const extractAppSlugsFromDashboards = (dashboards) => [
|
|
335
|
+
...new Set(dashboards.flatMap((dashboard) => dashboard.tiles.reduce((acc, tile) => {
|
|
336
|
+
if (tile.type !== common_1.DashboardTileTypes.DATA_APP)
|
|
337
|
+
return acc;
|
|
338
|
+
return tile.properties.appSlug
|
|
339
|
+
? [...acc, tile.properties.appSlug]
|
|
340
|
+
: acc;
|
|
341
|
+
}, []))),
|
|
342
|
+
];
|
|
334
343
|
const downloadContent = async (ids, type, projectId, projectName, customPath, languageMap = false, nested = false, skipSpaces = false, stripPivotSeries = false, rootSpaces = false, onProgress) => {
|
|
335
344
|
const contentFilters = parseContentFilters(ids);
|
|
336
345
|
const folderScheme = nested ? 'nested' : 'flat';
|
|
@@ -338,6 +347,7 @@ const downloadContent = async (ids, type, projectId, projectName, customPath, la
|
|
|
338
347
|
let offset = 0;
|
|
339
348
|
let total = 0;
|
|
340
349
|
let chartSlugs = [];
|
|
350
|
+
let appSlugs = [];
|
|
341
351
|
let allMetadataEntries = [];
|
|
342
352
|
let allSpaces = [];
|
|
343
353
|
do {
|
|
@@ -399,6 +409,10 @@ const downloadContent = async (ids, type, projectId, projectName, customPath, la
|
|
|
399
409
|
...chartSlugs,
|
|
400
410
|
...extractChartSlugsFromDashboards(results.dashboards),
|
|
401
411
|
];
|
|
412
|
+
appSlugs = [
|
|
413
|
+
...appSlugs,
|
|
414
|
+
...extractAppSlugsFromDashboards(results.dashboards),
|
|
415
|
+
];
|
|
402
416
|
}
|
|
403
417
|
else {
|
|
404
418
|
const chartsBySpace = groupBySpace(results.charts);
|
|
@@ -432,7 +446,13 @@ const downloadContent = async (ids, type, projectId, projectName, customPath, la
|
|
|
432
446
|
];
|
|
433
447
|
await (0, spacesAsCode_1.writeSpaceFiles)(uniqueSpaces, projectName, customPath, folderScheme, rootSpaces ? 'root' : 'folder', true);
|
|
434
448
|
}
|
|
435
|
-
return
|
|
449
|
+
return {
|
|
450
|
+
total,
|
|
451
|
+
chartSlugs: [...new Set(chartSlugs)],
|
|
452
|
+
appSlugs: [...new Set(appSlugs)],
|
|
453
|
+
metadataEntries: allMetadataEntries,
|
|
454
|
+
spaces: allSpaces,
|
|
455
|
+
};
|
|
436
456
|
};
|
|
437
457
|
exports.downloadContent = downloadContent;
|
|
438
458
|
const getScheduledDeliveriesFolder = (customPath) => path.join((0, contentAsCodePaths_1.getDownloadFolder)(customPath), projectResources_1.SCHEDULED_DELIVERY_CODE_RESOURCE.folderName);
|
|
@@ -836,8 +856,8 @@ const upsertScheduledContent = async (projectId, slugs, changes, force, contentT
|
|
|
836
856
|
};
|
|
837
857
|
// Space-scoped fallback listing for servers without the project-wide apps
|
|
838
858
|
// endpoint; omits apps that were never added to a space.
|
|
839
|
-
const
|
|
840
|
-
const
|
|
859
|
+
const listAppsViaContentApi = async (projectId) => {
|
|
860
|
+
const listedApps = [];
|
|
841
861
|
let page = 1;
|
|
842
862
|
let totalPageCount = 1;
|
|
843
863
|
do {
|
|
@@ -846,13 +866,13 @@ const listAppUuidsViaContentApi = async (projectId) => {
|
|
|
846
866
|
url: `/api/v2/content?projectUuids=${projectId}&contentTypes=data_app&page=${page}&pageSize=100`,
|
|
847
867
|
body: undefined,
|
|
848
868
|
});
|
|
849
|
-
|
|
869
|
+
listedApps.push(...contentResult.data
|
|
850
870
|
.filter((item) => item.contentType === 'data_app')
|
|
851
|
-
.map((item) => item.uuid));
|
|
871
|
+
.map((item) => ({ appUuid: item.uuid, slug: item.slug })));
|
|
852
872
|
totalPageCount = contentResult.pagination?.totalPageCount ?? 1;
|
|
853
873
|
page += 1;
|
|
854
874
|
} while (page <= totalPageCount);
|
|
855
|
-
return
|
|
875
|
+
return listedApps;
|
|
856
876
|
};
|
|
857
877
|
const downloadHandler = async (options) => {
|
|
858
878
|
globalState_1.default.setVerbose(options.verbose);
|
|
@@ -966,6 +986,13 @@ const downloadHandler = async (options) => {
|
|
|
966
986
|
});
|
|
967
987
|
try {
|
|
968
988
|
let allMetadataEntries = [];
|
|
989
|
+
// Shared across both apps-download steps so two different apps whose
|
|
990
|
+
// names collide under the pre-slug fallback naming don't clobber each other.
|
|
991
|
+
const downloadedAppFolders = new Set();
|
|
992
|
+
// App slugs referenced by downloaded dashboards' tiles, populated by
|
|
993
|
+
// the Dashboards step and consumed by the Linked data apps step.
|
|
994
|
+
let dashboardAppSlugs = [];
|
|
995
|
+
const explicitAppRefs = new Set((Array.isArray(options.apps) ? options.apps : []).map(appsDownload_1.getDataAppReference));
|
|
969
996
|
if (shouldDownloadSpaces) {
|
|
970
997
|
output.startItem('Spaces');
|
|
971
998
|
try {
|
|
@@ -999,19 +1026,19 @@ const downloadHandler = async (options) => {
|
|
|
999
1026
|
globalState_1.default.log(styles.warning(`No charts filters provided, skipping`));
|
|
1000
1027
|
}
|
|
1001
1028
|
else {
|
|
1002
|
-
const
|
|
1029
|
+
const { total: regularChartTotal, metadataEntries: regularChartMeta, } = await output.runItem({
|
|
1003
1030
|
label: 'Charts',
|
|
1004
1031
|
action: () => (0, exports.downloadContent)(options.charts, 'charts', projectId, projectName, options.path, options.languageMap, options.nested, skipEmbeddedSpaces, options.stripPivotSeries, options.rootSpaces, output.updateActive),
|
|
1005
|
-
detail: (
|
|
1032
|
+
detail: ({ total }) => `${total} downloaded`,
|
|
1006
1033
|
});
|
|
1007
1034
|
allMetadataEntries = [
|
|
1008
1035
|
...allMetadataEntries,
|
|
1009
1036
|
...regularChartMeta,
|
|
1010
1037
|
];
|
|
1011
|
-
const
|
|
1038
|
+
const { total: sqlChartTotal, metadataEntries: sqlChartMeta } = await output.runItem({
|
|
1012
1039
|
label: 'SQL charts',
|
|
1013
1040
|
action: () => (0, exports.downloadContent)(options.charts, 'sqlCharts', projectId, projectName, options.path, options.languageMap, options.nested, skipEmbeddedSpaces, false, options.rootSpaces, output.updateActive),
|
|
1014
|
-
detail: (
|
|
1041
|
+
detail: ({ total }) => `${total} downloaded`,
|
|
1015
1042
|
});
|
|
1016
1043
|
allMetadataEntries = [...allMetadataEntries, ...sqlChartMeta];
|
|
1017
1044
|
chartTotal = regularChartTotal + sqlChartTotal;
|
|
@@ -1024,30 +1051,38 @@ const downloadHandler = async (options) => {
|
|
|
1024
1051
|
}
|
|
1025
1052
|
else {
|
|
1026
1053
|
let chartSlugs = [];
|
|
1054
|
+
let appSlugs = [];
|
|
1027
1055
|
let dashMeta;
|
|
1028
|
-
|
|
1056
|
+
({
|
|
1057
|
+
total: dashboardTotal,
|
|
1058
|
+
chartSlugs,
|
|
1059
|
+
appSlugs,
|
|
1060
|
+
metadataEntries: dashMeta,
|
|
1061
|
+
} = await output.runItem({
|
|
1029
1062
|
label: 'Dashboards',
|
|
1030
1063
|
action: () => (0, exports.downloadContent)(options.dashboards, 'dashboards', projectId, projectName, options.path, options.languageMap, options.nested, skipEmbeddedSpaces, false, options.rootSpaces, output.updateActive),
|
|
1031
|
-
detail: (
|
|
1032
|
-
});
|
|
1064
|
+
detail: ({ total }) => `${total} downloaded`,
|
|
1065
|
+
}));
|
|
1033
1066
|
allMetadataEntries = [...allMetadataEntries, ...dashMeta];
|
|
1034
1067
|
if (hasFilters &&
|
|
1035
1068
|
chartSlugs.length > 0 &&
|
|
1036
1069
|
!options.skipCharts) {
|
|
1037
1070
|
output.startItem('Linked charts');
|
|
1038
1071
|
output.updateActive(`${chartSlugs.length} dashboard dependencies`);
|
|
1039
|
-
const
|
|
1072
|
+
const { total: regularCharts, metadataEntries: linkedChartMeta, } = await (0, exports.downloadContent)(chartSlugs, 'charts', projectId, projectName, options.path, options.languageMap, options.nested, skipEmbeddedSpaces, options.stripPivotSeries, options.rootSpaces, output.updateActive);
|
|
1040
1073
|
allMetadataEntries = [
|
|
1041
1074
|
...allMetadataEntries,
|
|
1042
1075
|
...linkedChartMeta,
|
|
1043
1076
|
];
|
|
1044
|
-
const
|
|
1077
|
+
const { total: sqlCharts, metadataEntries: linkedSqlMeta } = await (0, exports.downloadContent)(chartSlugs, 'sqlCharts', projectId, projectName, options.path, options.languageMap, options.nested, skipEmbeddedSpaces, false, options.rootSpaces, output.updateActive);
|
|
1045
1078
|
allMetadataEntries = [
|
|
1046
1079
|
...allMetadataEntries,
|
|
1047
1080
|
...linkedSqlMeta,
|
|
1048
1081
|
];
|
|
1049
1082
|
output.completeItem(`${regularCharts + sqlCharts} downloaded`);
|
|
1050
1083
|
}
|
|
1084
|
+
// Consumed after the explicit apps step (see cappedAppSlugs).
|
|
1085
|
+
dashboardAppSlugs = appSlugs;
|
|
1051
1086
|
}
|
|
1052
1087
|
}
|
|
1053
1088
|
if (!options.spacesOnly && shouldDownloadAiAgents(options)) {
|
|
@@ -1106,6 +1141,9 @@ const downloadHandler = async (options) => {
|
|
|
1106
1141
|
apps: Array.isArray(options.apps) ? options.apps : undefined,
|
|
1107
1142
|
includeApps,
|
|
1108
1143
|
});
|
|
1144
|
+
// Slugs covered by a (possibly --apps-limit-truncated) --include-apps
|
|
1145
|
+
// listing, so the Linked data apps step knows what fell outside the cap.
|
|
1146
|
+
let cappedAppSlugs = new Set();
|
|
1109
1147
|
if (appsSelection.mode !== 'none') {
|
|
1110
1148
|
output.startItem('Data apps');
|
|
1111
1149
|
let appRefsToDownload;
|
|
@@ -1116,14 +1154,17 @@ const downloadHandler = async (options) => {
|
|
|
1116
1154
|
else {
|
|
1117
1155
|
// List every app in the project (includes apps not in any space)
|
|
1118
1156
|
output.updateActive('listing project apps…');
|
|
1119
|
-
let
|
|
1157
|
+
let listedApps;
|
|
1120
1158
|
try {
|
|
1121
1159
|
const projectApps = await (0, apiClient_1.lightdashApi)({
|
|
1122
1160
|
method: 'GET',
|
|
1123
1161
|
url: `/api/v1/ee/projects/${projectId}/apps`,
|
|
1124
1162
|
body: undefined,
|
|
1125
1163
|
});
|
|
1126
|
-
|
|
1164
|
+
listedApps = projectApps.map((app) => ({
|
|
1165
|
+
appUuid: app.appUuid,
|
|
1166
|
+
slug: app.slug,
|
|
1167
|
+
}));
|
|
1127
1168
|
}
|
|
1128
1169
|
catch (listErr) {
|
|
1129
1170
|
if (!(0, appsDownload_1.shouldFallBackToSpaceScopedListing)(listErr)) {
|
|
@@ -1131,18 +1172,21 @@ const downloadHandler = async (options) => {
|
|
|
1131
1172
|
throw listErr;
|
|
1132
1173
|
}
|
|
1133
1174
|
appListingError = (0, common_1.getErrorMessage)(listErr);
|
|
1134
|
-
|
|
1175
|
+
listedApps = [];
|
|
1135
1176
|
}
|
|
1136
1177
|
else {
|
|
1137
1178
|
globalState_1.default.log(styles.warning('This server does not support project-wide app listing; only apps that are in a space will be included.'));
|
|
1138
|
-
|
|
1139
|
-
await listAppUuidsViaContentApi(projectId);
|
|
1179
|
+
listedApps = await listAppsViaContentApi(projectId);
|
|
1140
1180
|
}
|
|
1141
1181
|
}
|
|
1142
|
-
const { appUuids: cappedAppUuids, truncatedCount } = (0, appsDownload_1.capListedApps)(
|
|
1182
|
+
const { appUuids: cappedAppUuids, truncatedCount } = (0, appsDownload_1.capListedApps)(listedApps.map((app) => app.appUuid), appsLimit);
|
|
1143
1183
|
if (truncatedCount > 0) {
|
|
1144
|
-
globalState_1.default.log(styles.warning(`Found ${
|
|
1184
|
+
globalState_1.default.log(styles.warning(`Found ${listedApps.length} data apps, downloading the first ${appsLimit}. Pass --apps-limit <n> to raise the cap.`));
|
|
1145
1185
|
}
|
|
1186
|
+
const cappedAppUuidSet = new Set(cappedAppUuids);
|
|
1187
|
+
cappedAppSlugs = new Set(listedApps
|
|
1188
|
+
.filter((app) => cappedAppUuidSet.has(app.appUuid))
|
|
1189
|
+
.map((app) => app.slug));
|
|
1146
1190
|
appRefsToDownload = [
|
|
1147
1191
|
...new Set([
|
|
1148
1192
|
...cappedAppUuids,
|
|
@@ -1162,71 +1206,62 @@ const downloadHandler = async (options) => {
|
|
|
1162
1206
|
output.updateActive(`0 of ${appRefsToDownload.length} downloaded`);
|
|
1163
1207
|
const baseDir = (0, contentAsCodePaths_1.getDownloadFolder)(options.path);
|
|
1164
1208
|
const appsDir = path.join(baseDir, 'apps');
|
|
1165
|
-
const
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
scaffoldingVersion: env_1.CLI_VERSION,
|
|
1183
|
-
};
|
|
1184
|
-
// eslint-disable-next-line no-await-in-loop
|
|
1185
|
-
await (0, appCodeFiles_1.writeBundleToDir)(appDir, { ...code, manifest });
|
|
1186
|
-
// eslint-disable-next-line no-await-in-loop
|
|
1187
|
-
await (0, appCodeFiles_1.writeFilesToDir)(appDir, (0, scaffolding_1.buildStaticAuthoringFiles)({
|
|
1188
|
-
appName: code.manifest.name,
|
|
1189
|
-
sdkVersion: env_1.CLI_VERSION,
|
|
1190
|
-
}));
|
|
1191
|
-
// Server-provided deps override the scaffold's
|
|
1192
|
-
// template package.json so re-uploads round-trip.
|
|
1193
|
-
if (code.dependencies) {
|
|
1194
|
-
// eslint-disable-next-line no-await-in-loop
|
|
1195
|
-
await (0, appCodeFiles_1.writeDependenciesToDir)(appDir, code.dependencies);
|
|
1196
|
-
}
|
|
1197
|
-
// eslint-disable-next-line no-await-in-loop
|
|
1198
|
-
await (0, appCodeFiles_1.writeContextToDir)(appDir, code.context);
|
|
1199
|
-
appSuccessCount += 1;
|
|
1200
|
-
}
|
|
1201
|
-
catch (appErr) {
|
|
1202
|
-
const outcome = (0, appsDownload_1.classifyAppDownloadError)(appErr);
|
|
1203
|
-
if (outcome.kind === 'skip-not-built') {
|
|
1204
|
-
appSkippedNotBuiltCount += 1;
|
|
1205
|
-
globalState_1.default.debug(`> Skipped app ${appRef}: no built version to download`);
|
|
1206
|
-
}
|
|
1207
|
-
else {
|
|
1208
|
-
appFailures.push({
|
|
1209
|
-
appRef,
|
|
1210
|
-
message: outcome.message,
|
|
1211
|
-
});
|
|
1212
|
-
globalState_1.default.log(styles.error(`Failed to download app ${appRef}: ${outcome.message}`));
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
output.updateActive(`${appSuccessCount +
|
|
1216
|
-
appSkippedNotBuiltCount +
|
|
1217
|
-
appFailures.length} of ${appRefsToDownload.length} processed`);
|
|
1218
|
-
}
|
|
1219
|
-
const summary = (0, appsDownload_1.appsDownloadSummary)(appSuccessCount, appRefsToDownload.length, appFailures, appsDir, appSkippedNotBuiltCount);
|
|
1220
|
-
output.completeItem(`${appSuccessCount} downloaded${appSkippedNotBuiltCount > 0
|
|
1221
|
-
? `, ${appSkippedNotBuiltCount} skipped`
|
|
1222
|
-
: ''}${appFailures.length > 0
|
|
1223
|
-
? `, ${appFailures.length} failed`
|
|
1224
|
-
: ''}`, summary.ok ? undefined : 'warning');
|
|
1209
|
+
const { successCount, skippedNotBuiltCount, failures } = await (0, appsDownload_1.downloadAppsToDir)({
|
|
1210
|
+
appRefs: appRefsToDownload,
|
|
1211
|
+
projectId,
|
|
1212
|
+
appsDir,
|
|
1213
|
+
takenFolders: downloadedAppFolders,
|
|
1214
|
+
cliVersion: env_1.CLI_VERSION,
|
|
1215
|
+
fetchApp: (fetchProjectId, appRef) => (0, apiClient_1.lightdashApi)({
|
|
1216
|
+
method: 'GET',
|
|
1217
|
+
url: `/api/v1/ee/projects/${fetchProjectId}/apps/${encodeURIComponent(appRef)}/download`,
|
|
1218
|
+
body: undefined,
|
|
1219
|
+
}),
|
|
1220
|
+
onProgress: (processed, total) => output.updateActive(`${processed} of ${total} processed`),
|
|
1221
|
+
});
|
|
1222
|
+
const summary = (0, appsDownload_1.appsDownloadSummary)(successCount, appRefsToDownload.length, failures, appsDir, skippedNotBuiltCount);
|
|
1223
|
+
output.completeItem(`${successCount} downloaded${skippedNotBuiltCount > 0
|
|
1224
|
+
? `, ${skippedNotBuiltCount} skipped`
|
|
1225
|
+
: ''}${failures.length > 0 ? `, ${failures.length} failed` : ''}`, summary.ok ? undefined : 'warning');
|
|
1225
1226
|
if (!summary.ok) {
|
|
1226
1227
|
summary.failureLines.forEach((line) => globalState_1.default.log(styles.warning(line)));
|
|
1227
1228
|
}
|
|
1228
1229
|
}
|
|
1229
1230
|
}
|
|
1231
|
+
// Dashboard-referenced apps not already covered above (explicit
|
|
1232
|
+
// --apps ref, or a non-truncated slot in the --include-apps cap).
|
|
1233
|
+
const linkedAppSlugs = (0, appsDownload_1.computeLinkedAppSlugs)({
|
|
1234
|
+
appSlugs: dashboardAppSlugs,
|
|
1235
|
+
explicitRefs: explicitAppRefs,
|
|
1236
|
+
includeApps,
|
|
1237
|
+
cappedAppSlugs,
|
|
1238
|
+
});
|
|
1239
|
+
if (linkedAppSlugs.length > 0) {
|
|
1240
|
+
output.startItem('Linked data apps');
|
|
1241
|
+
const appsDir = path.join((0, contentAsCodePaths_1.getDownloadFolder)(options.path), 'apps');
|
|
1242
|
+
const outcome = await (0, appsDownload_1.downloadAppsToDir)({
|
|
1243
|
+
appRefs: linkedAppSlugs,
|
|
1244
|
+
projectId,
|
|
1245
|
+
appsDir,
|
|
1246
|
+
takenFolders: downloadedAppFolders,
|
|
1247
|
+
cliVersion: env_1.CLI_VERSION,
|
|
1248
|
+
fetchApp: (fetchProjectId, appRef) => (0, apiClient_1.lightdashApi)({
|
|
1249
|
+
method: 'GET',
|
|
1250
|
+
url: `/api/v1/ee/projects/${fetchProjectId}/apps/${encodeURIComponent(appRef)}/download`,
|
|
1251
|
+
body: undefined,
|
|
1252
|
+
}),
|
|
1253
|
+
onProgress: (processed, total) => output.updateActive(`${processed} of ${total} processed`),
|
|
1254
|
+
});
|
|
1255
|
+
const linkedSummary = (0, appsDownload_1.appsDownloadSummary)(outcome.successCount, linkedAppSlugs.length, outcome.failures, appsDir, outcome.skippedNotBuiltCount);
|
|
1256
|
+
output.completeItem(`${outcome.successCount} downloaded${outcome.skippedNotBuiltCount > 0
|
|
1257
|
+
? `, ${outcome.skippedNotBuiltCount} skipped`
|
|
1258
|
+
: ''}${outcome.failures.length > 0
|
|
1259
|
+
? `, ${outcome.failures.length} failed`
|
|
1260
|
+
: ''}`, linkedSummary.ok ? undefined : 'warning');
|
|
1261
|
+
if (!linkedSummary.ok) {
|
|
1262
|
+
linkedSummary.failureLines.forEach((line) => globalState_1.default.log(styles.warning(line)));
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1230
1265
|
// Write metadata file with all downloadedAt timestamps
|
|
1231
1266
|
const metadataToWrite = {
|
|
1232
1267
|
version: 1,
|
|
@@ -1364,6 +1399,7 @@ const upsertSingleItem = async (item, type, projectId, changes, force, config, s
|
|
|
1364
1399
|
}),
|
|
1365
1400
|
});
|
|
1366
1401
|
globalState_1.default.debug(`${type} "${item.name}": ${upsertData[type]?.[0].action}`);
|
|
1402
|
+
(upsertData.warnings ?? []).forEach((warning) => globalState_1.default.log(styles.warning(` ⚠ ${item.slug}: ${warning}`)));
|
|
1367
1403
|
// Merge storeUploadChanges result into changes in-place
|
|
1368
1404
|
const updatedChanges = storeUploadChanges(changes, upsertData);
|
|
1369
1405
|
Object.keys(updatedChanges).forEach((key) => {
|
|
@@ -1524,21 +1560,31 @@ const upsertResources = async (type, projectId, changes, force, slugs, canUpload
|
|
|
1524
1560
|
}
|
|
1525
1561
|
return { changes, total: filteredItems.length };
|
|
1526
1562
|
};
|
|
1527
|
-
|
|
1563
|
+
// readCodeFiles walks the whole download folder recursively, so callers that
|
|
1564
|
+
// need both chart and app slugs should read once and reuse the result.
|
|
1565
|
+
const readDashboardItems = async (customPath, looseDashboards = []) => {
|
|
1528
1566
|
const folderDashboards = await readCodeFiles('dashboards', customPath);
|
|
1529
|
-
|
|
1530
|
-
const filteredDashboardItems = dashboardSlugs.length > 0
|
|
1531
|
-
? dashboardItems.filter((dashboard) => dashboardSlugs.includes(dashboard.slug))
|
|
1532
|
-
: dashboardItems;
|
|
1533
|
-
return filteredDashboardItems.reduce((acc, dashboard) => {
|
|
1534
|
-
const dashboardChartSlugs = dashboard.tiles
|
|
1535
|
-
.map((tile) => 'chartSlug' in tile.properties
|
|
1536
|
-
? tile.properties.chartSlug
|
|
1537
|
-
: undefined)
|
|
1538
|
-
.filter((dashboardChartSlug) => !!dashboardChartSlug);
|
|
1539
|
-
return [...acc, ...dashboardChartSlugs];
|
|
1540
|
-
}, []);
|
|
1567
|
+
return [...folderDashboards, ...looseDashboards];
|
|
1541
1568
|
};
|
|
1569
|
+
const selectDashboards = (dashboardItems, dashboardSlugs) => dashboardSlugs.length > 0
|
|
1570
|
+
? dashboardItems.filter((dashboard) => dashboardSlugs.includes(dashboard.slug))
|
|
1571
|
+
: dashboardItems;
|
|
1572
|
+
const selectDashboardChartSlugs = (dashboardItems, dashboardSlugs) => selectDashboards(dashboardItems, dashboardSlugs).reduce((acc, dashboard) => {
|
|
1573
|
+
const dashboardChartSlugs = dashboard.tiles
|
|
1574
|
+
.map((tile) => 'chartSlug' in tile.properties
|
|
1575
|
+
? tile.properties.chartSlug
|
|
1576
|
+
: undefined)
|
|
1577
|
+
.filter((dashboardChartSlug) => !!dashboardChartSlug);
|
|
1578
|
+
return [...acc, ...dashboardChartSlugs];
|
|
1579
|
+
}, []);
|
|
1580
|
+
const selectDashboardAppSlugs = (dashboardItems, dashboardSlugs) => [
|
|
1581
|
+
...new Set(extractAppSlugsFromDashboards(selectDashboards(dashboardItems, dashboardSlugs))),
|
|
1582
|
+
];
|
|
1583
|
+
const getDashboardChartSlugs = async (dashboardSlugs, customPath, looseDashboards = []) => selectDashboardChartSlugs(await readDashboardItems(customPath, looseDashboards), dashboardSlugs);
|
|
1584
|
+
const getDashboardAppSlugs = async (dashboardSlugs, customPath, looseDashboards = []) => selectDashboardAppSlugs(await readDashboardItems(customPath, looseDashboards), dashboardSlugs);
|
|
1585
|
+
// Mirrors the Dashboards phase's own guard: a filtered upload with no
|
|
1586
|
+
// dashboard slugs uploads no dashboards, so there is nothing to derive from.
|
|
1587
|
+
const isFilteredWithNoDashboards = (hasFilters, dashboardSlugs) => hasFilters && dashboardSlugs.length === 0;
|
|
1542
1588
|
const uploadHandler = async (options) => {
|
|
1543
1589
|
globalState_1.default.setVerbose(options.verbose);
|
|
1544
1590
|
if (options.spacesOnly && options.skipSpaces) {
|
|
@@ -1671,97 +1717,8 @@ const uploadHandler = async (options) => {
|
|
|
1671
1717
|
});
|
|
1672
1718
|
}
|
|
1673
1719
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
label: 'Charts',
|
|
1677
|
-
changes,
|
|
1678
|
-
action: async () => {
|
|
1679
|
-
const chartSlugs = options.includeCharts
|
|
1680
|
-
? Array.from(new Set([
|
|
1681
|
-
...options.charts,
|
|
1682
|
-
...(await getDashboardChartSlugs(options.dashboards, options.path, looseFiles.dashboards)),
|
|
1683
|
-
]))
|
|
1684
|
-
: options.charts;
|
|
1685
|
-
if (hasFilters && chartSlugs.length === 0) {
|
|
1686
|
-
globalState_1.default.log(styles.warning(`No charts filters provided, skipping`));
|
|
1687
|
-
return changes;
|
|
1688
|
-
}
|
|
1689
|
-
const result = await upsertResources('charts', projectId, changes, options.force, chartSlugs, uploadPermissions.charts, options.path, options.skipSpaceCreate, options.public, options.validate, concurrency, looseFiles.charts, spaceNames);
|
|
1690
|
-
chartTotal = result.total;
|
|
1691
|
-
return result.changes;
|
|
1692
|
-
},
|
|
1693
|
-
});
|
|
1694
|
-
changes = await runUploadChangesPhase({
|
|
1695
|
-
output,
|
|
1696
|
-
label: 'Dashboards',
|
|
1697
|
-
changes,
|
|
1698
|
-
action: async () => {
|
|
1699
|
-
if (hasFilters && options.dashboards.length === 0) {
|
|
1700
|
-
globalState_1.default.log(styles.warning(`No dashboard filters provided, skipping`));
|
|
1701
|
-
return changes;
|
|
1702
|
-
}
|
|
1703
|
-
const result = await upsertResources('dashboards', projectId, changes, options.force, options.dashboards, uploadPermissions.dashboards, options.path, options.skipSpaceCreate, options.public, options.validate, concurrency, looseFiles.dashboards, spaceNames);
|
|
1704
|
-
dashboardTotal = result.total;
|
|
1705
|
-
return result.changes;
|
|
1706
|
-
},
|
|
1707
|
-
});
|
|
1708
|
-
if (!options.skipAgents) {
|
|
1709
|
-
if (hasFilters && options.agents.length === 0) {
|
|
1710
|
-
globalState_1.default.log(styles.warning(`No AI agent filters provided, skipping`));
|
|
1711
|
-
}
|
|
1712
|
-
else {
|
|
1713
|
-
try {
|
|
1714
|
-
changes = await runUploadChangesPhase({
|
|
1715
|
-
output,
|
|
1716
|
-
label: 'AI agents',
|
|
1717
|
-
changes,
|
|
1718
|
-
action: () => upsertAiAgents(projectId, options.agents, changes, options.force, options.path, options.agents.length === 0),
|
|
1719
|
-
});
|
|
1720
|
-
}
|
|
1721
|
-
catch (error) {
|
|
1722
|
-
throw new AiAgentAsCodeUploadError(error);
|
|
1723
|
-
}
|
|
1724
|
-
}
|
|
1725
|
-
}
|
|
1726
|
-
if (!options.skipAlerts) {
|
|
1727
|
-
if (hasFilters && options.alerts.length === 0) {
|
|
1728
|
-
globalState_1.default.log(styles.warning(`No alert filters provided, skipping`));
|
|
1729
|
-
}
|
|
1730
|
-
else {
|
|
1731
|
-
changes = await runUploadChangesPhase({
|
|
1732
|
-
output,
|
|
1733
|
-
label: 'Alerts',
|
|
1734
|
-
changes,
|
|
1735
|
-
action: () => upsertScheduledContent(projectId, options.alerts, changes, options.force, common_1.ContentAsCodeType.ALERT, uploadPermissions.alerts, options.path),
|
|
1736
|
-
});
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
if (!options.skipScheduledDeliveries) {
|
|
1740
|
-
if (hasFilters && options.scheduledDeliveries.length === 0) {
|
|
1741
|
-
globalState_1.default.log(styles.warning(`No scheduled delivery filters provided, skipping`));
|
|
1742
|
-
}
|
|
1743
|
-
else {
|
|
1744
|
-
changes = await runUploadChangesPhase({
|
|
1745
|
-
output,
|
|
1746
|
-
label: 'Scheduled deliveries',
|
|
1747
|
-
changes,
|
|
1748
|
-
action: () => upsertScheduledContent(projectId, options.scheduledDeliveries, changes, options.force, common_1.ContentAsCodeType.SCHEDULED_DELIVERY, uploadPermissions.scheduledDeliveries, options.path),
|
|
1749
|
-
});
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
if (!options.skipGoogleSheets) {
|
|
1753
|
-
if (hasFilters && options.googleSheets.length === 0) {
|
|
1754
|
-
globalState_1.default.log(styles.warning(`No Google Sheets sync filters provided, skipping`));
|
|
1755
|
-
}
|
|
1756
|
-
else {
|
|
1757
|
-
changes = await runUploadChangesPhase({
|
|
1758
|
-
output,
|
|
1759
|
-
label: 'Google Sheets syncs',
|
|
1760
|
-
changes,
|
|
1761
|
-
action: () => upsertScheduledContent(projectId, options.googleSheets, changes, options.force, common_1.ContentAsCodeType.GOOGLE_SHEETS_SYNC, uploadPermissions.googleSheets, options.path),
|
|
1762
|
-
});
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1720
|
+
// Apps resolve their external connection links by slug in the target
|
|
1721
|
+
// project, so connections must exist before any app is uploaded.
|
|
1765
1722
|
if (!options.skipExternalConnections) {
|
|
1766
1723
|
if (hasFilters && options.externalConnections.length === 0) {
|
|
1767
1724
|
globalState_1.default.log(styles.warning(`No external connection filters provided, skipping`));
|
|
@@ -1775,28 +1732,72 @@ const uploadHandler = async (options) => {
|
|
|
1775
1732
|
});
|
|
1776
1733
|
}
|
|
1777
1734
|
}
|
|
1778
|
-
//
|
|
1779
|
-
//
|
|
1735
|
+
// Both the Data apps and Charts phases derive slugs from the same
|
|
1736
|
+
// dashboard YAML; read the download folder once and share it.
|
|
1737
|
+
let dashboardItemsPromise;
|
|
1738
|
+
const loadDashboardItems = () => {
|
|
1739
|
+
dashboardItemsPromise =
|
|
1740
|
+
dashboardItemsPromise ??
|
|
1741
|
+
readDashboardItems(options.path, looseFiles.dashboards);
|
|
1742
|
+
return dashboardItemsPromise;
|
|
1743
|
+
};
|
|
1744
|
+
// Upload data apps (enterprise; explicit --apps/--include-apps, or
|
|
1745
|
+
// auto-pushed for a dashboard's apps). Must land before dashboards.
|
|
1780
1746
|
const explicitAppReferences = Array.isArray(options.apps)
|
|
1781
1747
|
? options.apps
|
|
1782
1748
|
: [];
|
|
1783
|
-
const
|
|
1749
|
+
const isExplicitAppSelection = options.includeApps === true || explicitAppReferences.length > 0;
|
|
1750
|
+
const autoPushAppSlugs = isFilteredWithNoDashboards(hasFilters, options.dashboards)
|
|
1751
|
+
? []
|
|
1752
|
+
: selectDashboardAppSlugs(await loadDashboardItems(), options.dashboards);
|
|
1753
|
+
const shouldUploadApps = isExplicitAppSelection || autoPushAppSlugs.length > 0;
|
|
1784
1754
|
let appsCreated = 0;
|
|
1785
1755
|
let appsUpdated = 0;
|
|
1786
1756
|
let appsUnchanged = 0;
|
|
1787
1757
|
let appsFailed = 0;
|
|
1788
1758
|
let appsSkipped = 0;
|
|
1759
|
+
let eeAppRoutesUnavailable = false;
|
|
1789
1760
|
const changesBeforeApps = { ...changes };
|
|
1790
1761
|
if (shouldUploadApps && !uploadPermissions.dataApps) {
|
|
1791
1762
|
output.startItem('Data apps');
|
|
1792
|
-
globalState_1.default.log(styles.
|
|
1763
|
+
globalState_1.default.log(styles.warning(`Skipping data apps: create:DataApp or manage:DataApp permission is required. Dashboard tiles will resolve only if their apps already exist in this project.`));
|
|
1793
1764
|
output.completeItem('permission denied', 'warning');
|
|
1794
1765
|
}
|
|
1795
1766
|
else if (shouldUploadApps) {
|
|
1796
1767
|
output.startItem('Data apps');
|
|
1797
|
-
// --include-apps uploads
|
|
1798
|
-
//
|
|
1799
|
-
const uploadFilter =
|
|
1768
|
+
// Explicit refs filter by slug/appUuid; --include-apps uploads all.
|
|
1769
|
+
// A pure auto-push run applies no filter — gated per folder below.
|
|
1770
|
+
const uploadFilter = isExplicitAppSelection
|
|
1771
|
+
? (0, appsDownload_1.getDataAppUploadFilter)(explicitAppReferences, options.includeApps === true)
|
|
1772
|
+
: null;
|
|
1773
|
+
// The manifest always names the source project, so comparing it
|
|
1774
|
+
// to the target would re-push every run — list the target instead.
|
|
1775
|
+
let presence = {
|
|
1776
|
+
kind: 'unknown',
|
|
1777
|
+
targetProjectUuid: projectId,
|
|
1778
|
+
};
|
|
1779
|
+
// Only the auto-push gate below (skipped entirely when explicit)
|
|
1780
|
+
// reads presence, so an explicit run never needs the listing.
|
|
1781
|
+
if (!isExplicitAppSelection && autoPushAppSlugs.length > 0) {
|
|
1782
|
+
try {
|
|
1783
|
+
const projectApps = await (0, apiClient_1.lightdashApi)({
|
|
1784
|
+
method: 'GET',
|
|
1785
|
+
url: `/api/v1/ee/projects/${projectId}/apps`,
|
|
1786
|
+
body: undefined,
|
|
1787
|
+
});
|
|
1788
|
+
// An older server answers without slugs; that is not an
|
|
1789
|
+
// error, but it is not an answer either.
|
|
1790
|
+
if (projectApps.every((app) => app.slug !== undefined)) {
|
|
1791
|
+
presence = {
|
|
1792
|
+
kind: 'known',
|
|
1793
|
+
slugs: new Set(projectApps.map((app) => app.slug)),
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
catch (listErr) {
|
|
1798
|
+
globalState_1.default.debug(`Could not list target project apps: ${(0, common_1.getErrorMessage)(listErr)}`);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1800
1801
|
const baseDir = (0, contentAsCodePaths_1.getDownloadFolder)(options.path);
|
|
1801
1802
|
const appsDir = path.join(baseDir, 'apps');
|
|
1802
1803
|
let appFolderEntries;
|
|
@@ -1807,7 +1808,6 @@ const uploadHandler = async (options) => {
|
|
|
1807
1808
|
}
|
|
1808
1809
|
catch (err) {
|
|
1809
1810
|
if (err.code === 'ENOENT') {
|
|
1810
|
-
globalState_1.default.log(styles.warning(`No apps directory found at ${appsDir}. Run 'lightdash download --include-apps' first.`));
|
|
1811
1811
|
appFolderEntries = [];
|
|
1812
1812
|
}
|
|
1813
1813
|
else {
|
|
@@ -1816,7 +1816,9 @@ const uploadHandler = async (options) => {
|
|
|
1816
1816
|
}
|
|
1817
1817
|
const subDirs = appFolderEntries.filter((e) => e.isDirectory());
|
|
1818
1818
|
if (subDirs.length === 0) {
|
|
1819
|
-
globalState_1.default.log(styles.warning(
|
|
1819
|
+
globalState_1.default.log(styles.warning(isExplicitAppSelection
|
|
1820
|
+
? `No app folders found in ${appsDir}. Run 'lightdash download --include-apps' first.`
|
|
1821
|
+
: `No app folders found in ${appsDir} for the dashboard(s) being uploaded. Re-run 'lightdash download' to fetch their apps.`));
|
|
1820
1822
|
}
|
|
1821
1823
|
const matchedRefs = new Set();
|
|
1822
1824
|
for (const subDir of subDirs) {
|
|
@@ -1825,13 +1827,41 @@ const uploadHandler = async (options) => {
|
|
|
1825
1827
|
// eslint-disable-next-line no-await-in-loop
|
|
1826
1828
|
const code = await (0, appCodeFiles_1.readBundleFromDir)(folderPath);
|
|
1827
1829
|
if (!(0, appsDownload_1.uploadFilterMatches)(uploadFilter, code.manifest)) {
|
|
1828
|
-
|
|
1830
|
+
const isAutoPushCandidate = code.manifest.slug !== undefined &&
|
|
1831
|
+
autoPushAppSlugs.includes(code.manifest.slug);
|
|
1832
|
+
if (isAutoPushCandidate) {
|
|
1833
|
+
globalState_1.default.log(styles.warning(`Skipping app "${subDir.name}" — excluded by --apps, but a dashboard being uploaded references it. Its tile may fail to resolve unless the app already exists in the target project.`));
|
|
1834
|
+
}
|
|
1835
|
+
else {
|
|
1836
|
+
globalState_1.default.debug(`Skipping app folder "${subDir.name}" (not in filter)`);
|
|
1837
|
+
}
|
|
1829
1838
|
// eslint-disable-next-line no-continue
|
|
1830
1839
|
continue;
|
|
1831
1840
|
}
|
|
1832
1841
|
if (uploadFilter) {
|
|
1833
1842
|
(0, appsDownload_1.matchedUploadRefs)(uploadFilter, code.manifest).forEach((ref) => matchedRefs.add(ref));
|
|
1834
1843
|
}
|
|
1844
|
+
if (!isExplicitAppSelection) {
|
|
1845
|
+
const isAutoPushCandidate = code.manifest.slug !== undefined &&
|
|
1846
|
+
autoPushAppSlugs.includes(code.manifest.slug);
|
|
1847
|
+
if (!isAutoPushCandidate) {
|
|
1848
|
+
// eslint-disable-next-line no-continue
|
|
1849
|
+
continue;
|
|
1850
|
+
}
|
|
1851
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1852
|
+
const folderChanged = await (0, appCodeFiles_1.appFolderNeedsUpdating)(folderPath, code.manifest);
|
|
1853
|
+
if (!(0, appsDownload_1.shouldAutoPushApp)({
|
|
1854
|
+
manifest: code.manifest,
|
|
1855
|
+
presence,
|
|
1856
|
+
folderChanged,
|
|
1857
|
+
force: options.force === true,
|
|
1858
|
+
})) {
|
|
1859
|
+
globalState_1.default.debug(`Skipping app "${subDir.name}" — unchanged and already in the target project`);
|
|
1860
|
+
appsSkipped += 1;
|
|
1861
|
+
// eslint-disable-next-line no-continue
|
|
1862
|
+
continue;
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1835
1865
|
// Read declared dependencies from the app folder (optional).
|
|
1836
1866
|
// eslint-disable-next-line no-await-in-loop
|
|
1837
1867
|
const rawDeps = await (0, appCodeFiles_1.readDependenciesFromDir)(folderPath);
|
|
@@ -1939,10 +1969,17 @@ const uploadHandler = async (options) => {
|
|
|
1939
1969
|
}
|
|
1940
1970
|
}
|
|
1941
1971
|
catch (appErr) {
|
|
1942
|
-
appsFailed += 1;
|
|
1943
1972
|
const status = appErr instanceof common_1.LightdashError
|
|
1944
1973
|
? appErr.statusCode
|
|
1945
1974
|
: undefined;
|
|
1975
|
+
// Auto-push is flag-free, so a server without the EE app
|
|
1976
|
+
// routes must not fail an upload the user never asked for.
|
|
1977
|
+
if (!isExplicitAppSelection && status === 404) {
|
|
1978
|
+
eeAppRoutesUnavailable = true;
|
|
1979
|
+
globalState_1.default.log(styles.warning(`Skipping data apps: the enterprise "data apps" feature is not available on this instance. Dashboard tiles will resolve only if their apps already exist in this project.`));
|
|
1980
|
+
break;
|
|
1981
|
+
}
|
|
1982
|
+
appsFailed += 1;
|
|
1946
1983
|
const hint = status === 404
|
|
1947
1984
|
? ' — the enterprise "data apps" feature may not be enabled on this instance'
|
|
1948
1985
|
: '';
|
|
@@ -1966,7 +2003,12 @@ const uploadHandler = async (options) => {
|
|
|
1966
2003
|
if (appsSkipped > 0)
|
|
1967
2004
|
changes['data apps skipped'] = appsSkipped;
|
|
1968
2005
|
const appSummary = summarizeUploadChanges(changesBeforeApps, changes);
|
|
1969
|
-
|
|
2006
|
+
if (eeAppRoutesUnavailable) {
|
|
2007
|
+
output.completeItem('not available on this server', 'warning');
|
|
2008
|
+
}
|
|
2009
|
+
else {
|
|
2010
|
+
output.completeItem(appSummary.detail, appSummary.variant);
|
|
2011
|
+
}
|
|
1970
2012
|
if (appsFailed > 0) {
|
|
1971
2013
|
// App uploads are fire-and-forget per folder, so failures are
|
|
1972
2014
|
// logged and tallied rather than thrown — but the process must
|
|
@@ -1975,6 +2017,97 @@ const uploadHandler = async (options) => {
|
|
|
1975
2017
|
process.exitCode = 1;
|
|
1976
2018
|
}
|
|
1977
2019
|
}
|
|
2020
|
+
changes = await runUploadChangesPhase({
|
|
2021
|
+
output,
|
|
2022
|
+
label: 'Charts',
|
|
2023
|
+
changes,
|
|
2024
|
+
action: async () => {
|
|
2025
|
+
const chartSlugs = options.includeCharts
|
|
2026
|
+
? Array.from(new Set([
|
|
2027
|
+
...options.charts,
|
|
2028
|
+
...selectDashboardChartSlugs(await loadDashboardItems(), options.dashboards),
|
|
2029
|
+
]))
|
|
2030
|
+
: options.charts;
|
|
2031
|
+
if (hasFilters && chartSlugs.length === 0) {
|
|
2032
|
+
globalState_1.default.log(styles.warning(`No charts filters provided, skipping`));
|
|
2033
|
+
return changes;
|
|
2034
|
+
}
|
|
2035
|
+
const result = await upsertResources('charts', projectId, changes, options.force, chartSlugs, uploadPermissions.charts, options.path, options.skipSpaceCreate, options.public, options.validate, concurrency, looseFiles.charts, spaceNames);
|
|
2036
|
+
chartTotal = result.total;
|
|
2037
|
+
return result.changes;
|
|
2038
|
+
},
|
|
2039
|
+
});
|
|
2040
|
+
changes = await runUploadChangesPhase({
|
|
2041
|
+
output,
|
|
2042
|
+
label: 'Dashboards',
|
|
2043
|
+
changes,
|
|
2044
|
+
action: async () => {
|
|
2045
|
+
if (hasFilters && options.dashboards.length === 0) {
|
|
2046
|
+
globalState_1.default.log(styles.warning(`No dashboard filters provided, skipping`));
|
|
2047
|
+
return changes;
|
|
2048
|
+
}
|
|
2049
|
+
const result = await upsertResources('dashboards', projectId, changes, options.force, options.dashboards, uploadPermissions.dashboards, options.path, options.skipSpaceCreate, options.public, options.validate, concurrency, looseFiles.dashboards, spaceNames);
|
|
2050
|
+
dashboardTotal = result.total;
|
|
2051
|
+
return result.changes;
|
|
2052
|
+
},
|
|
2053
|
+
});
|
|
2054
|
+
if (!options.skipAgents) {
|
|
2055
|
+
if (hasFilters && options.agents.length === 0) {
|
|
2056
|
+
globalState_1.default.log(styles.warning(`No AI agent filters provided, skipping`));
|
|
2057
|
+
}
|
|
2058
|
+
else {
|
|
2059
|
+
try {
|
|
2060
|
+
changes = await runUploadChangesPhase({
|
|
2061
|
+
output,
|
|
2062
|
+
label: 'AI agents',
|
|
2063
|
+
changes,
|
|
2064
|
+
action: () => upsertAiAgents(projectId, options.agents, changes, options.force, options.path, options.agents.length === 0),
|
|
2065
|
+
});
|
|
2066
|
+
}
|
|
2067
|
+
catch (error) {
|
|
2068
|
+
throw new AiAgentAsCodeUploadError(error);
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
if (!options.skipAlerts) {
|
|
2073
|
+
if (hasFilters && options.alerts.length === 0) {
|
|
2074
|
+
globalState_1.default.log(styles.warning(`No alert filters provided, skipping`));
|
|
2075
|
+
}
|
|
2076
|
+
else {
|
|
2077
|
+
changes = await runUploadChangesPhase({
|
|
2078
|
+
output,
|
|
2079
|
+
label: 'Alerts',
|
|
2080
|
+
changes,
|
|
2081
|
+
action: () => upsertScheduledContent(projectId, options.alerts, changes, options.force, common_1.ContentAsCodeType.ALERT, uploadPermissions.alerts, options.path),
|
|
2082
|
+
});
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
if (!options.skipScheduledDeliveries) {
|
|
2086
|
+
if (hasFilters && options.scheduledDeliveries.length === 0) {
|
|
2087
|
+
globalState_1.default.log(styles.warning(`No scheduled delivery filters provided, skipping`));
|
|
2088
|
+
}
|
|
2089
|
+
else {
|
|
2090
|
+
changes = await runUploadChangesPhase({
|
|
2091
|
+
output,
|
|
2092
|
+
label: 'Scheduled deliveries',
|
|
2093
|
+
changes,
|
|
2094
|
+
action: () => upsertScheduledContent(projectId, options.scheduledDeliveries, changes, options.force, common_1.ContentAsCodeType.SCHEDULED_DELIVERY, uploadPermissions.scheduledDeliveries, options.path),
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
if (!options.skipGoogleSheets) {
|
|
2099
|
+
if (hasFilters && options.googleSheets.length === 0) {
|
|
2100
|
+
globalState_1.default.log(styles.warning(`No Google Sheets sync filters provided, skipping`));
|
|
2101
|
+
}
|
|
2102
|
+
else {
|
|
2103
|
+
changes = await runUploadChangesPhase({
|
|
2104
|
+
output,
|
|
2105
|
+
label: 'Google Sheets syncs',
|
|
2106
|
+
changes,
|
|
2107
|
+
action: () => upsertScheduledContent(projectId, options.googleSheets, changes, options.force, common_1.ContentAsCodeType.GOOGLE_SHEETS_SYNC, uploadPermissions.googleSheets, options.path),
|
|
2108
|
+
});
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
1978
2111
|
const end = Date.now();
|
|
1979
2112
|
await analytics_1.LightdashAnalytics.track({
|
|
1980
2113
|
event: 'upload.completed',
|
|
@@ -2009,12 +2142,15 @@ exports.uploadHandler = uploadHandler;
|
|
|
2009
2142
|
exports.testHelpers = {
|
|
2010
2143
|
assertUniqueSpacePaths: spacesAsCode_1.assertUniqueSpacePaths,
|
|
2011
2144
|
downloadSpaces: spacesAsCode_1.downloadSpaces,
|
|
2145
|
+
extractAppSlugsFromDashboards,
|
|
2012
2146
|
getFlatSpaceFileNames: spacesAsCode_1.getFlatSpaceFileNames,
|
|
2147
|
+
getDashboardAppSlugs,
|
|
2013
2148
|
getDashboardChartSlugs,
|
|
2014
2149
|
hasContentFilters,
|
|
2015
2150
|
isAiAgentsUnavailableError,
|
|
2016
2151
|
isExternalConnectionsUnavailableError,
|
|
2017
2152
|
downloadAiAgents,
|
|
2153
|
+
isFilteredWithNoDashboards,
|
|
2018
2154
|
readAiAgentFiles,
|
|
2019
2155
|
readExternalConnectionFiles,
|
|
2020
2156
|
readSpaceFiles: spacesAsCode_1.readSpaceFiles,
|