@lightdash/cli 1.251.7 → 1.252.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/dbt/manifest.d.ts.map +1 -1
- package/dist/dbt/manifest.js +33 -1
- package/dist/dbt/manifest.test.js +91 -0
- package/dist/dbt/validation.d.ts +1 -1
- package/dist/dbt/validation.d.ts.map +1 -1
- package/dist/dbt/validation.js +11 -2
- package/dist/dbt/validation.test.js +21 -0
- package/dist/handlers/compile.d.ts +2 -0
- package/dist/handlers/compile.d.ts.map +1 -1
- package/dist/handlers/compile.js +142 -11
- package/dist/handlers/compileProject.test.js +385 -1
- package/dist/handlers/compileWarehouseColumnValidation.test.js +1 -0
- package/dist/handlers/deploy.d.ts +1 -1
- package/dist/handlers/deploy.d.ts.map +1 -1
- package/dist/handlers/deploy.js +21 -12
- package/dist/handlers/deploy.test.js +50 -3
- package/dist/handlers/preview.d.ts +3 -1
- package/dist/handlers/preview.d.ts.map +1 -1
- package/dist/handlers/preview.js +2 -0
- package/dist/index.js +8 -3
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/dbt/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/dbt/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,WAAW,EAId,MAAM,mBAAmB,CAAC;AAQ3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,MAAM,gBAAgB,GAAG;IAC3B,SAAS,EAAE,MAAM,CAAC;CACrB,CAAC;AAsBF,eAAO,MAAM,YAAY,mBAEtB,gBAAgB,KAAG,OAAO,CAAC,WAAW,CAGxC,CAAC;AAEF,eAAO,MAAM,SAAS,UAAW,MAAM,KAAG,OAOzC,CAAC;AAoEF,eAAO,MAAM,mBAAmB,cACjB,MAAM,KAClB,OAAO,CAAC,WAAW,CAKrB,CAAC"}
|
package/dist/dbt/manifest.js
CHANGED
|
@@ -40,14 +40,46 @@ const isHttpUrl = (value) => {
|
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
42
|
exports.isHttpUrl = isHttpUrl;
|
|
43
|
+
const isGenericUnexpectedServerError = (responseText) => {
|
|
44
|
+
try {
|
|
45
|
+
const response = JSON.parse(responseText);
|
|
46
|
+
if (!(0, common_1.isApiError)(response))
|
|
47
|
+
return false;
|
|
48
|
+
const { error } = response;
|
|
49
|
+
return (error.statusCode === 500 &&
|
|
50
|
+
error.name === 'UnexpectedServerError' &&
|
|
51
|
+
error.message === 'Something went wrong.');
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const isLightdashFrontendFallback = (url, response, responseText) => {
|
|
58
|
+
const { pathname } = new URL(url);
|
|
59
|
+
const isFrontendPath = pathname !== '/api' && !pathname.startsWith('/api/');
|
|
60
|
+
const isLightdashResponse = response.headers.get('lightdash-version') !== null;
|
|
61
|
+
const isHtmlResponse = response.headers.get('content-type')?.includes('text/html') === true ||
|
|
62
|
+
/^\s*(?:<!doctype html|<html)/i.test(responseText);
|
|
63
|
+
return (isFrontendPath &&
|
|
64
|
+
isLightdashResponse &&
|
|
65
|
+
(isHtmlResponse || isGenericUnexpectedServerError(responseText)));
|
|
66
|
+
};
|
|
43
67
|
const loadManifestFromUrl = async (url) => {
|
|
44
68
|
globalState_1.default.debug(`> Loading dbt manifest from ${url}`);
|
|
45
69
|
try {
|
|
46
70
|
const response = await (0, node_fetch_1.default)(url);
|
|
71
|
+
const responseText = await response.text();
|
|
72
|
+
const isFrontendFallback = isLightdashFrontendFallback(url, response, responseText);
|
|
47
73
|
if (!response.ok) {
|
|
74
|
+
if (isFrontendFallback) {
|
|
75
|
+
throw new Error('Manifest not found: the URL resolves to a Lightdash app route instead of a dbt manifest');
|
|
76
|
+
}
|
|
48
77
|
throw new Error(`${response.status} ${response.statusText}`);
|
|
49
78
|
}
|
|
50
|
-
|
|
79
|
+
if (isFrontendFallback) {
|
|
80
|
+
throw new Error('Manifest not found: the URL resolves to a Lightdash app route instead of a dbt manifest');
|
|
81
|
+
}
|
|
82
|
+
const manifest = JSON.parse(responseText);
|
|
51
83
|
return manifest;
|
|
52
84
|
}
|
|
53
85
|
catch (err) {
|
|
@@ -50,6 +50,9 @@ describe('loadCombineManifest', () => {
|
|
|
50
50
|
});
|
|
51
51
|
mockedFetch.mockResolvedValue({
|
|
52
52
|
ok: true,
|
|
53
|
+
headers: {
|
|
54
|
+
get: (name) => name === 'content-type' ? 'application/json' : null,
|
|
55
|
+
},
|
|
53
56
|
text: async () => JSON.stringify(manifest),
|
|
54
57
|
});
|
|
55
58
|
const result = await (0, manifest_1.loadCombineManifest)('https://example.com/manifest.json');
|
|
@@ -62,10 +65,98 @@ describe('loadCombineManifest', () => {
|
|
|
62
65
|
ok: false,
|
|
63
66
|
status: 404,
|
|
64
67
|
statusText: 'Not Found',
|
|
68
|
+
headers: {
|
|
69
|
+
get: () => 'application/json',
|
|
70
|
+
},
|
|
65
71
|
text: async () => '',
|
|
66
72
|
});
|
|
67
73
|
await expect((0, manifest_1.loadCombineManifest)('https://example.com/manifest.json')).rejects.toThrow('Could not load manifest from https://example.com/manifest.json');
|
|
68
74
|
});
|
|
75
|
+
test('reports the Lightdash dev app fallback as a missing manifest', async () => {
|
|
76
|
+
mockedFetch.mockResolvedValue({
|
|
77
|
+
ok: false,
|
|
78
|
+
status: 500,
|
|
79
|
+
statusText: 'Internal Server Error',
|
|
80
|
+
headers: {
|
|
81
|
+
get: (name) => {
|
|
82
|
+
if (name === 'content-type') {
|
|
83
|
+
return 'application/json; charset=utf-8';
|
|
84
|
+
}
|
|
85
|
+
if (name === 'lightdash-version')
|
|
86
|
+
return '1.163.2';
|
|
87
|
+
return null;
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
text: async () => JSON.stringify({
|
|
91
|
+
status: 'error',
|
|
92
|
+
error: {
|
|
93
|
+
statusCode: 500,
|
|
94
|
+
name: 'UnexpectedServerError',
|
|
95
|
+
message: 'Something went wrong.',
|
|
96
|
+
data: {},
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
const promise = (0, manifest_1.loadCombineManifest)('http://localhost:8080/charter5-does-not-exist/manifest.json');
|
|
101
|
+
await expect(promise).rejects.toThrow('Manifest not found');
|
|
102
|
+
await expect(promise).rejects.not.toThrow('500 Internal Server Error');
|
|
103
|
+
});
|
|
104
|
+
test('reports the Lightdash production html fallback as a missing manifest', async () => {
|
|
105
|
+
mockedFetch.mockResolvedValue({
|
|
106
|
+
ok: true,
|
|
107
|
+
status: 200,
|
|
108
|
+
statusText: 'OK',
|
|
109
|
+
headers: {
|
|
110
|
+
get: (name) => {
|
|
111
|
+
if (name === 'content-type') {
|
|
112
|
+
return 'text/html; charset=utf-8';
|
|
113
|
+
}
|
|
114
|
+
if (name === 'lightdash-version')
|
|
115
|
+
return '1.163.2';
|
|
116
|
+
return null;
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
text: async () => '<!DOCTYPE html><html><body><div id="root"></div></body></html>',
|
|
120
|
+
});
|
|
121
|
+
await expect((0, manifest_1.loadCombineManifest)('https://lightdash.example.com/missing/manifest.json')).rejects.toThrow('Manifest not found');
|
|
122
|
+
});
|
|
123
|
+
test.each([
|
|
124
|
+
[
|
|
125
|
+
'a Lightdash API error',
|
|
126
|
+
'https://lightdash.example.com/api/v1/missing/manifest.json',
|
|
127
|
+
'1.163.2',
|
|
128
|
+
],
|
|
129
|
+
[
|
|
130
|
+
'an unmarked external error',
|
|
131
|
+
'https://example.com/missing/manifest.json',
|
|
132
|
+
null,
|
|
133
|
+
],
|
|
134
|
+
])('preserves %s', async (_description, url, lightdashVersion) => {
|
|
135
|
+
mockedFetch.mockResolvedValue({
|
|
136
|
+
ok: false,
|
|
137
|
+
status: 500,
|
|
138
|
+
statusText: 'Internal Server Error',
|
|
139
|
+
headers: {
|
|
140
|
+
get: (name) => {
|
|
141
|
+
if (name === 'content-type')
|
|
142
|
+
return 'application/json';
|
|
143
|
+
if (name === 'lightdash-version')
|
|
144
|
+
return lightdashVersion;
|
|
145
|
+
return null;
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
text: async () => JSON.stringify({
|
|
149
|
+
status: 'error',
|
|
150
|
+
error: {
|
|
151
|
+
statusCode: 500,
|
|
152
|
+
name: 'UnexpectedServerError',
|
|
153
|
+
message: 'Something went wrong.',
|
|
154
|
+
data: {},
|
|
155
|
+
},
|
|
156
|
+
}),
|
|
157
|
+
});
|
|
158
|
+
await expect((0, manifest_1.loadCombineManifest)(url)).rejects.toThrow('500 Internal Server Error');
|
|
159
|
+
});
|
|
69
160
|
test('reads a manifest from a local path', async () => {
|
|
70
161
|
const manifest = buildManifest({
|
|
71
162
|
'model.proj.b': modelNode('model.proj.b'),
|
package/dist/dbt/validation.d.ts
CHANGED
|
@@ -4,6 +4,6 @@ type DbtModelsGroupedByState = {
|
|
|
4
4
|
invalid: ExploreError[];
|
|
5
5
|
skipped: DbtRawModelNode[];
|
|
6
6
|
};
|
|
7
|
-
export declare const validateDbtModel: (adapterType: string, manifestVersion: DbtManifestVersion, models: DbtRawModelNode[]) => Promise<DbtModelsGroupedByState>;
|
|
7
|
+
export declare const validateDbtModel: (adapterType: string, manifestVersion: DbtManifestVersion, models: DbtRawModelNode[], servedModelIds?: ReadonlySet<string>) => Promise<DbtModelsGroupedByState>;
|
|
8
8
|
export {};
|
|
9
9
|
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/dbt/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,YAAY,EACZ,eAAe,EACf,YAAY,EAOZ,KAAK,kBAAkB,EAC1B,MAAM,mBAAmB,CAAC;AAG3B,KAAK,uBAAuB,GAAG;IAC3B,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,OAAO,EAAE,eAAe,EAAE,CAAC;CAC9B,CAAC;
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/dbt/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,YAAY,EACZ,eAAe,EACf,YAAY,EAOZ,KAAK,kBAAkB,EAC1B,MAAM,mBAAmB,CAAC;AAG3B,KAAK,uBAAuB,GAAG;IAC3B,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,OAAO,EAAE,eAAe,EAAE,CAAC;CAC9B,CAAC;AAaF,eAAO,MAAM,gBAAgB,gBACZ,MAAM,mBACF,kBAAkB,UAC3B,eAAe,EAAE,mBACT,WAAW,CAAC,MAAM,CAAC,KACpC,OAAO,CAAC,uBAAuB,CAuEjC,CAAC"}
|
package/dist/dbt/validation.js
CHANGED
|
@@ -4,7 +4,14 @@ exports.validateDbtModel = void 0;
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const common_1 = require("@lightdash/common");
|
|
6
6
|
const globalState_1 = tslib_1.__importDefault(require("../globalState"));
|
|
7
|
-
const
|
|
7
|
+
const formatServedManifestValidationError = (message) => {
|
|
8
|
+
const details = message
|
|
9
|
+
.replace(/ (?:Remove or update it|Update this field) in your dbt model, then deploy again\./g, '')
|
|
10
|
+
.trimEnd();
|
|
11
|
+
const separator = /[.!?]$/.test(details) ? ' ' : '. ';
|
|
12
|
+
return `${details}${separator}The served manifest is outdated. Redeploy the Lightdash project, then try again.`;
|
|
13
|
+
};
|
|
14
|
+
const validateDbtModel = async (adapterType, manifestVersion, models, servedModelIds = new Set()) => {
|
|
8
15
|
globalState_1.default.debug(`> Validating ${models.length} models from dbt manifest`);
|
|
9
16
|
globalState_1.default.debug(`> Validating models using dbt manifest version ${manifestVersion}`);
|
|
10
17
|
const validator = new common_1.ManifestValidator(manifestVersion);
|
|
@@ -15,7 +22,9 @@ const validateDbtModel = async (adapterType, manifestVersion, models) => {
|
|
|
15
22
|
if (!isValid) {
|
|
16
23
|
error = {
|
|
17
24
|
type: common_1.InlineErrorType.METADATA_PARSE_ERROR,
|
|
18
|
-
message:
|
|
25
|
+
message: servedModelIds.has(model.unique_id)
|
|
26
|
+
? formatServedManifestValidationError(errorMessage)
|
|
27
|
+
: errorMessage,
|
|
19
28
|
};
|
|
20
29
|
}
|
|
21
30
|
else if (isValid && Object.values(model.columns).length <= 0) {
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const common_1 = require("@lightdash/common");
|
|
4
4
|
const validation_1 = require("./validation");
|
|
5
5
|
const makeModel = (name) => ({
|
|
6
|
+
unique_id: `model.test.${name}`,
|
|
6
7
|
name,
|
|
7
8
|
resource_type: 'model',
|
|
8
9
|
database: 'database',
|
|
@@ -10,6 +11,26 @@ const makeModel = (name) => ({
|
|
|
10
11
|
meta: {},
|
|
11
12
|
});
|
|
12
13
|
describe('validateDbtModel', () => {
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
vi.restoreAllMocks();
|
|
16
|
+
});
|
|
17
|
+
test('directs served manifest validation failures to a project redeploy', async () => {
|
|
18
|
+
const validationMessage = 'Model "served_orders" from dbt source "server": Field at model root must have required property \'original_file_path\'. Update this field in your dbt model, then deploy again.';
|
|
19
|
+
const invalidValidator = Object.assign(vi.fn().mockReturnValue(false), {
|
|
20
|
+
errors: [],
|
|
21
|
+
});
|
|
22
|
+
vi.spyOn(common_1.ManifestValidator, 'getValidator').mockReturnValue(invalidValidator);
|
|
23
|
+
vi.spyOn(common_1.ManifestValidator, 'formatAjvErrors').mockReturnValue(validationMessage);
|
|
24
|
+
const servedModel = makeModel('served_orders');
|
|
25
|
+
const localModel = makeModel('local_orders');
|
|
26
|
+
const result = await (0, validation_1.validateDbtModel)('postgres', common_1.DbtManifestVersion.V11, [servedModel, localModel], new Set([servedModel.unique_id]));
|
|
27
|
+
const servedMessage = result.invalid[0].errors[0].message;
|
|
28
|
+
const localMessage = result.invalid[1].errors[0].message;
|
|
29
|
+
expect(servedMessage).toContain('The served manifest is outdated.');
|
|
30
|
+
expect(servedMessage).toContain('Redeploy the Lightdash project, then try again.');
|
|
31
|
+
expect(servedMessage).not.toContain('Update this field in your dbt model');
|
|
32
|
+
expect(localMessage).toContain('Update this field in your dbt model');
|
|
33
|
+
});
|
|
13
34
|
test('keeps valid v12 models when column config metadata is invalid', async () => {
|
|
14
35
|
const validModel = makeModel('valid_orders');
|
|
15
36
|
const invalidModel = {
|
|
@@ -12,6 +12,8 @@ export type CompileHandlerOptions = DbtCompileOptions & {
|
|
|
12
12
|
disableTimestampConversion?: boolean;
|
|
13
13
|
validateWarehouseColumns?: boolean;
|
|
14
14
|
partialCompilation?: boolean;
|
|
15
|
+
combineManifestProjectUuid?: string;
|
|
16
|
+
combine?: boolean;
|
|
15
17
|
};
|
|
16
18
|
export type CompileProjectResult = {
|
|
17
19
|
explores: (Explore | ExploreError)[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/handlers/compile.ts"],"names":[],"mappings":"AAAA,OAAO,EAOH,OAAO,EACP,YAAY,
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/handlers/compile.ts"],"names":[],"mappings":"AAAA,OAAO,EAOH,OAAO,EACP,YAAY,EAgBf,MAAM,mBAAmB,CAAC;AAqB3B,OAAO,EAAE,iBAAiB,EAA8B,MAAM,eAAe,CAAC;AAI9E,MAAM,MAAM,qBAAqB,GAAG,iBAAiB,GAAG;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IAC/B,QAAQ,EAAE,CAAC,OAAO,GAAG,YAAY,CAAC,EAAE,CAAC;IACrC,iBAAiB,EAAE,OAAO,CAAC;CAC9B,CAAC;AAEF,eAAO,MAAM,uBAAuB,YACvB,OAAO,GAAG,YAAY,KAChC,OAIW,CAAC;AAEf,eAAO,MAAM,0BAA0B,YAC1B,OAAO,GAAG,YAAY,KAChC,OAAO,GAAG,YAiBZ,CAAC;AA6TF,eAAO,MAAM,cAAc,YACd,qBAAqB,KAC/B,OAAO,CAAC,oBAAoB,CA0a9B,CAAC;AAEF,eAAO,MAAM,OAAO,YACP,qBAAqB,KAC/B,OAAO,CAAC,CAAC,OAAO,GAAG,YAAY,CAAC,EAAE,CACO,CAAC;AAE7C,eAAO,MAAM,cAAc,oBACN,qBAAqB,kBAmCzC,CAAC"}
|
package/dist/handlers/compile.js
CHANGED
|
@@ -15,6 +15,7 @@ const lightdash_config_1 = require("../lightdash-config");
|
|
|
15
15
|
const loader_1 = require("../lightdash/loader");
|
|
16
16
|
const projectType_1 = require("../lightdash/projectType");
|
|
17
17
|
const styles = tslib_1.__importStar(require("../styles"));
|
|
18
|
+
const apiClient_1 = require("./dbt/apiClient");
|
|
18
19
|
const compile_1 = require("./dbt/compile");
|
|
19
20
|
const getDbtVersion_1 = require("./dbt/getDbtVersion");
|
|
20
21
|
const getWarehouseClient_1 = tslib_1.__importDefault(require("./dbt/getWarehouseClient"));
|
|
@@ -35,6 +36,64 @@ const stripWarehouseColumnErrors = (explore) => {
|
|
|
35
36
|
: exploreWithoutWarnings;
|
|
36
37
|
};
|
|
37
38
|
exports.stripWarehouseColumnErrors = stripWarehouseColumnErrors;
|
|
39
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
40
|
+
const isServedDbtManifest = (value) => isRecord(value) && isRecord(value.nodes) && isRecord(value.metadata);
|
|
41
|
+
const getManifestModelIds = (manifest) => Object.entries(manifest.nodes)
|
|
42
|
+
.filter(([, node]) => node.resource_type === 'model')
|
|
43
|
+
.map(([uniqueId]) => uniqueId);
|
|
44
|
+
const getCompiledManifestModelIds = (manifest) => Object.entries(manifest.nodes)
|
|
45
|
+
.filter(([, node]) => node.resource_type === 'model' &&
|
|
46
|
+
node
|
|
47
|
+
.compiled === true)
|
|
48
|
+
.map(([uniqueId]) => uniqueId);
|
|
49
|
+
const inferLocalSourceName = (localManifest, servedManifest) => {
|
|
50
|
+
const sourceNames = new Set(getManifestModelIds(localManifest).flatMap((uniqueId) => {
|
|
51
|
+
const servedNode = servedManifest.nodes[uniqueId];
|
|
52
|
+
return servedNode?.resource_type === 'model' &&
|
|
53
|
+
typeof servedNode.lightdash_source_name === 'string'
|
|
54
|
+
? [servedNode.lightdash_source_name]
|
|
55
|
+
: [];
|
|
56
|
+
}));
|
|
57
|
+
if (sourceNames.size > 1) {
|
|
58
|
+
throw new common_1.ParseError(`Cannot automatically combine manifest from the server: overlapping local models match multiple Lightdash sources (${[...sourceNames].sort().join(', ')})`);
|
|
59
|
+
}
|
|
60
|
+
return sourceNames.values().next().value;
|
|
61
|
+
};
|
|
62
|
+
const withoutModelsFromSource = (manifest, sourceName) => ({
|
|
63
|
+
...manifest,
|
|
64
|
+
nodes: Object.fromEntries(Object.entries(manifest.nodes).filter(([, node]) => {
|
|
65
|
+
const sourceAnnotatedNode = node;
|
|
66
|
+
return !(node.resource_type === 'model' &&
|
|
67
|
+
sourceAnnotatedNode.lightdash_source_name === sourceName);
|
|
68
|
+
})),
|
|
69
|
+
});
|
|
70
|
+
const withoutUnselectedOverlappingModels = (localManifest, servedManifest, sourceName, selectedModelIds) => ({
|
|
71
|
+
...localManifest,
|
|
72
|
+
nodes: Object.fromEntries(Object.entries(localManifest.nodes).filter(([uniqueId, localNode]) => {
|
|
73
|
+
const servedNode = servedManifest.nodes[uniqueId];
|
|
74
|
+
return !(localNode.resource_type === 'model' &&
|
|
75
|
+
!selectedModelIds.has(uniqueId) &&
|
|
76
|
+
servedNode?.resource_type === 'model' &&
|
|
77
|
+
servedNode.lightdash_source_name === sourceName);
|
|
78
|
+
})),
|
|
79
|
+
});
|
|
80
|
+
const combinePreviewManifests = (localManifest, externalManifest, localSourceName) => {
|
|
81
|
+
const { manifest, addedModelIds } = (0, manifest_1.combineManifests)(localManifest, externalManifest);
|
|
82
|
+
const nodes = { ...manifest.nodes };
|
|
83
|
+
Object.entries(localManifest.nodes).forEach(([uniqueId, localNode]) => {
|
|
84
|
+
const externalNode = externalManifest.nodes[uniqueId];
|
|
85
|
+
const sourceName = localNode.resource_type === 'model' && localSourceName !== undefined
|
|
86
|
+
? localSourceName
|
|
87
|
+
: externalNode?.lightdash_source_name;
|
|
88
|
+
if (typeof sourceName === 'string') {
|
|
89
|
+
nodes[uniqueId] = {
|
|
90
|
+
...nodes[uniqueId],
|
|
91
|
+
lightdash_source_name: sourceName,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return { manifest: { ...manifest, nodes }, addedModelIds };
|
|
96
|
+
};
|
|
38
97
|
const getDisplayableDiagnostics = (explore, allowPartialCompilation) => {
|
|
39
98
|
const diagnostics = explore.warnings ?? [];
|
|
40
99
|
const errors = diagnostics.filter((diagnostic) => diagnostic.type === common_1.InlineErrorType.WAREHOUSE_COLUMN_ERROR);
|
|
@@ -200,18 +259,90 @@ const compileProject = async (options) => {
|
|
|
200
259
|
(0, common_1.getCompiledModels)(projectManifestModels, compiledModelIds)
|
|
201
260
|
.length === projectManifestModels.length;
|
|
202
261
|
let effectiveCompiledModelIds = compiledModelIds;
|
|
262
|
+
const servedModelIds = new Set();
|
|
263
|
+
let additionalManifest;
|
|
264
|
+
let combineSource;
|
|
265
|
+
let isAutomaticServerManifest = false;
|
|
203
266
|
if (options.combineManifest) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
267
|
+
additionalManifest = await (0, manifest_1.loadCombineManifest)(options.combineManifest);
|
|
268
|
+
combineSource = `external manifest from ${options.combineManifest}`;
|
|
269
|
+
}
|
|
270
|
+
else if (options.combine !== false &&
|
|
271
|
+
options.combineManifestProjectUuid) {
|
|
272
|
+
try {
|
|
273
|
+
const manifestEndpoint = `/api/v1/projects/${options.combineManifestProjectUuid}/dbt/manifest`;
|
|
274
|
+
const response = await (0, apiClient_1.lightdashRawApi)({
|
|
275
|
+
method: 'GET',
|
|
276
|
+
url: manifestEndpoint,
|
|
277
|
+
body: undefined,
|
|
278
|
+
});
|
|
279
|
+
const servedManifest = await response.json();
|
|
280
|
+
if (!isServedDbtManifest(servedManifest)) {
|
|
281
|
+
throw new Error(`${manifestEndpoint} returned an invalid manifest: expected an object with metadata and a nodes record`);
|
|
282
|
+
}
|
|
283
|
+
additionalManifest = servedManifest;
|
|
284
|
+
combineSource = 'manifest from the server';
|
|
285
|
+
isAutomaticServerManifest = true;
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
if (error instanceof common_1.LightdashError &&
|
|
289
|
+
error.statusCode === 404) {
|
|
290
|
+
console.info(styles.info('No server manifest found; continuing with the preview manifest'));
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
console.error(styles.warning(`Could not fetch the server manifest; continuing with the preview manifest: ${(0, common_1.getErrorMessage)(error)}`));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (additionalManifest && combineSource) {
|
|
298
|
+
const localSourceName = isAutomaticServerManifest
|
|
299
|
+
? inferLocalSourceName(manifest, additionalManifest)
|
|
300
|
+
: undefined;
|
|
301
|
+
if (isAutomaticServerManifest && localSourceName === undefined) {
|
|
302
|
+
console.info(styles.info(`Skipped combining ${combineSource}: the local dbt project is not a source of this Lightdash project`));
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
const localManifest = isAutomaticServerManifest &&
|
|
306
|
+
!isProjectComplete &&
|
|
307
|
+
localSourceName !== undefined
|
|
308
|
+
? withoutUnselectedOverlappingModels(manifest, additionalManifest, localSourceName, new Set(originallySelectedModelIds ??
|
|
309
|
+
compiledModelIds ??
|
|
310
|
+
[]))
|
|
311
|
+
: manifest;
|
|
312
|
+
const externalManifest = isAutomaticServerManifest &&
|
|
313
|
+
isProjectComplete &&
|
|
314
|
+
localSourceName !== undefined
|
|
315
|
+
? withoutModelsFromSource(additionalManifest, localSourceName)
|
|
316
|
+
: additionalManifest;
|
|
317
|
+
const compiledExternalModelIds = getCompiledManifestModelIds(additionalManifest);
|
|
318
|
+
const { manifest: merged, addedModelIds } = combinePreviewManifests(localManifest, externalManifest, localSourceName);
|
|
319
|
+
manifest = merged;
|
|
320
|
+
if (isAutomaticServerManifest) {
|
|
321
|
+
addedModelIds.forEach((modelId) => {
|
|
322
|
+
servedModelIds.add(modelId);
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
if (effectiveCompiledModelIds !== undefined &&
|
|
326
|
+
addedModelIds.length > 0) {
|
|
327
|
+
effectiveCompiledModelIds = [
|
|
328
|
+
...effectiveCompiledModelIds,
|
|
329
|
+
...addedModelIds,
|
|
330
|
+
];
|
|
331
|
+
}
|
|
332
|
+
let combineResult;
|
|
333
|
+
if (addedModelIds.length > 0) {
|
|
334
|
+
combineResult = `added ${addedModelIds.length} model(s) not present in the preview manifest`;
|
|
335
|
+
}
|
|
336
|
+
else if (compiledExternalModelIds.length === 0) {
|
|
337
|
+
combineResult =
|
|
338
|
+
'added 0 model(s) because the manifest contains no compiled models';
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
combineResult =
|
|
342
|
+
'added 0 model(s) because all compiled models already exist in the preview manifest';
|
|
343
|
+
}
|
|
344
|
+
console.info(styles.info(`Combined ${combineSource}: ${combineResult}`));
|
|
213
345
|
}
|
|
214
|
-
console.info(styles.info(`Combined external manifest from ${options.combineManifest}: added ${addedModelIds.length} model(s) not present in the preview manifest`));
|
|
215
346
|
}
|
|
216
347
|
const manifestVersion = (0, common_1.getDbtManifestVersion)(manifest);
|
|
217
348
|
const manifestModels = (0, common_1.getModelsFromManifest)(manifest);
|
|
@@ -223,7 +354,7 @@ const compileProject = async (options) => {
|
|
|
223
354
|
? await patchDeferredModels(compiledModels, originallySelectedModelIds, options.state)
|
|
224
355
|
: compiledModels;
|
|
225
356
|
const adapterType = manifest.metadata.adapter_type;
|
|
226
|
-
const { valid: validModels, invalid: failedExplores } = await (0, validation_1.validateDbtModel)(adapterType, manifestVersion, modelsForValidation);
|
|
357
|
+
const { valid: validModels, invalid: failedExplores } = await (0, validation_1.validateDbtModel)(adapterType, manifestVersion, modelsForValidation, servedModelIds);
|
|
227
358
|
if (failedExplores.length > 0) {
|
|
228
359
|
const errors = failedExplores.map((failedExplore) => failedExplore.errors.map((error) => `- ${failedExplore.name}: ${error.message}\n`));
|
|
229
360
|
console.error(styles.warning(`Found ${failedExplores.length} errors when validating dbt models:
|