dax-optimizer-sdk 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -41
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -9
- package/dist/index.js.map +1 -1
- package/dist/preflight.d.ts +2 -2
- package/dist/preflight.js +4 -4
- package/dist/preflight.js.map +1 -1
- package/dist/types.d.ts +18 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vpax-export.d.ts +14 -5
- package/dist/vpax-export.d.ts.map +1 -1
- package/dist/vpax-export.js +53 -27
- package/dist/vpax-export.js.map +1 -1
- package/package.json +5 -1
- package/tools/vpax-bridge/Program.cs +225 -0
- package/tools/vpax-bridge/VpaxBridge.csproj +14 -0
package/README.md
CHANGED
|
@@ -1,112 +1,130 @@
|
|
|
1
1
|
# dax-optimizer-sdk
|
|
2
2
|
|
|
3
|
-
Programmatic DAX Optimizer analysis for Power BI and Analysis Services models. Exports a model to VPAX format, uploads it to the [DAX Optimizer](https://daxoptimizer.com) service, and returns structured performance analysis results — all from a single function call.
|
|
3
|
+
Programmatic DAX Optimizer analysis for Power BI, Fabric, and Analysis Services models. Exports a model to VPAX format, uploads it to the [DAX Optimizer](https://daxoptimizer.com) service, and returns structured performance analysis results — all from a single function call.
|
|
4
4
|
|
|
5
5
|
## Prerequisites
|
|
6
6
|
|
|
7
|
-
Three CLI tools must be installed on the machine before using this library. If any are missing, the library throws a `PrerequisiteError` with the exact install command.
|
|
8
|
-
|
|
9
7
|
| Tool | Purpose | Install |
|
|
10
8
|
|------|---------|---------|
|
|
11
|
-
| **.NET
|
|
12
|
-
| **DAX Optimizer CLI
|
|
13
|
-
|
|
9
|
+
| **.NET 8.0+** | Runtime for vpax-bridge and DAX Optimizer CLI | [Download](https://dotnet.microsoft.com/download) |
|
|
10
|
+
| **DAX Optimizer CLI** | Uploads VPAX and runs analysis | `dotnet tool install --global Dax.Optimizer.CLI` |
|
|
11
|
+
|
|
12
|
+
vpax-bridge (bundled) is built automatically on `npm install` via `dotnet build`.
|
|
14
13
|
|
|
15
14
|
You also need a [DAX Optimizer account](https://daxoptimizer.com) with a workspace ID.
|
|
16
15
|
|
|
17
16
|
## Authentication
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
### DAX Optimizer auth (for analysis upload)
|
|
19
|
+
|
|
20
|
+
Three modes — pass an `auth` object:
|
|
20
21
|
|
|
21
|
-
### PAT (Personal Access Token)
|
|
22
|
-
Non-interactive. For CI/CD and automation. Requires Enterprise license.
|
|
23
22
|
```ts
|
|
23
|
+
// PAT (CI/CD, automation — requires Enterprise license)
|
|
24
24
|
auth: { method: 'pat', username: 'group:automation', token: process.env.TOKEN! }
|
|
25
|
+
|
|
26
|
+
// Interactive (opens browser, credentials cached by CLI)
|
|
27
|
+
auth: { method: 'interactive' }
|
|
28
|
+
|
|
29
|
+
// Group (opens browser, switches to group account)
|
|
30
|
+
auth: { method: 'group', username: 'my-team' }
|
|
25
31
|
```
|
|
26
32
|
|
|
27
|
-
###
|
|
28
|
-
|
|
33
|
+
### Fabric auth (for VPAX export from cloud models)
|
|
34
|
+
|
|
35
|
+
For Fabric / Power BI Service models, add `interactive: true` to open a browser for Azure AD sign-in:
|
|
36
|
+
|
|
29
37
|
```ts
|
|
30
|
-
|
|
38
|
+
interactive: true
|
|
31
39
|
```
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
Or pass a pre-acquired token:
|
|
42
|
+
|
|
35
43
|
```ts
|
|
36
|
-
|
|
44
|
+
accessToken: '<bearer-token>'
|
|
37
45
|
```
|
|
38
46
|
|
|
39
|
-
|
|
47
|
+
Not needed for local Power BI Desktop models.
|
|
40
48
|
|
|
41
49
|
## Quick start
|
|
42
50
|
|
|
51
|
+
### Local Power BI Desktop
|
|
52
|
+
|
|
43
53
|
```ts
|
|
44
54
|
import { analyzeDaxModel } from 'dax-optimizer-sdk'
|
|
45
55
|
|
|
46
56
|
const result = await analyzeDaxModel({
|
|
47
|
-
// Authentication
|
|
48
57
|
auth: { method: 'pat', username: 'group:automation', token: process.env.TOKEN! },
|
|
49
|
-
|
|
50
|
-
// Connection to your Power BI Desktop / Analysis Services instance
|
|
51
58
|
server: 'localhost:65072',
|
|
52
59
|
databaseId: 'f97515be-bbb7-434b-83fc-b2599bf8a754',
|
|
53
|
-
|
|
54
|
-
// DAX Optimizer target
|
|
55
60
|
workspaceId: process.env.WORKSPACE_ID!,
|
|
56
61
|
modelName: 'My Model',
|
|
57
|
-
modelCreate: true,
|
|
58
|
-
contractId: '...',
|
|
62
|
+
modelCreate: true,
|
|
63
|
+
contractId: '...',
|
|
59
64
|
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Fabric / Power BI Service
|
|
60
68
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
```ts
|
|
70
|
+
const result = await analyzeDaxModel({
|
|
71
|
+
auth: { method: 'pat', username: 'group:automation', token: process.env.TOKEN! },
|
|
72
|
+
server: 'powerbi://api.powerbi.com/v1.0/myorg/My Workspace',
|
|
73
|
+
databaseId: 'My Dataset',
|
|
74
|
+
interactive: true, // opens browser for Azure AD sign-in
|
|
75
|
+
workspaceId: process.env.WORKSPACE_ID!,
|
|
76
|
+
modelName: 'My Dataset',
|
|
77
|
+
modelCreate: true,
|
|
78
|
+
contractId: '...',
|
|
79
|
+
})
|
|
65
80
|
```
|
|
66
81
|
|
|
67
82
|
## Where do `server` and `databaseId` come from?
|
|
68
83
|
|
|
69
|
-
This library does **not** include Power BI model discovery. The caller
|
|
84
|
+
This library does **not** include Power BI model discovery. The caller provides the connection details:
|
|
70
85
|
|
|
71
|
-
- **
|
|
72
|
-
- **
|
|
86
|
+
- **Local PBI Desktop** — use Power BI MCP `ListLocalInstances` + `Connect` + `database_operations List`
|
|
87
|
+
- **Fabric** — use the XMLA endpoint URL: `powerbi://api.powerbi.com/v1.0/myorg/<workspace>` and the dataset name as `databaseId`
|
|
73
88
|
|
|
74
89
|
## API
|
|
75
90
|
|
|
76
91
|
### `analyzeDaxModel(options): Promise<DaxOptimizerResult>`
|
|
77
92
|
|
|
78
|
-
End-to-end
|
|
93
|
+
End-to-end: preflight check → VPAX export → analysis → results → cleanup.
|
|
79
94
|
|
|
80
95
|
### `checkPrerequisites(): PreflightResult`
|
|
81
96
|
|
|
82
|
-
Returns `{ dotnet
|
|
97
|
+
Returns `{ dotnet, daxoptimizer, vpaxBridge }` booleans.
|
|
83
98
|
|
|
84
99
|
### `exportVpax(options): Promise<string>`
|
|
85
100
|
|
|
86
|
-
Exports a model to VPAX
|
|
101
|
+
Exports a model to VPAX. Supports local, Fabric (interactive), and Fabric (token).
|
|
87
102
|
|
|
88
103
|
### `runAnalysis(options): Promise<DaxOptimizerResult>`
|
|
89
104
|
|
|
90
|
-
|
|
105
|
+
Analyzes an existing VPAX file.
|
|
91
106
|
|
|
92
107
|
### `logout(): Promise<void>`
|
|
93
108
|
|
|
94
|
-
Clears cached
|
|
109
|
+
Clears cached DAX Optimizer CLI credentials.
|
|
95
110
|
|
|
96
111
|
### `isInteractive(auth): boolean`
|
|
97
112
|
|
|
98
|
-
Returns `true` if the auth mode may open a browser
|
|
113
|
+
Returns `true` if the DAX Optimizer auth mode may open a browser.
|
|
99
114
|
|
|
100
115
|
## Error handling
|
|
101
116
|
|
|
102
117
|
| Error class | When |
|
|
103
118
|
|-------------|------|
|
|
104
|
-
| `AuthError` | Invalid auth
|
|
105
|
-
| `PrerequisiteError` |
|
|
106
|
-
| `VpaxExportError` |
|
|
107
|
-
| `AnalysisError` |
|
|
119
|
+
| `AuthError` | Invalid auth config or login/logout failure |
|
|
120
|
+
| `PrerequisiteError` | Missing tool (includes install command) |
|
|
121
|
+
| `VpaxExportError` | VPAX export failed |
|
|
122
|
+
| `AnalysisError` | DAX Optimizer CLI failed or results couldn't be parsed |
|
|
123
|
+
|
|
124
|
+
## Known limitations
|
|
108
125
|
|
|
109
|
-
|
|
126
|
+
- **Direct Lake models** (compat level 1604) are not yet supported by DAX Optimizer
|
|
127
|
+
- **TMDL/PBIP offline** export produces a VPAX without data statistics, which DAX Optimizer rejects
|
|
110
128
|
|
|
111
129
|
## License
|
|
112
130
|
|
package/dist/index.d.ts
CHANGED
|
@@ -2,14 +2,14 @@ import { DaxOptimizerOptions, DaxOptimizerResult } from './types.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* End-to-end DAX Optimizer analysis.
|
|
4
4
|
*
|
|
5
|
-
* 1. Validates prerequisites (
|
|
5
|
+
* 1. Validates prerequisites (vpax-bridge, daxoptimizer, dotnet).
|
|
6
6
|
* 2. Exports the model to a temporary VPAX file.
|
|
7
7
|
* 3. Uploads VPAX to DAX Optimizer and waits for results.
|
|
8
8
|
* 4. Cleans up temp files and returns the parsed result.
|
|
9
9
|
*/
|
|
10
10
|
export declare function analyzeDaxModel(options: DaxOptimizerOptions): Promise<DaxOptimizerResult>;
|
|
11
11
|
export { checkPrerequisites } from './preflight.js';
|
|
12
|
-
export { exportVpax,
|
|
12
|
+
export { exportVpax, findVpaxBridge, buildConnectionString } from './vpax-export.js';
|
|
13
13
|
export { runAnalysis } from './analyze.js';
|
|
14
14
|
export { logout, isInteractive, buildAuthArgs } from './auth.js';
|
|
15
15
|
export * from './types.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AA6BA,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAEnB,MAAM,YAAY,CAAA;AAKnB;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAwC/F;AAGD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAChE,cAAc,YAAY,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// Usage:
|
|
6
6
|
// import { analyzeDaxModel, checkPrerequisites, logout } from 'dax-optimizer-sdk'
|
|
7
7
|
//
|
|
8
|
-
// //
|
|
8
|
+
// // Local PBI Desktop
|
|
9
9
|
// const result = await analyzeDaxModel({
|
|
10
10
|
// auth: { method: 'pat', username: 'group:automation', token: '...' },
|
|
11
11
|
// server: 'localhost:65072',
|
|
@@ -14,11 +14,16 @@
|
|
|
14
14
|
// modelName: 'My Model',
|
|
15
15
|
// })
|
|
16
16
|
//
|
|
17
|
-
// //
|
|
18
|
-
// const
|
|
19
|
-
// auth: { method: '
|
|
20
|
-
//
|
|
17
|
+
// // Fabric with interactive browser login
|
|
18
|
+
// const result = await analyzeDaxModel({
|
|
19
|
+
// auth: { method: 'pat', username: '...', token: '...' },
|
|
20
|
+
// server: 'powerbi://api.powerbi.com/v1.0/myorg/MyWorkspace',
|
|
21
|
+
// databaseId: 'My Dataset',
|
|
22
|
+
// interactive: true,
|
|
23
|
+
// workspaceId: '...',
|
|
24
|
+
// modelName: 'My Model',
|
|
21
25
|
// })
|
|
26
|
+
//
|
|
22
27
|
// ---------------------------------------------------------------------------
|
|
23
28
|
import { unlinkSync } from 'node:fs';
|
|
24
29
|
import { PrerequisiteError, } from './types.js';
|
|
@@ -28,7 +33,7 @@ import { runAnalysis } from './analyze.js';
|
|
|
28
33
|
/**
|
|
29
34
|
* End-to-end DAX Optimizer analysis.
|
|
30
35
|
*
|
|
31
|
-
* 1. Validates prerequisites (
|
|
36
|
+
* 1. Validates prerequisites (vpax-bridge, daxoptimizer, dotnet).
|
|
32
37
|
* 2. Exports the model to a temporary VPAX file.
|
|
33
38
|
* 3. Uploads VPAX to DAX Optimizer and waits for results.
|
|
34
39
|
* 4. Cleans up temp files and returns the parsed result.
|
|
@@ -41,8 +46,8 @@ export async function analyzeDaxModel(options) {
|
|
|
41
46
|
missing.push('dotnet 6.0+');
|
|
42
47
|
if (!pre.daxoptimizer)
|
|
43
48
|
missing.push('daxoptimizer CLI (dotnet tool install --global Dax.Optimizer.CLI)');
|
|
44
|
-
if (!pre.
|
|
45
|
-
missing.push('
|
|
49
|
+
if (!pre.vpaxBridge)
|
|
50
|
+
missing.push('vpax-bridge (run "dotnet build" in tools/vpax-bridge)');
|
|
46
51
|
if (missing.length)
|
|
47
52
|
throw new PrerequisiteError(missing);
|
|
48
53
|
// ── 2. Export VPAX ────────────────────────────────────────────────────
|
|
@@ -50,6 +55,10 @@ export async function analyzeDaxModel(options) {
|
|
|
50
55
|
databaseId: options.databaseId,
|
|
51
56
|
server: options.server,
|
|
52
57
|
connectionString: options.connectionString,
|
|
58
|
+
interactive: options.interactive,
|
|
59
|
+
accessToken: options.accessToken,
|
|
60
|
+
clientId: options.clientId,
|
|
61
|
+
tenantId: options.tenantId,
|
|
53
62
|
});
|
|
54
63
|
// ── 3. Run analysis ───────────────────────────────────────────────────
|
|
55
64
|
try {
|
|
@@ -77,7 +86,7 @@ export async function analyzeDaxModel(options) {
|
|
|
77
86
|
}
|
|
78
87
|
// Re-export everything so callers can import from a single entry point
|
|
79
88
|
export { checkPrerequisites } from './preflight.js';
|
|
80
|
-
export { exportVpax,
|
|
89
|
+
export { exportVpax, findVpaxBridge, buildConnectionString } from './vpax-export.js';
|
|
81
90
|
export { runAnalysis } from './analyze.js';
|
|
82
91
|
export { logout, isInteractive, buildAuthArgs } from './auth.js';
|
|
83
92
|
export * from './types.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,6BAA6B;AAC7B,8EAA8E;AAC9E,EAAE;AACF,SAAS;AACT,oFAAoF;AACpF,EAAE;AACF,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
|
package/dist/preflight.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { PreflightResult } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Check whether all required
|
|
3
|
+
* Check whether all required tools are available.
|
|
4
4
|
*
|
|
5
5
|
* - `dotnet` 6.0+
|
|
6
6
|
* - `daxoptimizer` (Dax.Optimizer.CLI .NET global tool)
|
|
7
|
-
* - `
|
|
7
|
+
* - `vpax-bridge` (bundled .NET tool for VPAX export)
|
|
8
8
|
*/
|
|
9
9
|
export declare function checkPrerequisites(): PreflightResult;
|
|
10
10
|
//# sourceMappingURL=preflight.d.ts.map
|
package/dist/preflight.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// DAX Optimizer – prerequisite checks
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
|
-
import {
|
|
5
|
+
import { findVpaxBridge } from './vpax-export.js';
|
|
6
6
|
function commandExists(cmd, args = ['--version']) {
|
|
7
7
|
try {
|
|
8
8
|
execFileSync(cmd, args, { encoding: 'utf-8', timeout: 10_000, stdio: 'pipe' });
|
|
@@ -23,17 +23,17 @@ function checkDotnet() {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
* Check whether all required
|
|
26
|
+
* Check whether all required tools are available.
|
|
27
27
|
*
|
|
28
28
|
* - `dotnet` 6.0+
|
|
29
29
|
* - `daxoptimizer` (Dax.Optimizer.CLI .NET global tool)
|
|
30
|
-
* - `
|
|
30
|
+
* - `vpax-bridge` (bundled .NET tool for VPAX export)
|
|
31
31
|
*/
|
|
32
32
|
export function checkPrerequisites() {
|
|
33
33
|
return {
|
|
34
34
|
dotnet: checkDotnet(),
|
|
35
35
|
daxoptimizer: commandExists('daxoptimizer'),
|
|
36
|
-
|
|
36
|
+
vpaxBridge: findVpaxBridge() !== null,
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
//# sourceMappingURL=preflight.js.map
|
package/dist/preflight.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preflight.js","sourceRoot":"","sources":["../preflight.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,sCAAsC;AACtC,8EAA8E;AAE9E,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"preflight.js","sourceRoot":"","sources":["../preflight.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,sCAAsC;AACtC,8EAA8E;AAE9E,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEjD,SAAS,aAAa,CAAC,GAAW,EAAE,OAAiB,CAAC,WAAW,CAAC;IAChE,IAAI,CAAC;QACH,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QAC9E,OAAO,IAAI,CAAA;IACb,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC;AAED,SAAS,WAAW;IAClB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QACxG,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;QACpD,OAAO,KAAK,IAAI,CAAC,CAAA;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO;QACL,MAAM,EAAE,WAAW,EAAE;QACrB,YAAY,EAAE,aAAa,CAAC,cAAc,CAAC;QAC3C,UAAU,EAAE,cAAc,EAAE,KAAK,IAAI;KACtC,CAAA;AACH,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -52,12 +52,29 @@ export interface DaxOptimizerOptions {
|
|
|
52
52
|
failOnIssues?: boolean;
|
|
53
53
|
/** Optional: full MSOLAP connection string (overrides server + databaseId) */
|
|
54
54
|
connectionString?: string;
|
|
55
|
+
/** Open browser for Azure AD sign-in (for Fabric/cloud models) */
|
|
56
|
+
interactive?: boolean;
|
|
57
|
+
/** Pre-acquired bearer token for Fabric/cloud XMLA endpoint */
|
|
58
|
+
accessToken?: string;
|
|
59
|
+
/** Azure AD app client ID for interactive auth (default: well-known AS tools ID) */
|
|
60
|
+
clientId?: string;
|
|
61
|
+
/** Azure AD tenant ID for interactive auth (default: 'common') */
|
|
62
|
+
tenantId?: string;
|
|
55
63
|
}
|
|
56
64
|
/** Subset of options needed just for VPAX export. */
|
|
57
65
|
export interface VpaxExportOptions {
|
|
58
66
|
databaseId: string;
|
|
59
67
|
server: string;
|
|
68
|
+
/** Full MSOLAP connection string (overrides server + databaseId) */
|
|
60
69
|
connectionString?: string;
|
|
70
|
+
/** Open browser for Azure AD sign-in (for Fabric/cloud models) */
|
|
71
|
+
interactive?: boolean;
|
|
72
|
+
/** Pre-acquired bearer token for Fabric/cloud XMLA endpoint */
|
|
73
|
+
accessToken?: string;
|
|
74
|
+
/** Azure AD app client ID for interactive auth (default: well-known AS tools ID) */
|
|
75
|
+
clientId?: string;
|
|
76
|
+
/** Azure AD tenant ID for interactive auth (default: 'common') */
|
|
77
|
+
tenantId?: string;
|
|
61
78
|
outputPath?: string;
|
|
62
79
|
}
|
|
63
80
|
/** Subset of options needed for the `daxoptimizer analyze` step. */
|
|
@@ -76,7 +93,7 @@ export interface AnalyzeOptions {
|
|
|
76
93
|
export interface PreflightResult {
|
|
77
94
|
dotnet: boolean;
|
|
78
95
|
daxoptimizer: boolean;
|
|
79
|
-
|
|
96
|
+
vpaxBridge: boolean;
|
|
80
97
|
}
|
|
81
98
|
export interface DaxOptimizerMessage {
|
|
82
99
|
category: number;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"AAIA,kDAAkD;AAClD,MAAM,MAAM,kBAAkB,GAAG,eAAe,GAAG,QAAQ,GAAG,YAAY,CAAA;AAI1E,0FAA0F;AAC1F,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,KAAK,CAAA;IACb,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAA;IAChB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAA;CACd;AAED,0FAA0F;AAC1F,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,aAAa,CAAA;CACtB;AAED,4EAA4E;AAC5E,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,OAAO,CAAA;IACf,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,eAAe,GAAG,SAAS,CAAA;AAIpE,2EAA2E;AAC3E,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAA;IACd,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAA;IAClB,gCAAgC;IAChC,IAAI,EAAE,gBAAgB,CAAA;IACtB,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAA;IACnB,+CAA+C;IAC/C,MAAM,CAAC,EAAE,kBAAkB,CAAA;IAC3B,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gEAAgE;IAChE,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qDAAqD;IACrD,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,+BAA+B;AAC/B,8EAA8E;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,+BAA+B;AAC/B,8EAA8E;AA6L9E,+EAA+E;AAE/E,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACvB;IAAnB,YAAmB,OAAiB,EAAE,OAAgB;QACpD,KAAK,CAAC,OAAO,IAAI,0BAA0B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAD/C,YAAO,GAAP,OAAO,CAAU;QAElC,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAA;IACjC,CAAC;CACF;AAED,MAAM,OAAO,SAAU,SAAQ,KAAK;IAClC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,WAAW,CAAA;IACzB,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACJ;IAAwB;IAA5D,YAAY,OAAe,EAAS,MAAe,EAAS,MAAe;QACzE,KAAK,CAAC,OAAO,CAAC,CAAA;QADoB,WAAM,GAAN,MAAM,CAAS;QAAS,WAAM,GAAN,MAAM,CAAS;QAEzE,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAA;IAC/B,CAAC;CACF;AAED,MAAM,OAAO,aAAc,SAAQ,KAAK;IACF;IAA0B;IAAwB;IAAtF,YAAY,OAAe,EAAS,QAAiB,EAAS,MAAe,EAAS,MAAe;QACnG,KAAK,CAAC,OAAO,CAAC,CAAA;QADoB,aAAQ,GAAR,QAAQ,CAAS;QAAS,WAAM,GAAN,MAAM,CAAS;QAAS,WAAM,GAAN,MAAM,CAAS;QAEnG,IAAI,CAAC,IAAI,GAAG,eAAe,CAAA;IAC7B,CAAC;CACF"}
|
package/dist/vpax-export.d.ts
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { VpaxExportOptions } from './types.js';
|
|
2
|
-
/** Locate dscmd.exe on PATH or at well-known install locations. */
|
|
3
|
-
export declare function findDscmd(): string | null;
|
|
4
2
|
/**
|
|
5
|
-
*
|
|
3
|
+
* Locate the vpax-bridge executable.
|
|
4
|
+
* Checks the built output relative to this module's location.
|
|
5
|
+
*/
|
|
6
|
+
export declare function findVpaxBridge(): string | null;
|
|
7
|
+
/**
|
|
8
|
+
* Build an MSOLAP connection string from export options.
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildConnectionString(options: VpaxExportOptions): string;
|
|
11
|
+
/**
|
|
12
|
+
* Export a Power BI / Analysis Services / Fabric model to VPAX format.
|
|
6
13
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
14
|
+
* Uses vpax-bridge which supports:
|
|
15
|
+
* - Local PBI Desktop (no auth)
|
|
16
|
+
* - Fabric with interactive browser login (`interactive: true`)
|
|
17
|
+
* - Fabric with pre-acquired token (`accessToken: '...'`)
|
|
9
18
|
*
|
|
10
19
|
* @returns Absolute path to the exported .vpax file.
|
|
11
20
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vpax-export.d.ts","sourceRoot":"","sources":["../vpax-export.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vpax-export.d.ts","sourceRoot":"","sources":["../vpax-export.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,iBAAiB,EAAmB,MAAM,YAAY,CAAA;AAI/D;;;GAGG;AACH,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAqB9C;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAKxE;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoDtE"}
|
package/dist/vpax-export.js
CHANGED
|
@@ -1,61 +1,87 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
|
-
// DAX Optimizer – VPAX export via
|
|
2
|
+
// DAX Optimizer – VPAX export via vpax-bridge (.NET wrapper around SQLBI libs)
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
import { execFile, execFileSync } from 'node:child_process';
|
|
5
5
|
import { existsSync, mkdirSync, statSync } from 'node:fs';
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
7
|
import { dirname, join } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
8
9
|
import { VpaxExportError } from './types.js';
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
/**
|
|
12
|
+
* Locate the vpax-bridge executable.
|
|
13
|
+
* Checks the built output relative to this module's location.
|
|
14
|
+
*/
|
|
15
|
+
export function findVpaxBridge() {
|
|
16
|
+
// When running from source (lib/dax-optimizer/): tools/vpax-bridge/bin/...
|
|
17
|
+
// When running from dist (lib/dax-optimizer/dist/): ../tools/vpax-bridge/bin/...
|
|
18
|
+
const candidates = [
|
|
19
|
+
join(__dirname, '..', 'tools', 'vpax-bridge', 'bin', 'Debug', 'net8.0', 'vpax-bridge.exe'),
|
|
20
|
+
join(__dirname, 'tools', 'vpax-bridge', 'bin', 'Debug', 'net8.0', 'vpax-bridge.exe'),
|
|
21
|
+
join(__dirname, '..', 'tools', 'vpax-bridge', 'bin', 'Release', 'net8.0', 'vpax-bridge.exe'),
|
|
22
|
+
];
|
|
23
|
+
for (const p of candidates) {
|
|
24
|
+
if (existsSync(p))
|
|
25
|
+
return p;
|
|
26
|
+
}
|
|
27
|
+
// Also check PATH
|
|
15
28
|
try {
|
|
16
|
-
const result = execFileSync('where', ['
|
|
29
|
+
const result = execFileSync('where', ['vpax-bridge'], { encoding: 'utf-8', timeout: 5_000 });
|
|
17
30
|
const first = result.trim().split(/\r?\n/)[0];
|
|
18
31
|
if (first && existsSync(first))
|
|
19
32
|
return first;
|
|
20
33
|
}
|
|
21
34
|
catch { /* not on PATH */ }
|
|
22
|
-
for (const p of KNOWN_DSCMD_PATHS) {
|
|
23
|
-
if (existsSync(p))
|
|
24
|
-
return p;
|
|
25
|
-
}
|
|
26
35
|
return null;
|
|
27
36
|
}
|
|
28
37
|
/**
|
|
29
|
-
*
|
|
38
|
+
* Build an MSOLAP connection string from export options.
|
|
39
|
+
*/
|
|
40
|
+
export function buildConnectionString(options) {
|
|
41
|
+
if (options.connectionString) {
|
|
42
|
+
return options.connectionString;
|
|
43
|
+
}
|
|
44
|
+
return `Provider=MSOLAP;Data Source=${options.server};Initial Catalog=${options.databaseId}`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Export a Power BI / Analysis Services / Fabric model to VPAX format.
|
|
30
48
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
49
|
+
* Uses vpax-bridge which supports:
|
|
50
|
+
* - Local PBI Desktop (no auth)
|
|
51
|
+
* - Fabric with interactive browser login (`interactive: true`)
|
|
52
|
+
* - Fabric with pre-acquired token (`accessToken: '...'`)
|
|
33
53
|
*
|
|
34
54
|
* @returns Absolute path to the exported .vpax file.
|
|
35
55
|
*/
|
|
36
56
|
export function exportVpax(options) {
|
|
37
|
-
const { databaseId
|
|
57
|
+
const { databaseId } = options;
|
|
38
58
|
const outputPath = options.outputPath ?? join(tmpdir(), `${databaseId}.vpax`);
|
|
39
|
-
const
|
|
40
|
-
if (!
|
|
41
|
-
throw new VpaxExportError('
|
|
59
|
+
const bridge = findVpaxBridge();
|
|
60
|
+
if (!bridge) {
|
|
61
|
+
throw new VpaxExportError('vpax-bridge not found. Run "dotnet build" in the tools/vpax-bridge directory.');
|
|
42
62
|
}
|
|
43
63
|
// Ensure output directory exists
|
|
44
64
|
const dir = dirname(outputPath);
|
|
45
65
|
if (!existsSync(dir))
|
|
46
66
|
mkdirSync(dir, { recursive: true });
|
|
47
|
-
|
|
48
|
-
const args = ['
|
|
49
|
-
if (
|
|
50
|
-
args.push('
|
|
67
|
+
const connStr = buildConnectionString(options);
|
|
68
|
+
const args = ['export', outputPath, connStr, '--overwrite'];
|
|
69
|
+
if (options.interactive) {
|
|
70
|
+
args.push('--interactive');
|
|
71
|
+
if (options.clientId)
|
|
72
|
+
args.push('--client-id', options.clientId);
|
|
73
|
+
if (options.tenantId)
|
|
74
|
+
args.push('--tenant-id', options.tenantId);
|
|
51
75
|
}
|
|
52
|
-
else {
|
|
53
|
-
args.push('-
|
|
76
|
+
else if (options.accessToken) {
|
|
77
|
+
args.push('--access-token', options.accessToken);
|
|
54
78
|
}
|
|
79
|
+
// Interactive auth needs more time for browser login
|
|
80
|
+
const timeoutMs = options.interactive ? 300_000 : 120_000;
|
|
55
81
|
return new Promise((resolve, reject) => {
|
|
56
|
-
execFile(
|
|
82
|
+
execFile(bridge, args, { timeout: timeoutMs }, (error, stdout, stderr) => {
|
|
57
83
|
if (error) {
|
|
58
|
-
return reject(new VpaxExportError(`VPAX export failed: ${error.message}`, stdout, stderr));
|
|
84
|
+
return reject(new VpaxExportError(`VPAX export failed: ${(stderr || error.message).trim()}`, stdout, stderr));
|
|
59
85
|
}
|
|
60
86
|
if (!existsSync(outputPath)) {
|
|
61
87
|
return reject(new VpaxExportError('VPAX export completed but output file was not created.', stdout, stderr));
|
package/dist/vpax-export.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vpax-export.js","sourceRoot":"","sources":["../vpax-export.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E
|
|
1
|
+
{"version":3,"file":"vpax-export.js","sourceRoot":"","sources":["../vpax-export.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dax-optimizer-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Programmatic DAX Optimizer analysis for Power BI and Analysis Services models",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -13,11 +13,15 @@
|
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"dist/",
|
|
16
|
+
"tools/vpax-bridge/Program.cs",
|
|
17
|
+
"tools/vpax-bridge/VpaxBridge.csproj",
|
|
16
18
|
"README.md",
|
|
17
19
|
"LICENSE"
|
|
18
20
|
],
|
|
19
21
|
"scripts": {
|
|
20
22
|
"build": "tsc",
|
|
23
|
+
"build:bridge": "dotnet build tools/vpax-bridge -c Debug",
|
|
24
|
+
"postinstall": "dotnet build tools/vpax-bridge -c Debug -v quiet",
|
|
21
25
|
"prepublishOnly": "npm run build",
|
|
22
26
|
"preflight": "node dist/preflight.js"
|
|
23
27
|
},
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// vpax-bridge — VPAX export with interactive auth and TMDL support
|
|
2
|
+
//
|
|
3
|
+
// Usage:
|
|
4
|
+
// vpax-bridge export <output.vpax> <connection-string> [options]
|
|
5
|
+
// vpax-bridge export-tmdl <output.vpax> <tmdl-folder> [--overwrite]
|
|
6
|
+
//
|
|
7
|
+
// Options:
|
|
8
|
+
// --access-token <token> Use a pre-acquired bearer token
|
|
9
|
+
// --interactive Open browser for Azure AD sign-in (like DAX Studio)
|
|
10
|
+
// --client-id <id> Azure AD app client ID (default: well-known AS tools ID)
|
|
11
|
+
// --tenant-id <id> Azure AD tenant ID (default: common)
|
|
12
|
+
// --overwrite Overwrite existing output file
|
|
13
|
+
//
|
|
14
|
+
// Examples:
|
|
15
|
+
// # Local PBI Desktop (no auth needed)
|
|
16
|
+
// vpax-bridge export model.vpax "Provider=MSOLAP;Data Source=localhost:65072;Initial Catalog=mydb"
|
|
17
|
+
//
|
|
18
|
+
// # Fabric with interactive browser login
|
|
19
|
+
// vpax-bridge export model.vpax "Data Source=powerbi://api.powerbi.com/v1.0/myorg/Workspace;Initial Catalog=Dataset" --interactive
|
|
20
|
+
//
|
|
21
|
+
// # Fabric with pre-acquired token
|
|
22
|
+
// vpax-bridge export model.vpax "Data Source=powerbi://...;Initial Catalog=..." --access-token eyJ0...
|
|
23
|
+
//
|
|
24
|
+
// # From TMDL/PBIP folder (offline, no server needed)
|
|
25
|
+
// vpax-bridge export-tmdl model.vpax "C:\path\to\MyModel.SemanticModel"
|
|
26
|
+
|
|
27
|
+
using System;
|
|
28
|
+
using System.IO;
|
|
29
|
+
using System.Linq;
|
|
30
|
+
using System.Threading.Tasks;
|
|
31
|
+
using Dax.Model.Extractor;
|
|
32
|
+
using Dax.Vpax.Tools;
|
|
33
|
+
using Microsoft.AnalysisServices;
|
|
34
|
+
using Microsoft.Identity.Client;
|
|
35
|
+
using Tom = Microsoft.AnalysisServices.Tabular;
|
|
36
|
+
|
|
37
|
+
class Program
|
|
38
|
+
{
|
|
39
|
+
// Default client ID: well-known AS tools ID (same as DAX Studio)
|
|
40
|
+
private const string DefaultClientId = "a672d62c-fc7b-4e81-a576-e60dc46e951d";
|
|
41
|
+
private const string DefaultTenantId = "common";
|
|
42
|
+
private const string PowerBIScope = "https://analysis.windows.net/powerbi/api/.default";
|
|
43
|
+
|
|
44
|
+
static async Task<int> Main(string[] args)
|
|
45
|
+
{
|
|
46
|
+
if (args.Length < 3)
|
|
47
|
+
{
|
|
48
|
+
PrintUsage();
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
var command = args[0];
|
|
53
|
+
var outputPath = args[1];
|
|
54
|
+
bool overwrite = args.Contains("--overwrite");
|
|
55
|
+
|
|
56
|
+
if (File.Exists(outputPath) && !overwrite)
|
|
57
|
+
{
|
|
58
|
+
Console.Error.WriteLine($"Output file already exists: {outputPath}. Use --overwrite to replace.");
|
|
59
|
+
return 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try
|
|
63
|
+
{
|
|
64
|
+
return command switch
|
|
65
|
+
{
|
|
66
|
+
"export" => await ExportFromServer(outputPath, args),
|
|
67
|
+
"export-tmdl" => ExportFromTmdl(outputPath, args[2]),
|
|
68
|
+
_ => PrintUsage()
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (Exception ex)
|
|
72
|
+
{
|
|
73
|
+
Console.Error.WriteLine();
|
|
74
|
+
Console.Error.WriteLine($"Error: {ex.Message}");
|
|
75
|
+
if (ex.InnerException != null)
|
|
76
|
+
Console.Error.WriteLine($"Inner: {ex.InnerException.Message}");
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static int PrintUsage()
|
|
82
|
+
{
|
|
83
|
+
Console.Error.WriteLine("vpax-bridge — VPAX export with auth support");
|
|
84
|
+
Console.Error.WriteLine();
|
|
85
|
+
Console.Error.WriteLine("Usage:");
|
|
86
|
+
Console.Error.WriteLine(" vpax-bridge export <output.vpax> <connection-string> [--interactive] [--access-token <token>] [--overwrite]");
|
|
87
|
+
Console.Error.WriteLine(" vpax-bridge export-tmdl <output.vpax> <tmdl-folder> [--overwrite]");
|
|
88
|
+
return 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// <summary>Acquire an access token via interactive browser sign-in.</summary>
|
|
92
|
+
static async Task<string> AcquireTokenInteractive(string clientId = null, string tenantId = null)
|
|
93
|
+
{
|
|
94
|
+
var effectiveClientId = clientId ?? DefaultClientId;
|
|
95
|
+
var effectiveTenantId = tenantId ?? DefaultTenantId;
|
|
96
|
+
|
|
97
|
+
Console.Error.WriteLine($"Opening browser for Azure AD sign-in (client: {effectiveClientId}, tenant: {effectiveTenantId})...");
|
|
98
|
+
|
|
99
|
+
var app = PublicClientApplicationBuilder
|
|
100
|
+
.Create(effectiveClientId)
|
|
101
|
+
.WithAuthority($"https://login.microsoftonline.com/{effectiveTenantId}")
|
|
102
|
+
.WithDefaultRedirectUri()
|
|
103
|
+
.Build();
|
|
104
|
+
|
|
105
|
+
var result = await app.AcquireTokenInteractive(new[] { PowerBIScope })
|
|
106
|
+
.WithPrompt(Prompt.SelectAccount)
|
|
107
|
+
.ExecuteAsync();
|
|
108
|
+
|
|
109
|
+
Console.Error.WriteLine($"Authenticated as: {result.Account.Username}");
|
|
110
|
+
return result.AccessToken;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// <summary>Export VPAX from a live server connection.</summary>
|
|
114
|
+
static async Task<int> ExportFromServer(string outputPath, string[] args)
|
|
115
|
+
{
|
|
116
|
+
var connectionString = args[2];
|
|
117
|
+
string accessToken = null;
|
|
118
|
+
string clientId = null;
|
|
119
|
+
string tenantId = null;
|
|
120
|
+
bool interactive = false;
|
|
121
|
+
|
|
122
|
+
for (int i = 3; i < args.Length; i++)
|
|
123
|
+
{
|
|
124
|
+
if (args[i] == "--access-token" && i + 1 < args.Length)
|
|
125
|
+
accessToken = args[++i];
|
|
126
|
+
else if (args[i] == "--interactive")
|
|
127
|
+
interactive = true;
|
|
128
|
+
else if (args[i] == "--client-id" && i + 1 < args.Length)
|
|
129
|
+
clientId = args[++i];
|
|
130
|
+
else if (args[i] == "--tenant-id" && i + 1 < args.Length)
|
|
131
|
+
tenantId = args[++i];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Acquire token interactively if requested
|
|
135
|
+
if (interactive && string.IsNullOrEmpty(accessToken))
|
|
136
|
+
{
|
|
137
|
+
accessToken = await AcquireTokenInteractive(clientId, tenantId);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
Console.Error.Write("Extracting VPAX metadata...");
|
|
141
|
+
|
|
142
|
+
Dax.Metadata.Model daxModel;
|
|
143
|
+
|
|
144
|
+
Tom.Database database;
|
|
145
|
+
|
|
146
|
+
if (!string.IsNullOrEmpty(accessToken))
|
|
147
|
+
{
|
|
148
|
+
var asAccessToken = new AccessToken(accessToken, DateTimeOffset.UtcNow.AddHours(1));
|
|
149
|
+
|
|
150
|
+
// Connect with token, then extract with stats
|
|
151
|
+
database = TomExtractor.GetDatabase(connectionString, asAccessToken,
|
|
152
|
+
onTokenExpired: (expired) => asAccessToken);
|
|
153
|
+
|
|
154
|
+
daxModel = TomExtractor.GetDaxModel(
|
|
155
|
+
connectionString: connectionString,
|
|
156
|
+
applicationName: "vpax-bridge",
|
|
157
|
+
applicationVersion: "1.0.0",
|
|
158
|
+
readStatisticsFromData: true,
|
|
159
|
+
sampleRows: 0,
|
|
160
|
+
analyzeDirectQuery: false,
|
|
161
|
+
statsColumnBatchSize: 50,
|
|
162
|
+
accessToken: asAccessToken,
|
|
163
|
+
onTokenExpired: (expired) => asAccessToken
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
else
|
|
167
|
+
{
|
|
168
|
+
// No token — local PBI Desktop
|
|
169
|
+
database = TomExtractor.GetDatabase(connectionString);
|
|
170
|
+
|
|
171
|
+
daxModel = TomExtractor.GetDaxModel(
|
|
172
|
+
connectionString: connectionString,
|
|
173
|
+
applicationName: "vpax-bridge",
|
|
174
|
+
applicationVersion: "1.0.0",
|
|
175
|
+
readStatisticsFromData: true,
|
|
176
|
+
sampleRows: 0,
|
|
177
|
+
analyzeDirectQuery: false,
|
|
178
|
+
statsColumnBatchSize: 50
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return WriteVpax(outputPath, daxModel, database);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/// <summary>Export VPAX from a local TMDL folder (offline, no server needed).</summary>
|
|
186
|
+
static int ExportFromTmdl(string outputPath, string tmdlFolder)
|
|
187
|
+
{
|
|
188
|
+
Console.Error.Write("Loading TMDL model...");
|
|
189
|
+
|
|
190
|
+
var folder = tmdlFolder;
|
|
191
|
+
if (!File.Exists(Path.Combine(folder, "database.tmdl")))
|
|
192
|
+
{
|
|
193
|
+
var defFolder = Path.Combine(folder, "definition");
|
|
194
|
+
if (File.Exists(Path.Combine(defFolder, "database.tmdl")))
|
|
195
|
+
folder = defFolder;
|
|
196
|
+
else
|
|
197
|
+
throw new FileNotFoundException(
|
|
198
|
+
$"database.tmdl not found in '{tmdlFolder}' or '{defFolder}'. " +
|
|
199
|
+
"Point to the folder containing database.tmdl or the .SemanticModel folder.");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
var database = Tom.TmdlSerializer.DeserializeDatabaseFromFolder(folder);
|
|
203
|
+
Console.Error.Write($"loaded '{database.Name ?? "Model"}' ({database.Model.Tables.Count} tables)...");
|
|
204
|
+
|
|
205
|
+
var daxModel = TomExtractor.GetDaxModel(database.Model, "vpax-bridge", "1.0.0");
|
|
206
|
+
|
|
207
|
+
return WriteVpax(outputPath, daxModel);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
static int WriteVpax(string outputPath, Dax.Metadata.Model daxModel, Tom.Database database = null)
|
|
211
|
+
{
|
|
212
|
+
Console.Error.Write("done. Exporting VPAX file...");
|
|
213
|
+
|
|
214
|
+
var dir = Path.GetDirectoryName(Path.GetFullPath(outputPath));
|
|
215
|
+
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
|
216
|
+
Directory.CreateDirectory(dir);
|
|
217
|
+
|
|
218
|
+
var vpaModel = new Dax.ViewVpaExport.Model(daxModel);
|
|
219
|
+
VpaxTools.ExportVpax(outputPath, daxModel, vpaModel, database);
|
|
220
|
+
|
|
221
|
+
Console.Error.WriteLine("done.");
|
|
222
|
+
Console.WriteLine(outputPath);
|
|
223
|
+
return 0;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
|
2
|
+
<PropertyGroup>
|
|
3
|
+
<OutputType>Exe</OutputType>
|
|
4
|
+
<TargetFramework>net8.0</TargetFramework>
|
|
5
|
+
<AssemblyName>vpax-bridge</AssemblyName>
|
|
6
|
+
<RootNamespace>VpaxBridge</RootNamespace>
|
|
7
|
+
</PropertyGroup>
|
|
8
|
+
<ItemGroup>
|
|
9
|
+
<PackageReference Include="Dax.Model.Extractor" Version="1.12.0" />
|
|
10
|
+
<PackageReference Include="Dax.Vpax" Version="1.12.0" />
|
|
11
|
+
<PackageReference Include="Dax.ViewVpaExport" Version="1.12.0" />
|
|
12
|
+
<PackageReference Include="Microsoft.Identity.Client" Version="4.65.0" />
|
|
13
|
+
</ItemGroup>
|
|
14
|
+
</Project>
|