@lightdash/cli 0.3406.0 → 0.3406.1
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/contentAsCode/fileNames.d.ts +24 -0
- package/dist/handlers/contentAsCode/fileNames.d.ts.map +1 -0
- package/dist/handlers/contentAsCode/fileNames.js +67 -0
- package/dist/handlers/contentAsCode/fileNames.test.d.ts +2 -0
- package/dist/handlers/contentAsCode/fileNames.test.d.ts.map +1 -0
- package/dist/handlers/contentAsCode/fileNames.test.js +79 -0
- package/dist/handlers/contentAsCode/projectResources.d.ts +8 -0
- package/dist/handlers/contentAsCode/projectResources.d.ts.map +1 -0
- package/dist/handlers/contentAsCode/projectResources.js +95 -0
- package/dist/handlers/contentAsCode/registry.d.ts +9 -0
- package/dist/handlers/contentAsCode/registry.d.ts.map +1 -0
- package/dist/handlers/contentAsCode/registry.js +44 -0
- package/dist/handlers/contentAsCode/registry.test.d.ts +2 -0
- package/dist/handlers/contentAsCode/registry.test.d.ts.map +1 -0
- package/dist/handlers/contentAsCode/registry.test.js +18 -0
- package/dist/handlers/contentAsCode/resource.d.ts +67 -0
- package/dist/handlers/contentAsCode/resource.d.ts.map +1 -0
- package/dist/handlers/contentAsCode/resource.js +171 -0
- package/dist/handlers/contentAsCode/resource.test.d.ts +2 -0
- package/dist/handlers/contentAsCode/resource.test.d.ts.map +1 -0
- package/dist/handlers/contentAsCode/resource.test.js +39 -0
- package/dist/handlers/download.d.ts.map +1 -1
- package/dist/handlers/download.js +59 -109
- package/dist/handlers/organizationContent/customRoles.d.ts +4 -3
- package/dist/handlers/organizationContent/customRoles.d.ts.map +1 -1
- package/dist/handlers/organizationContent/customRoles.js +51 -137
- package/dist/handlers/organizationContent/customRoles.test.js +13 -2
- package/dist/handlers/organizationContent/fileNames.d.ts.map +1 -1
- package/dist/handlers/organizationContent/fileNames.js +8 -29
- package/dist/handlers/organizationContent/groups.d.ts +4 -3
- package/dist/handlers/organizationContent/groups.d.ts.map +1 -1
- package/dist/handlers/organizationContent/groups.js +54 -139
- package/dist/handlers/organizationContent/groups.test.js +2 -1
- package/dist/handlers/organizationContent/index.d.ts.map +1 -1
- package/dist/handlers/organizationContent/index.js +7 -22
- package/dist/handlers/organizationContent/users.d.ts +4 -3
- package/dist/handlers/organizationContent/users.d.ts.map +1 -1
- package/dist/handlers/organizationContent/users.js +75 -146
- package/dist/handlers/organizationContent/users.test.js +4 -3
- package/dist/handlers/spacesAsCode.d.ts +1 -1
- package/dist/handlers/spacesAsCode.d.ts.map +1 -1
- package/dist/handlers/spacesAsCode.js +21 -26
- package/package.json +3 -3
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const os_1 = tslib_1.__importDefault(require("os"));
|
|
6
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
7
|
+
const vitest_1 = require("vitest");
|
|
8
|
+
const projectResources_1 = require("./projectResources");
|
|
9
|
+
const resource_1 = require("./resource");
|
|
10
|
+
const temporaryDirectories = [];
|
|
11
|
+
(0, vitest_1.afterEach)(async () => {
|
|
12
|
+
await Promise.all(temporaryDirectories.splice(0).map((directory) => fs_1.promises.rm(directory, {
|
|
13
|
+
recursive: true,
|
|
14
|
+
force: true,
|
|
15
|
+
})));
|
|
16
|
+
});
|
|
17
|
+
(0, vitest_1.describe)('content-as-code resource files', () => {
|
|
18
|
+
(0, vitest_1.it)('ignores other scheduled content types in a shared legacy folder', async () => {
|
|
19
|
+
const basePath = await fs_1.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'lightdash-content-as-code-'));
|
|
20
|
+
temporaryDirectories.push(basePath);
|
|
21
|
+
const alertFolder = path_1.default.join(basePath, 'alerts', 'charts', 'orders');
|
|
22
|
+
await fs_1.promises.mkdir(alertFolder, { recursive: true });
|
|
23
|
+
await Promise.all([
|
|
24
|
+
fs_1.promises.writeFile(path_1.default.join(alertFolder, 'alert.yml'), 'contentType: alert\nversion: 1\nslug: revenue-alert\nname: Revenue alert\n'),
|
|
25
|
+
fs_1.promises.writeFile(path_1.default.join(alertFolder, 'delivery.yml'), 'contentType: scheduled_delivery\nversion: 1\nslug: revenue-delivery\nname: Revenue delivery\n'),
|
|
26
|
+
fs_1.promises.writeFile(path_1.default.join(alertFolder, 'invalid-alert.yml'), 'contentType: alert\nslug: invalid-alert\nname: Invalid alert\n'),
|
|
27
|
+
]);
|
|
28
|
+
const result = await (0, resource_1.readCodeResourceFiles)({
|
|
29
|
+
definition: projectResources_1.ALERT_CODE_RESOURCE,
|
|
30
|
+
basePath,
|
|
31
|
+
});
|
|
32
|
+
(0, vitest_1.expect)(result.files.map(({ document }) => document.slug)).toEqual([
|
|
33
|
+
'revenue-alert',
|
|
34
|
+
]);
|
|
35
|
+
(0, vitest_1.expect)(result.failures).toHaveLength(1);
|
|
36
|
+
(0, vitest_1.expect)(result.failures[0].message).toContain('invalid-alert.yml');
|
|
37
|
+
(0, vitest_1.expect)(result.failures[0].message).toContain('expected version 1');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/handlers/download.ts"],"names":[],"mappings":"AAEA,OAAO,EACH,WAAW,EAuBX,WAAW,EAGX,eAAe,EAef,KAAK,WAAW,EACnB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/handlers/download.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,6 +19,8 @@ const styles = tslib_1.__importStar(require("../styles"));
|
|
|
19
19
|
const appCodeFiles_1 = require("./apps/appCodeFiles");
|
|
20
20
|
const appsDownload_1 = require("./apps/appsDownload");
|
|
21
21
|
const scaffolding_1 = require("./apps/scaffolding");
|
|
22
|
+
const projectResources_1 = require("./contentAsCode/projectResources");
|
|
23
|
+
const resource_1 = require("./contentAsCode/resource");
|
|
22
24
|
const contentAsCodePaths_1 = require("./contentAsCodePaths");
|
|
23
25
|
const apiClient_1 = require("./dbt/apiClient");
|
|
24
26
|
const metadataFile_1 = require("./metadataFile");
|
|
@@ -288,19 +290,19 @@ const getContentTypeConfig = (type, projectId) => {
|
|
|
288
290
|
switch (type) {
|
|
289
291
|
case 'charts':
|
|
290
292
|
return {
|
|
291
|
-
endpoint: `/api/v1/projects/${projectId}/charts
|
|
293
|
+
endpoint: `/api/v1/projects/${projectId}/code/charts`,
|
|
292
294
|
displayName: 'charts',
|
|
293
295
|
supportsLanguageMap: true,
|
|
294
296
|
};
|
|
295
297
|
case 'dashboards':
|
|
296
298
|
return {
|
|
297
|
-
endpoint: `/api/v1/projects/${projectId}/dashboards
|
|
299
|
+
endpoint: `/api/v1/projects/${projectId}/code/dashboards`,
|
|
298
300
|
displayName: 'dashboards',
|
|
299
301
|
supportsLanguageMap: true,
|
|
300
302
|
};
|
|
301
303
|
case 'sqlCharts':
|
|
302
304
|
return {
|
|
303
|
-
endpoint: `/api/v1/projects/${projectId}/sqlCharts
|
|
305
|
+
endpoint: `/api/v1/projects/${projectId}/code/sqlCharts`,
|
|
304
306
|
displayName: 'SQL charts',
|
|
305
307
|
supportsLanguageMap: false,
|
|
306
308
|
};
|
|
@@ -421,55 +423,32 @@ const downloadContent = async (ids, type, projectId, projectName, customPath, la
|
|
|
421
423
|
return [total, [...new Set(chartSlugs)], allMetadataEntries, allSpaces];
|
|
422
424
|
};
|
|
423
425
|
exports.downloadContent = downloadContent;
|
|
424
|
-
const getScheduledDeliveriesFolder = (customPath) => path.join((0, contentAsCodePaths_1.getDownloadFolder)(customPath),
|
|
425
|
-
const
|
|
426
|
-
const
|
|
427
|
-
const getGoogleSheetsFolder = (customPath) => path.join((0, contentAsCodePaths_1.getDownloadFolder)(customPath), 'google-sheets');
|
|
428
|
-
const getVirtualViewsFolder = (customPath) => path.join((0, contentAsCodePaths_1.getDownloadFolder)(customPath), 'virtual-views');
|
|
426
|
+
const getScheduledDeliveriesFolder = (customPath) => path.join((0, contentAsCodePaths_1.getDownloadFolder)(customPath), projectResources_1.SCHEDULED_DELIVERY_CODE_RESOURCE.folderName);
|
|
427
|
+
const getAlertsFolder = (customPath) => path.join((0, contentAsCodePaths_1.getDownloadFolder)(customPath), projectResources_1.ALERT_CODE_RESOURCE.folderName);
|
|
428
|
+
const getGoogleSheetsFolder = (customPath) => path.join((0, contentAsCodePaths_1.getDownloadFolder)(customPath), projectResources_1.GOOGLE_SHEETS_CODE_RESOURCE.folderName);
|
|
429
429
|
const downloadVirtualViews = async (projectId, slugs, customPath) => {
|
|
430
|
-
const folder = getVirtualViewsFolder(customPath);
|
|
431
|
-
await fs_1.promises.mkdir(folder, { recursive: true });
|
|
432
430
|
const query = new URLSearchParams(slugs.map((slug) => ['slugs', slug])).toString();
|
|
433
431
|
const results = await (0, apiClient_1.lightdashApi)({
|
|
434
432
|
method: 'GET',
|
|
435
|
-
url: `/api/v1/projects/${projectId}/virtualViews
|
|
433
|
+
url: `/api/v1/projects/${projectId}/code/virtualViews${query ? `?${query}` : ''}`,
|
|
436
434
|
body: undefined,
|
|
437
435
|
});
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
436
|
+
await (0, resource_1.writeCodeResourceDocuments)({
|
|
437
|
+
definition: projectResources_1.VIRTUAL_VIEW_CODE_RESOURCE,
|
|
438
|
+
basePath: (0, contentAsCodePaths_1.getDownloadFolder)(customPath),
|
|
439
|
+
documents: results.virtualViews,
|
|
440
|
+
});
|
|
441
441
|
results.skipped.forEach(({ slug, reason }) => globalState_1.default.log(styles.warning(`Skipped virtual view "${slug}": ${reason}`)));
|
|
442
442
|
results.missingSlugs.forEach((slug) => globalState_1.default.log(styles.warning(`Virtual view "${slug}" was not found`)));
|
|
443
443
|
return results.virtualViews.length;
|
|
444
444
|
};
|
|
445
445
|
const readVirtualViewFiles = async (customPath) => {
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
(entry.name.endsWith('.yml') || entry.name.endsWith('.yaml'))) {
|
|
453
|
-
const parsed = yaml.load(await fs_1.promises.readFile(path.join(folder, entry.name), 'utf-8'));
|
|
454
|
-
if (parsed.contentType !== common_1.ContentAsCodeType.VIRTUAL_VIEW) {
|
|
455
|
-
throw new common_1.ParameterError(`Invalid virtual view contentType in ${entry.name}`);
|
|
456
|
-
}
|
|
457
|
-
virtualViews.push(parsed);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
const duplicates = Object.entries((0, groupBy_1.default)(virtualViews, 'slug'))
|
|
461
|
-
.filter(([, items]) => items.length > 1)
|
|
462
|
-
.map(([slug]) => slug);
|
|
463
|
-
if (duplicates.length > 0) {
|
|
464
|
-
throw new common_1.ParameterError(`Duplicate virtual view slugs: ${duplicates.join(', ')}`);
|
|
465
|
-
}
|
|
466
|
-
return virtualViews;
|
|
467
|
-
}
|
|
468
|
-
catch (error) {
|
|
469
|
-
if (error.code === 'ENOENT')
|
|
470
|
-
return [];
|
|
471
|
-
throw error;
|
|
472
|
-
}
|
|
446
|
+
const result = await (0, resource_1.readCodeResourceFiles)({
|
|
447
|
+
definition: projectResources_1.VIRTUAL_VIEW_CODE_RESOURCE,
|
|
448
|
+
basePath: (0, contentAsCodePaths_1.getDownloadFolder)(customPath),
|
|
449
|
+
});
|
|
450
|
+
(0, resource_1.assertCodeResourceFilesValid)(result);
|
|
451
|
+
return result.files.map(({ document }) => document);
|
|
473
452
|
};
|
|
474
453
|
const upsertVirtualViews = async (projectId, slugs, changes, force, canUpload, customPath) => {
|
|
475
454
|
const virtualViews = await readVirtualViewFiles(customPath);
|
|
@@ -497,7 +476,7 @@ const upsertVirtualViews = async (projectId, slugs, changes, force, canUpload, c
|
|
|
497
476
|
}
|
|
498
477
|
const result = await (0, apiClient_1.lightdashApi)({
|
|
499
478
|
method: 'POST',
|
|
500
|
-
url: `/api/v1/projects/${projectId}/virtualViews/${encodeURIComponent(virtualView.slug)}
|
|
479
|
+
url: `/api/v1/projects/${projectId}/code/virtualViews/${encodeURIComponent(virtualView.slug)}?force=${force}`,
|
|
501
480
|
body: JSON.stringify(virtualView),
|
|
502
481
|
});
|
|
503
482
|
const action = `virtual views ${getPromoteAction(result.action)}`;
|
|
@@ -516,6 +495,7 @@ const getScheduledContentConfig = (contentType, customPath) => {
|
|
|
516
495
|
case common_1.ContentAsCodeType.ALERT:
|
|
517
496
|
return {
|
|
518
497
|
folder: getAlertsFolder(customPath),
|
|
498
|
+
definition: projectResources_1.ALERT_CODE_RESOURCE,
|
|
519
499
|
route: 'alerts',
|
|
520
500
|
singular: 'alert',
|
|
521
501
|
plural: 'alerts',
|
|
@@ -523,6 +503,7 @@ const getScheduledContentConfig = (contentType, customPath) => {
|
|
|
523
503
|
case common_1.ContentAsCodeType.GOOGLE_SHEETS_SYNC:
|
|
524
504
|
return {
|
|
525
505
|
folder: getGoogleSheetsFolder(customPath),
|
|
506
|
+
definition: projectResources_1.GOOGLE_SHEETS_CODE_RESOURCE,
|
|
526
507
|
route: 'googleSheets',
|
|
527
508
|
singular: 'Google Sheets sync',
|
|
528
509
|
plural: 'Google Sheets syncs',
|
|
@@ -530,6 +511,7 @@ const getScheduledContentConfig = (contentType, customPath) => {
|
|
|
530
511
|
case common_1.ContentAsCodeType.SCHEDULED_DELIVERY:
|
|
531
512
|
return {
|
|
532
513
|
folder: getScheduledDeliveriesFolder(customPath),
|
|
514
|
+
definition: projectResources_1.SCHEDULED_DELIVERY_CODE_RESOURCE,
|
|
533
515
|
route: 'scheduledDeliveries',
|
|
534
516
|
singular: 'scheduled delivery',
|
|
535
517
|
plural: 'scheduled deliveries',
|
|
@@ -539,12 +521,11 @@ const getScheduledContentConfig = (contentType, customPath) => {
|
|
|
539
521
|
}
|
|
540
522
|
};
|
|
541
523
|
const downloadAiAgents = async (projectId, ids, customPath) => {
|
|
542
|
-
const outputDir = getAiAgentsFolder(customPath);
|
|
543
|
-
await fs_1.promises.mkdir(outputDir, { recursive: true });
|
|
544
524
|
const idQuery = ids.map((id) => ['ids', id]);
|
|
545
525
|
let offset = 0;
|
|
546
526
|
let total = 0;
|
|
547
527
|
let downloaded = 0;
|
|
528
|
+
const agents = [];
|
|
548
529
|
do {
|
|
549
530
|
const query = new URLSearchParams([
|
|
550
531
|
...idQuery,
|
|
@@ -552,54 +533,29 @@ const downloadAiAgents = async (projectId, ids, customPath) => {
|
|
|
552
533
|
]).toString();
|
|
553
534
|
const results = await (0, apiClient_1.lightdashApi)({
|
|
554
535
|
method: 'GET',
|
|
555
|
-
url: `/api/v1/projects/${projectId}/aiAgents
|
|
536
|
+
url: `/api/v1/projects/${projectId}/code/aiAgents?${query}`,
|
|
556
537
|
body: undefined,
|
|
557
538
|
});
|
|
558
|
-
|
|
559
|
-
const { updatedAt, downloadedAt, ...agentConfig } = agent;
|
|
560
|
-
// eslint-disable-next-line no-await-in-loop
|
|
561
|
-
await fs_1.promises.writeFile(path.join(outputDir, `${agent.slug}.yml`), yaml.dump(agentConfig, { quotingType: '"', sortKeys: true }));
|
|
562
|
-
}
|
|
539
|
+
agents.push(...results.agents);
|
|
563
540
|
results.missingIds.forEach((id) => globalState_1.default.debug(`No AI agent with id "${id}"`));
|
|
564
541
|
downloaded += results.agents.length;
|
|
565
542
|
offset = results.offset;
|
|
566
543
|
total = results.total;
|
|
567
544
|
} while (offset < total);
|
|
545
|
+
await (0, resource_1.writeCodeResourceDocuments)({
|
|
546
|
+
definition: projectResources_1.AI_AGENT_CODE_RESOURCE,
|
|
547
|
+
basePath: (0, contentAsCodePaths_1.getDownloadFolder)(customPath),
|
|
548
|
+
documents: agents,
|
|
549
|
+
});
|
|
568
550
|
return downloaded;
|
|
569
551
|
};
|
|
570
552
|
const readAiAgentFiles = async (customPath) => {
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const agents = [];
|
|
578
|
-
for (const entry of entries) {
|
|
579
|
-
if (entry.isFile() &&
|
|
580
|
-
(entry.name.endsWith('.yml') || entry.name.endsWith('.yaml'))) {
|
|
581
|
-
const filePath = path.join(entry.parentPath, entry.name);
|
|
582
|
-
// eslint-disable-next-line no-await-in-loop
|
|
583
|
-
const parsed = yaml.load(await fs_1.promises.readFile(filePath, 'utf-8'));
|
|
584
|
-
if (typeof parsed !== 'object' ||
|
|
585
|
-
parsed === null ||
|
|
586
|
-
Array.isArray(parsed)) {
|
|
587
|
-
throw new common_1.ParameterError(`Invalid AI agent file "${filePath}": expected a YAML object`);
|
|
588
|
-
}
|
|
589
|
-
if (!('contentType' in parsed) ||
|
|
590
|
-
parsed.contentType !== common_1.ContentAsCodeType.AI_AGENT) {
|
|
591
|
-
throw new common_1.ParameterError(`Invalid contentType in AI agent file "${filePath}": expected "${common_1.ContentAsCodeType.AI_AGENT}"`);
|
|
592
|
-
}
|
|
593
|
-
agents.push(parsed);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
return agents;
|
|
597
|
-
}
|
|
598
|
-
catch (error) {
|
|
599
|
-
if (error.code === 'ENOENT')
|
|
600
|
-
return [];
|
|
601
|
-
throw error;
|
|
602
|
-
}
|
|
553
|
+
const result = await (0, resource_1.readCodeResourceFiles)({
|
|
554
|
+
definition: projectResources_1.AI_AGENT_CODE_RESOURCE,
|
|
555
|
+
basePath: (0, contentAsCodePaths_1.getDownloadFolder)(customPath),
|
|
556
|
+
});
|
|
557
|
+
(0, resource_1.assertCodeResourceFilesValid)(result);
|
|
558
|
+
return result.files.map(({ document }) => document);
|
|
603
559
|
};
|
|
604
560
|
class AiAgentAsCodeUploadError extends Error {
|
|
605
561
|
originalError;
|
|
@@ -624,7 +580,7 @@ const upsertAiAgents = async (projectId, slugs, changes, force, customPath) => {
|
|
|
624
580
|
globalState_1.default.log(`Found ${filteredAgents.length} AI agent files`);
|
|
625
581
|
const results = await (0, apiClient_1.lightdashApi)({
|
|
626
582
|
method: 'POST',
|
|
627
|
-
url: `/api/v1/projects/${projectId}/aiAgents
|
|
583
|
+
url: `/api/v1/projects/${projectId}/code/aiAgents?force=${force}`,
|
|
628
584
|
body: JSON.stringify({ agents: filteredAgents }),
|
|
629
585
|
});
|
|
630
586
|
const counts = {
|
|
@@ -644,7 +600,7 @@ const downloadScheduledContent = async (projectId, slugs, contentType, customPat
|
|
|
644
600
|
const query = new URLSearchParams(slugs.map((slug) => ['slugs', slug])).toString();
|
|
645
601
|
const results = await (0, apiClient_1.lightdashApi)({
|
|
646
602
|
method: 'GET',
|
|
647
|
-
url: `/api/v1/projects/${projectId}/${config.route}
|
|
603
|
+
url: `/api/v1/projects/${projectId}/code/${config.route}${query ? `?${query}` : ''}`,
|
|
648
604
|
body: undefined,
|
|
649
605
|
});
|
|
650
606
|
let scheduledContent;
|
|
@@ -666,29 +622,23 @@ const downloadScheduledContent = async (projectId, slugs, contentType, customPat
|
|
|
666
622
|
return scheduledContent.length;
|
|
667
623
|
};
|
|
668
624
|
const readScheduledContentFiles = async (contentType, customPath) => {
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
withFileTypes: true,
|
|
625
|
+
const read = async (definition) => {
|
|
626
|
+
const result = await (0, resource_1.readCodeResourceFiles)({
|
|
627
|
+
definition,
|
|
628
|
+
basePath: (0, contentAsCodePaths_1.getDownloadFolder)(customPath),
|
|
674
629
|
});
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
}
|
|
688
|
-
catch (error) {
|
|
689
|
-
if (error.code === 'ENOENT')
|
|
690
|
-
return [];
|
|
691
|
-
throw error;
|
|
630
|
+
(0, resource_1.assertCodeResourceFilesValid)(result);
|
|
631
|
+
return result.files.map(({ document }) => document);
|
|
632
|
+
};
|
|
633
|
+
switch (contentType) {
|
|
634
|
+
case common_1.ContentAsCodeType.ALERT:
|
|
635
|
+
return read(projectResources_1.ALERT_CODE_RESOURCE);
|
|
636
|
+
case common_1.ContentAsCodeType.GOOGLE_SHEETS_SYNC:
|
|
637
|
+
return read(projectResources_1.GOOGLE_SHEETS_CODE_RESOURCE);
|
|
638
|
+
case common_1.ContentAsCodeType.SCHEDULED_DELIVERY:
|
|
639
|
+
return read(projectResources_1.SCHEDULED_DELIVERY_CODE_RESOURCE);
|
|
640
|
+
default:
|
|
641
|
+
return (0, common_1.assertUnreachable)(contentType, 'Unknown scheduled content type');
|
|
692
642
|
}
|
|
693
643
|
};
|
|
694
644
|
const upsertScheduledContent = async (projectId, slugs, changes, force, contentType, canUpload, customPath) => {
|
|
@@ -709,7 +659,7 @@ const upsertScheduledContent = async (projectId, slugs, changes, force, contentT
|
|
|
709
659
|
try {
|
|
710
660
|
const result = await (0, apiClient_1.lightdashApi)({
|
|
711
661
|
method: 'POST',
|
|
712
|
-
url: `/api/v1/projects/${projectId}/${config.route}/${item.slug}
|
|
662
|
+
url: `/api/v1/projects/${projectId}/code/${config.route}/${item.slug}?force=${force}`,
|
|
713
663
|
body: JSON.stringify(item),
|
|
714
664
|
});
|
|
715
665
|
const action = getPromoteAction(result.action);
|
|
@@ -1160,8 +1110,8 @@ const upsertSingleItem = async (item, type, projectId, changes, force, config, s
|
|
|
1160
1110
|
// SQL charts use a different endpoint
|
|
1161
1111
|
const isSqlChartItem = type === 'charts' && isSqlChart(item);
|
|
1162
1112
|
const endpoint = isSqlChartItem
|
|
1163
|
-
? `/api/v1/projects/${projectId}/sqlCharts/${item.slug}
|
|
1164
|
-
: `/api/v1/projects/${projectId}/${type}/${item.slug}
|
|
1113
|
+
? `/api/v1/projects/${projectId}/code/sqlCharts/${item.slug}`
|
|
1114
|
+
: `/api/v1/projects/${projectId}/code/${type}/${item.slug}`;
|
|
1165
1115
|
const upsertData = await (0, apiClient_1.lightdashApi)({
|
|
1166
1116
|
method: 'POST',
|
|
1167
1117
|
url: endpoint,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { type CustomRoleAsCode } from '@lightdash/common';
|
|
2
|
+
import { type CodeFileFailure, type CodeResourceDefinition } from '../contentAsCode/resource';
|
|
3
|
+
export type CustomRoleUploadFailure = CodeFileFailure;
|
|
4
4
|
export type CustomRoleUploadSummary = {
|
|
5
5
|
created: number;
|
|
6
6
|
updated: number;
|
|
@@ -8,6 +8,7 @@ export type CustomRoleUploadSummary = {
|
|
|
8
8
|
failed: number;
|
|
9
9
|
failures: CustomRoleUploadFailure[];
|
|
10
10
|
};
|
|
11
|
+
export declare const CUSTOM_ROLE_CODE_RESOURCE: CodeResourceDefinition<CustomRoleAsCode>;
|
|
11
12
|
export declare const getCustomRolesFolder: (organizationContentPath: string) => string;
|
|
12
13
|
export declare const formatCustomRoleUploadSummary: (summary: CustomRoleUploadSummary) => string;
|
|
13
14
|
export declare const readCustomRoleFiles: (organizationContentPath: string) => Promise<unknown[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"customRoles.d.ts","sourceRoot":"","sources":["../../../src/handlers/organizationContent/customRoles.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"customRoles.d.ts","sourceRoot":"","sources":["../../../src/handlers/organizationContent/customRoles.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,KAAK,gBAAgB,EACxB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAKH,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC9B,MAAM,2BAA2B,CAAC;AAGnC,MAAM,MAAM,uBAAuB,GAAG,eAAe,CAAC;AAEtD,MAAM,MAAM,uBAAuB,GAAG;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,uBAAuB,EAAE,CAAC;CACvC,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,sBAAsB,CAAC,gBAAgB,CAkB1E,CAAC;AAEN,eAAO,MAAM,oBAAoB,GAAI,yBAAyB,MAAM,KAAG,MACK,CAAC;AAE7E,eAAO,MAAM,6BAA6B,GACtC,SAAS,uBAAuB,KACjC,MACmH,CAAC;AAEvH,eAAO,MAAM,mBAAmB,GAC5B,yBAAyB,MAAM,KAChC,OAAO,CAAC,OAAO,EAAE,CAOnB,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC1B,kBAAkB,MAAM,EACxB,yBAAyB,MAAM,KAChC,OAAO,CAAC,uBAAuB,CAc5B,CAAC;AAEP,eAAO,MAAM,mBAAmB,GAC5B,kBAAkB,MAAM,EACxB,yBAAyB,MAAM,KAChC,OAAO,CAAC,MAAM,CAcX,CAAC"}
|
|
@@ -1,151 +1,65 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.downloadCustomRoles = exports.uploadCustomRoles = exports.readCustomRoleFiles = exports.formatCustomRoleUploadSummary = exports.getCustomRolesFolder = void 0;
|
|
3
|
+
exports.downloadCustomRoles = exports.uploadCustomRoles = exports.readCustomRoleFiles = exports.formatCustomRoleUploadSummary = exports.getCustomRolesFolder = exports.CUSTOM_ROLE_CODE_RESOURCE = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
/* eslint-disable no-await-in-loop */
|
|
6
5
|
const common_1 = require("@lightdash/common");
|
|
7
|
-
const fs_1 = require("fs");
|
|
8
|
-
const yaml = tslib_1.__importStar(require("js-yaml"));
|
|
9
|
-
const groupBy_1 = tslib_1.__importDefault(require("lodash/groupBy"));
|
|
10
6
|
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
const resource_1 = require("../contentAsCode/resource");
|
|
11
8
|
const apiClient_1 = require("../dbt/apiClient");
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
exports.CUSTOM_ROLE_CODE_RESOURCE = {
|
|
10
|
+
kind: 'custom_role',
|
|
11
|
+
displayLabel: 'custom role',
|
|
12
|
+
identityLabel: 'name',
|
|
13
|
+
scope: 'organization',
|
|
14
|
+
folderName: 'custom-roles',
|
|
15
|
+
acceptedExtensions: ['.yml'],
|
|
16
|
+
fileName: {
|
|
17
|
+
strategy: 'normalizedDisplayName',
|
|
18
|
+
fallbackPrefix: 'role',
|
|
19
|
+
extension: '.yml',
|
|
20
|
+
},
|
|
21
|
+
dependencies: [],
|
|
22
|
+
identity: ({ name }) => name,
|
|
23
|
+
displayName: ({ name }) => name,
|
|
24
|
+
parse: common_1.parseCustomRoleAsCode,
|
|
25
|
+
sort: (left, right) => left.name.localeCompare(right.name),
|
|
26
|
+
};
|
|
27
|
+
const getCustomRolesFolder = (organizationContentPath) => path.join(organizationContentPath, exports.CUSTOM_ROLE_CODE_RESOURCE.folderName);
|
|
14
28
|
exports.getCustomRolesFolder = getCustomRolesFolder;
|
|
15
29
|
const formatCustomRoleUploadSummary = (summary) => `${summary.created} created, ${summary.updated} updated, ${summary.unchanged} unchanged, ${summary.failed} failed`;
|
|
16
30
|
exports.formatCustomRoleUploadSummary = formatCustomRoleUploadSummary;
|
|
17
|
-
const getCustomRoleName = (role) => {
|
|
18
|
-
if (typeof role !== 'object' || role === null || Array.isArray(role)) {
|
|
19
|
-
return undefined;
|
|
20
|
-
}
|
|
21
|
-
return typeof role.name === 'string'
|
|
22
|
-
? role.name
|
|
23
|
-
: undefined;
|
|
24
|
-
};
|
|
25
|
-
const readCustomRoleFileResults = async (organizationContentPath) => {
|
|
26
|
-
const folder = (0, exports.getCustomRolesFolder)(organizationContentPath);
|
|
27
|
-
let entries;
|
|
28
|
-
try {
|
|
29
|
-
entries = await fs_1.promises.readdir(folder, { withFileTypes: true });
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
if (error.code === 'ENOENT') {
|
|
33
|
-
return { roleFiles: [], failures: [] };
|
|
34
|
-
}
|
|
35
|
-
throw error;
|
|
36
|
-
}
|
|
37
|
-
const files = entries
|
|
38
|
-
.filter((entry) => entry.isFile() && entry.name.endsWith('.yml'))
|
|
39
|
-
.sort((left, right) => left.name.localeCompare(right.name));
|
|
40
|
-
const results = await Promise.all(files.map(async (file) => {
|
|
41
|
-
const filePath = path.join(folder, file.name);
|
|
42
|
-
try {
|
|
43
|
-
return {
|
|
44
|
-
kind: 'role',
|
|
45
|
-
roleFile: {
|
|
46
|
-
filePath,
|
|
47
|
-
role: yaml.load(await fs_1.promises.readFile(filePath, 'utf8')),
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
return {
|
|
53
|
-
kind: 'failure',
|
|
54
|
-
failure: {
|
|
55
|
-
message: `Unable to parse custom role file "${filePath}": ${(0, common_1.getErrorMessage)(error)}`,
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
}));
|
|
60
|
-
const parsedRoleFiles = results.flatMap((result) => result.kind === 'role' ? [result.roleFile] : []);
|
|
61
|
-
const failures = results.flatMap((result) => result.kind === 'failure' ? [result.failure] : []);
|
|
62
|
-
const namedRoleFiles = parsedRoleFiles.flatMap((roleFile) => {
|
|
63
|
-
const name = getCustomRoleName(roleFile.role);
|
|
64
|
-
return name === undefined ? [] : [{ name, roleFile }];
|
|
65
|
-
});
|
|
66
|
-
const roleFilesByName = (0, groupBy_1.default)(namedRoleFiles, ({ name }) => name);
|
|
67
|
-
const duplicateNames = new Set(Object.entries(roleFilesByName)
|
|
68
|
-
.filter(([, roleFiles]) => roleFiles.length > 1)
|
|
69
|
-
.map(([name]) => name));
|
|
70
|
-
for (const duplicateName of [...duplicateNames].sort()) {
|
|
71
|
-
for (const { roleFile } of roleFilesByName[duplicateName]) {
|
|
72
|
-
failures.push({
|
|
73
|
-
message: `Duplicate custom role name "${duplicateName}" in "${roleFile.filePath}"`,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return {
|
|
78
|
-
roleFiles: parsedRoleFiles.filter(({ role }) => !duplicateNames.has(getCustomRoleName(role) ?? '')),
|
|
79
|
-
failures,
|
|
80
|
-
};
|
|
81
|
-
};
|
|
82
31
|
const readCustomRoleFiles = async (organizationContentPath) => {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
32
|
+
const result = await (0, resource_1.readCodeResourceFiles)({
|
|
33
|
+
definition: exports.CUSTOM_ROLE_CODE_RESOURCE,
|
|
34
|
+
basePath: organizationContentPath,
|
|
35
|
+
});
|
|
36
|
+
(0, resource_1.assertCodeResourceFilesValid)(result);
|
|
37
|
+
return result.files.map(({ document }) => document);
|
|
88
38
|
};
|
|
89
39
|
exports.readCustomRoleFiles = readCustomRoleFiles;
|
|
90
|
-
const uploadCustomRoles = async (organizationUuid, organizationContentPath) => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
method: 'POST',
|
|
103
|
-
url: `/api/v2/orgs/${organizationUuid}/roles/code`,
|
|
104
|
-
body: JSON.stringify(role),
|
|
105
|
-
});
|
|
106
|
-
switch (result.action) {
|
|
107
|
-
case common_1.PromotionAction.CREATE:
|
|
108
|
-
summary.created += 1;
|
|
109
|
-
break;
|
|
110
|
-
case common_1.PromotionAction.UPDATE:
|
|
111
|
-
summary.updated += 1;
|
|
112
|
-
break;
|
|
113
|
-
case common_1.PromotionAction.NO_CHANGES:
|
|
114
|
-
summary.unchanged += 1;
|
|
115
|
-
break;
|
|
116
|
-
default:
|
|
117
|
-
throw new common_1.ParameterError(`Unsupported custom role promotion action: ${result.action}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
summary.failures.push({
|
|
122
|
-
message: `Invalid custom role file "${filePath}": ${(0, common_1.getErrorMessage)(error)}`,
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
summary.failed = summary.failures.length;
|
|
127
|
-
return summary;
|
|
128
|
-
};
|
|
40
|
+
const uploadCustomRoles = async (organizationUuid, organizationContentPath) => (0, resource_1.uploadCodeResource)({
|
|
41
|
+
definition: exports.CUSTOM_ROLE_CODE_RESOURCE,
|
|
42
|
+
basePath: organizationContentPath,
|
|
43
|
+
upsert: async (role) => {
|
|
44
|
+
const result = await (0, apiClient_1.lightdashApi)({
|
|
45
|
+
method: 'POST',
|
|
46
|
+
url: `/api/v2/orgs/${organizationUuid}/code/roles`,
|
|
47
|
+
body: JSON.stringify(role),
|
|
48
|
+
});
|
|
49
|
+
return result.action;
|
|
50
|
+
},
|
|
51
|
+
});
|
|
129
52
|
exports.uploadCustomRoles = uploadCustomRoles;
|
|
130
|
-
const downloadCustomRoles = async (organizationUuid, organizationContentPath) => {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const filenames = (0, fileNames_1.getOrganizationContentFileNames)({
|
|
143
|
-
values: customRoles.map(({ name }) => name),
|
|
144
|
-
fallbackPrefix: 'role',
|
|
145
|
-
});
|
|
146
|
-
await Promise.all(customRoles.map(async (role, index) => {
|
|
147
|
-
await fs_1.promises.writeFile(path.join(folder, filenames[index]), yaml.dump(role, { quotingType: '"', sortKeys: true }));
|
|
148
|
-
}));
|
|
149
|
-
return customRoles.length;
|
|
150
|
-
};
|
|
53
|
+
const downloadCustomRoles = async (organizationUuid, organizationContentPath) => (0, resource_1.downloadCodeResource)({
|
|
54
|
+
definition: exports.CUSTOM_ROLE_CODE_RESOURCE,
|
|
55
|
+
basePath: organizationContentPath,
|
|
56
|
+
list: async () => {
|
|
57
|
+
const { customRoles } = await (0, apiClient_1.lightdashApi)({
|
|
58
|
+
method: 'GET',
|
|
59
|
+
url: `/api/v2/orgs/${organizationUuid}/code/roles`,
|
|
60
|
+
body: undefined,
|
|
61
|
+
});
|
|
62
|
+
return customRoles;
|
|
63
|
+
},
|
|
64
|
+
});
|
|
151
65
|
exports.downloadCustomRoles = downloadCustomRoles;
|
|
@@ -59,7 +59,7 @@ describe('custom roles as code', () => {
|
|
|
59
59
|
expect(total).toBe(1);
|
|
60
60
|
expect(apiClient_1.lightdashApi).toHaveBeenCalledWith({
|
|
61
61
|
method: 'GET',
|
|
62
|
-
url: '/api/v2/orgs/organization-uuid/roles
|
|
62
|
+
url: '/api/v2/orgs/organization-uuid/code/roles',
|
|
63
63
|
body: undefined,
|
|
64
64
|
});
|
|
65
65
|
expect(yaml.load(await fs_1.promises.readFile(path.join(tmpDir, 'custom-roles', 'custom-role-name.yml'), 'utf8'))).toStrictEqual({
|
|
@@ -71,6 +71,7 @@ describe('custom roles as code', () => {
|
|
|
71
71
|
});
|
|
72
72
|
expect((await fs_1.promises.readdir(folder)).sort()).toStrictEqual([
|
|
73
73
|
'custom-role-name.yml',
|
|
74
|
+
'stale.yml',
|
|
74
75
|
]);
|
|
75
76
|
});
|
|
76
77
|
it('uses stable hashes for normalized filename collisions', async () => {
|
|
@@ -82,6 +83,16 @@ describe('custom roles as code', () => {
|
|
|
82
83
|
expect(filenames).toHaveLength(2);
|
|
83
84
|
expect(filenames.every((filename) => /^data-admin-[a-f0-9]{8}\.yml$/.test(filename))).toBe(true);
|
|
84
85
|
});
|
|
86
|
+
it('reuses an existing filename owned by the same identity', async () => {
|
|
87
|
+
await writeCustomRole('hand-authored-name.yml', customRole('Data Admin'));
|
|
88
|
+
vitest_1.vi.mocked(apiClient_1.lightdashApi).mockResolvedValueOnce({
|
|
89
|
+
customRoles: [customRole('Data Admin')],
|
|
90
|
+
});
|
|
91
|
+
await (0, customRoles_1.downloadCustomRoles)('organization-uuid', tmpDir);
|
|
92
|
+
expect(await fs_1.promises.readdir((0, customRoles_1.getCustomRolesFolder)(tmpDir))).toStrictEqual([
|
|
93
|
+
'hand-authored-name.yml',
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
85
96
|
it('counts backend create, update, and no-op actions', async () => {
|
|
86
97
|
await writeCustomRole('a.yml', customRole('Role A'));
|
|
87
98
|
await writeCustomRole('b.yml', customRole('Role B'));
|
|
@@ -102,7 +113,7 @@ describe('custom roles as code', () => {
|
|
|
102
113
|
});
|
|
103
114
|
expect(apiClient_1.lightdashApi).toHaveBeenNthCalledWith(1, {
|
|
104
115
|
method: 'POST',
|
|
105
|
-
url: '/api/v2/orgs/organization-uuid/roles
|
|
116
|
+
url: '/api/v2/orgs/organization-uuid/code/roles',
|
|
106
117
|
body: expect.any(String),
|
|
107
118
|
});
|
|
108
119
|
expect(JSON.parse(vitest_1.vi.mocked(apiClient_1.lightdashApi).mock.calls[0][0].body)).toStrictEqual(customRole('Role A'));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileNames.d.ts","sourceRoot":"","sources":["../../../src/handlers/organizationContent/fileNames.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fileNames.d.ts","sourceRoot":"","sources":["../../../src/handlers/organizationContent/fileNames.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,+BAA+B,GAAI,6BAG7C;IACC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;CAC1B,KAAG,MAAM,EAOJ,CAAC"}
|