@luquimbo/bi-superpowers 1.0.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/.claude-plugin/plugin.json +8 -0
- package/.mcp.json +25 -0
- package/AGENTS.md +244 -0
- package/CHANGELOG.md +265 -0
- package/LICENSE +21 -0
- package/README.md +211 -0
- package/bin/build-plugin.js +30 -0
- package/bin/cli.js +1064 -0
- package/bin/commands/add.js +533 -0
- package/bin/commands/add.test.js +77 -0
- package/bin/commands/build-desktop.js +166 -0
- package/bin/commands/changelog.js +443 -0
- package/bin/commands/diff.js +325 -0
- package/bin/commands/lint.js +419 -0
- package/bin/commands/lint.test.js +103 -0
- package/bin/commands/mcp-setup.js +246 -0
- package/bin/commands/pull.js +287 -0
- package/bin/commands/pull.test.js +36 -0
- package/bin/commands/push.js +231 -0
- package/bin/commands/push.test.js +14 -0
- package/bin/commands/search.js +344 -0
- package/bin/commands/search.test.js +115 -0
- package/bin/commands/setup.js +545 -0
- package/bin/commands/setup.test.js +46 -0
- package/bin/commands/sync-profile.js +405 -0
- package/bin/commands/sync-profile.test.js +14 -0
- package/bin/commands/sync-source.js +418 -0
- package/bin/commands/sync-source.test.js +14 -0
- package/bin/commands/watch.js +206 -0
- package/bin/lib/generators/claude-plugin.js +266 -0
- package/bin/lib/generators/claude-plugin.test.js +110 -0
- package/bin/lib/generators/index.js +116 -0
- package/bin/lib/generators/shared.js +282 -0
- package/bin/lib/licensing/index.js +35 -0
- package/bin/lib/licensing/storage.js +364 -0
- package/bin/lib/licensing/storage.test.js +55 -0
- package/bin/lib/licensing/validator.js +213 -0
- package/bin/lib/licensing/validator.test.js +137 -0
- package/bin/lib/microsoft-mcp.js +176 -0
- package/bin/lib/microsoft-mcp.test.js +106 -0
- package/bin/lib/skills.js +84 -0
- package/bin/mcp/powerbi-modeling-launcher.js +38 -0
- package/bin/postinstall.js +44 -0
- package/bin/utils/errors.js +159 -0
- package/bin/utils/git.js +298 -0
- package/bin/utils/logger.js +142 -0
- package/bin/utils/mcp-detect.js +274 -0
- package/bin/utils/mcp-detect.test.js +105 -0
- package/bin/utils/pbix.js +305 -0
- package/bin/utils/pbix.test.js +37 -0
- package/bin/utils/profiles.js +312 -0
- package/bin/utils/projects.js +168 -0
- package/bin/utils/readline.js +206 -0
- package/bin/utils/readline.test.js +47 -0
- package/bin/utils/tui.js +314 -0
- package/bin/utils/tui.test.js +127 -0
- package/commands/contributions.md +265 -0
- package/commands/data-model-design.md +468 -0
- package/commands/dax-doctor.md +248 -0
- package/commands/fabric-scripts.md +452 -0
- package/commands/migration-assistant.md +290 -0
- package/commands/model-documenter.md +242 -0
- package/commands/pbi-connect.md +239 -0
- package/commands/project-kickoff.md +905 -0
- package/commands/report-layout.md +296 -0
- package/commands/rls-design.md +533 -0
- package/commands/theme-tweaker.md +624 -0
- package/config.example.json +23 -0
- package/config.json +23 -0
- package/desktop-extension/manifest.json +37 -0
- package/desktop-extension/package.json +10 -0
- package/desktop-extension/server.js +95 -0
- package/docs/openrouter-free-models.md +92 -0
- package/library/examples/README.md +151 -0
- package/library/examples/finance-reporting/README.md +351 -0
- package/library/examples/finance-reporting/data-model.md +267 -0
- package/library/examples/finance-reporting/measures.dax +557 -0
- package/library/examples/hr-analytics/README.md +371 -0
- package/library/examples/hr-analytics/data-model.md +315 -0
- package/library/examples/hr-analytics/measures.dax +460 -0
- package/library/examples/marketing-analytics/README.md +37 -0
- package/library/examples/marketing-analytics/data-model.md +62 -0
- package/library/examples/marketing-analytics/measures.dax +110 -0
- package/library/examples/retail-analytics/README.md +439 -0
- package/library/examples/retail-analytics/data-model.md +288 -0
- package/library/examples/retail-analytics/measures.dax +481 -0
- package/library/examples/supply-chain/README.md +37 -0
- package/library/examples/supply-chain/data-model.md +69 -0
- package/library/examples/supply-chain/measures.dax +77 -0
- package/library/examples/udf-library/README.md +228 -0
- package/library/examples/udf-library/functions.dax +571 -0
- package/library/snippets/dax/README.md +292 -0
- package/library/snippets/dax/business-domains.md +576 -0
- package/library/snippets/dax/calculate-patterns.md +276 -0
- package/library/snippets/dax/calculation-groups.md +489 -0
- package/library/snippets/dax/error-handling.md +495 -0
- package/library/snippets/dax/iterators-and-aggregations.md +474 -0
- package/library/snippets/dax/kpis-and-metrics.md +293 -0
- package/library/snippets/dax/rankings-and-topn.md +235 -0
- package/library/snippets/dax/security-patterns.md +413 -0
- package/library/snippets/dax/text-and-formatting.md +316 -0
- package/library/snippets/dax/time-intelligence.md +196 -0
- package/library/snippets/dax/user-defined-functions.md +477 -0
- package/library/snippets/dax/virtual-tables.md +546 -0
- package/library/snippets/excel-formulas/README.md +84 -0
- package/library/snippets/excel-formulas/aggregations.md +330 -0
- package/library/snippets/excel-formulas/dates-and-times.md +361 -0
- package/library/snippets/excel-formulas/dynamic-arrays.md +314 -0
- package/library/snippets/excel-formulas/lookups.md +169 -0
- package/library/snippets/excel-formulas/text-functions.md +363 -0
- package/library/snippets/governance/naming-conventions.md +97 -0
- package/library/snippets/governance/review-checklists.md +107 -0
- package/library/snippets/power-query/README.md +389 -0
- package/library/snippets/power-query/api-integration.md +707 -0
- package/library/snippets/power-query/connections.md +434 -0
- package/library/snippets/power-query/data-cleaning.md +298 -0
- package/library/snippets/power-query/error-handling.md +526 -0
- package/library/snippets/power-query/parameters.md +350 -0
- package/library/snippets/power-query/performance.md +506 -0
- package/library/snippets/power-query/transformations.md +330 -0
- package/library/snippets/report-design/accessibility.md +78 -0
- package/library/snippets/report-design/chart-selection.md +54 -0
- package/library/snippets/report-design/layout-patterns.md +87 -0
- package/library/templates/data-models/README.md +93 -0
- package/library/templates/data-models/finance-model.md +627 -0
- package/library/templates/data-models/retail-star-schema.md +473 -0
- package/library/templates/excel/README.md +83 -0
- package/library/templates/excel/budget-tracker.md +432 -0
- package/library/templates/excel/data-entry-form.md +533 -0
- package/library/templates/power-bi/README.md +72 -0
- package/library/templates/power-bi/finance-report.md +449 -0
- package/library/templates/power-bi/kpi-scorecard.md +461 -0
- package/library/templates/power-bi/sales-dashboard.md +281 -0
- package/library/themes/excel/README.md +436 -0
- package/library/themes/power-bi/README.md +271 -0
- package/library/themes/power-bi/accessible.json +307 -0
- package/library/themes/power-bi/bi-superpowers-default.json +858 -0
- package/library/themes/power-bi/corporate-blue.json +291 -0
- package/library/themes/power-bi/dark-mode.json +291 -0
- package/library/themes/power-bi/minimal.json +292 -0
- package/library/themes/power-bi/print-friendly.json +309 -0
- package/package.json +93 -0
- package/skills/contributions/SKILL.md +267 -0
- package/skills/data-model-design/SKILL.md +470 -0
- package/skills/data-modeling/SKILL.md +254 -0
- package/skills/data-quality/SKILL.md +664 -0
- package/skills/dax/SKILL.md +708 -0
- package/skills/dax-doctor/SKILL.md +250 -0
- package/skills/dax-udf/SKILL.md +489 -0
- package/skills/deployment/SKILL.md +320 -0
- package/skills/excel-formulas/SKILL.md +463 -0
- package/skills/fabric-scripts/SKILL.md +454 -0
- package/skills/fast-standard/SKILL.md +509 -0
- package/skills/governance/SKILL.md +205 -0
- package/skills/migration-assistant/SKILL.md +292 -0
- package/skills/model-documenter/SKILL.md +244 -0
- package/skills/pbi-connect/SKILL.md +241 -0
- package/skills/power-query/SKILL.md +406 -0
- package/skills/project-kickoff/SKILL.md +907 -0
- package/skills/query-performance/SKILL.md +480 -0
- package/skills/report-design/SKILL.md +207 -0
- package/skills/report-layout/SKILL.md +298 -0
- package/skills/rls-design/SKILL.md +535 -0
- package/skills/semantic-model/SKILL.md +237 -0
- package/skills/testing-validation/SKILL.md +643 -0
- package/skills/theme-tweaker/SKILL.md +626 -0
- package/src/content/base.md +237 -0
- package/src/content/mcp-requirements.json +69 -0
- package/src/content/routing.md +203 -0
- package/src/content/skills/contributions.md +259 -0
- package/src/content/skills/data-model-design.md +462 -0
- package/src/content/skills/data-modeling.md +246 -0
- package/src/content/skills/data-quality.md +656 -0
- package/src/content/skills/dax-doctor.md +242 -0
- package/src/content/skills/dax-udf.md +481 -0
- package/src/content/skills/dax.md +700 -0
- package/src/content/skills/deployment.md +312 -0
- package/src/content/skills/excel-formulas.md +455 -0
- package/src/content/skills/fabric-scripts.md +446 -0
- package/src/content/skills/fast-standard.md +501 -0
- package/src/content/skills/governance.md +197 -0
- package/src/content/skills/migration-assistant.md +284 -0
- package/src/content/skills/model-documenter.md +236 -0
- package/src/content/skills/pbi-connect.md +233 -0
- package/src/content/skills/power-query.md +398 -0
- package/src/content/skills/project-kickoff.md +899 -0
- package/src/content/skills/query-performance.md +472 -0
- package/src/content/skills/report-design.md +199 -0
- package/src/content/skills/report-layout.md +290 -0
- package/src/content/skills/rls-design.md +527 -0
- package/src/content/skills/semantic-model.md +229 -0
- package/src/content/skills/testing-validation.md +635 -0
- package/src/content/skills/theme-tweaker.md +618 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
# Error Handling in Power Query
|
|
2
|
+
|
|
3
|
+
Patterns for handling errors, debugging, and building robust data pipelines.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Try-Otherwise Pattern
|
|
8
|
+
|
|
9
|
+
### Basic Error Handling
|
|
10
|
+
|
|
11
|
+
```m
|
|
12
|
+
let
|
|
13
|
+
Source = YourSource,
|
|
14
|
+
|
|
15
|
+
// Safe transformation - returns null on error
|
|
16
|
+
SafeConvert = Table.TransformColumns(Source, {
|
|
17
|
+
{"Amount", each try Number.From(_) otherwise null, type number}
|
|
18
|
+
})
|
|
19
|
+
in
|
|
20
|
+
SafeConvert
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### With Custom Default Value
|
|
24
|
+
|
|
25
|
+
```m
|
|
26
|
+
let
|
|
27
|
+
Source = YourSource,
|
|
28
|
+
|
|
29
|
+
// Return 0 instead of null on error
|
|
30
|
+
SafeAmount = Table.TransformColumns(Source, {
|
|
31
|
+
{"Amount", each try Number.From(_) otherwise 0, type number}
|
|
32
|
+
})
|
|
33
|
+
in
|
|
34
|
+
SafeAmount
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### With Error Logging
|
|
38
|
+
|
|
39
|
+
```m
|
|
40
|
+
let
|
|
41
|
+
Source = YourSource,
|
|
42
|
+
|
|
43
|
+
// Capture both value and error status
|
|
44
|
+
WithErrorInfo = Table.AddColumn(Source, "ParseResult",
|
|
45
|
+
each
|
|
46
|
+
let
|
|
47
|
+
Result = try Number.From([Amount])
|
|
48
|
+
in
|
|
49
|
+
[
|
|
50
|
+
Value = if Result[HasError] then null else Result[Value],
|
|
51
|
+
HasError = Result[HasError],
|
|
52
|
+
Error = if Result[HasError] then Result[Error][Message] else null
|
|
53
|
+
]
|
|
54
|
+
),
|
|
55
|
+
|
|
56
|
+
// Expand the record
|
|
57
|
+
Expanded = Table.ExpandRecordColumn(WithErrorInfo, "ParseResult",
|
|
58
|
+
{"Value", "HasError", "Error"},
|
|
59
|
+
{"Amount_Parsed", "Amount_HasError", "Amount_Error"}
|
|
60
|
+
)
|
|
61
|
+
in
|
|
62
|
+
Expanded
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Try Expression Details
|
|
68
|
+
|
|
69
|
+
### Structure of Try Result
|
|
70
|
+
|
|
71
|
+
```m
|
|
72
|
+
let
|
|
73
|
+
// try returns a record with these fields:
|
|
74
|
+
// [HasError] - true/false
|
|
75
|
+
// [Value] - result if no error
|
|
76
|
+
// [Error] - error record if error occurred
|
|
77
|
+
|
|
78
|
+
Result = try Number.From("abc"),
|
|
79
|
+
|
|
80
|
+
// Result = [HasError = true, Error = [Reason = "...", Message = "...", Detail = "..."]]
|
|
81
|
+
|
|
82
|
+
SafeValue = if Result[HasError] then 0 else Result[Value]
|
|
83
|
+
in
|
|
84
|
+
SafeValue
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Access Error Details
|
|
88
|
+
|
|
89
|
+
```m
|
|
90
|
+
let
|
|
91
|
+
Source = YourSource,
|
|
92
|
+
|
|
93
|
+
WithErrorDetails = Table.AddColumn(Source, "ErrorInfo",
|
|
94
|
+
each
|
|
95
|
+
let
|
|
96
|
+
Result = try SomeRiskyOperation([ColumnA])
|
|
97
|
+
in
|
|
98
|
+
if Result[HasError] then
|
|
99
|
+
"Error: " & Result[Error][Reason] &
|
|
100
|
+
" - " & Result[Error][Message]
|
|
101
|
+
else
|
|
102
|
+
"Success"
|
|
103
|
+
)
|
|
104
|
+
in
|
|
105
|
+
WithErrorDetails
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Null Handling
|
|
111
|
+
|
|
112
|
+
### COALESCE Pattern (First Non-Null)
|
|
113
|
+
|
|
114
|
+
```m
|
|
115
|
+
let
|
|
116
|
+
Source = YourSource,
|
|
117
|
+
|
|
118
|
+
// Return first non-null value
|
|
119
|
+
WithCoalesce = Table.AddColumn(Source, "ContactEmail",
|
|
120
|
+
each
|
|
121
|
+
if [PrimaryEmail] <> null then [PrimaryEmail]
|
|
122
|
+
else if [SecondaryEmail] <> null then [SecondaryEmail]
|
|
123
|
+
else if [WorkEmail] <> null then [WorkEmail]
|
|
124
|
+
else "unknown@example.com",
|
|
125
|
+
type text
|
|
126
|
+
)
|
|
127
|
+
in
|
|
128
|
+
WithCoalesce
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Null-Safe Operations
|
|
132
|
+
|
|
133
|
+
```m
|
|
134
|
+
let
|
|
135
|
+
Source = YourSource,
|
|
136
|
+
|
|
137
|
+
// Safe text operations (handle null)
|
|
138
|
+
SafeText = Table.TransformColumns(Source, {
|
|
139
|
+
{"Name", each if _ = null then "" else Text.Trim(_), type text}
|
|
140
|
+
}),
|
|
141
|
+
|
|
142
|
+
// Safe number operations
|
|
143
|
+
SafeNumber = Table.TransformColumns(Source, {
|
|
144
|
+
{"Amount", each if _ = null then 0 else _, type number}
|
|
145
|
+
})
|
|
146
|
+
in
|
|
147
|
+
SafeNumber
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Check for Null Before Operation
|
|
151
|
+
|
|
152
|
+
```m
|
|
153
|
+
let
|
|
154
|
+
Source = YourSource,
|
|
155
|
+
|
|
156
|
+
SafeCalculation = Table.AddColumn(Source, "Result",
|
|
157
|
+
each
|
|
158
|
+
if [Denominator] = null or [Denominator] = 0 then
|
|
159
|
+
null
|
|
160
|
+
else
|
|
161
|
+
[Numerator] / [Denominator],
|
|
162
|
+
type number
|
|
163
|
+
)
|
|
164
|
+
in
|
|
165
|
+
SafeCalculation
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Error Row Separation
|
|
171
|
+
|
|
172
|
+
### Split Clean and Error Rows
|
|
173
|
+
|
|
174
|
+
```m
|
|
175
|
+
let
|
|
176
|
+
Source = YourSource,
|
|
177
|
+
|
|
178
|
+
// Add error flag
|
|
179
|
+
WithFlag = Table.AddColumn(Source, "_HasError",
|
|
180
|
+
each
|
|
181
|
+
try (
|
|
182
|
+
// Validate required fields
|
|
183
|
+
[CustomerID] <> null and
|
|
184
|
+
[Amount] <> null and
|
|
185
|
+
Number.From([Amount]) >= 0
|
|
186
|
+
) otherwise false,
|
|
187
|
+
type logical
|
|
188
|
+
),
|
|
189
|
+
|
|
190
|
+
// Separate good and bad rows
|
|
191
|
+
CleanRows = Table.SelectRows(WithFlag, each [_HasError] = true),
|
|
192
|
+
ErrorRows = Table.SelectRows(WithFlag, each [_HasError] = false),
|
|
193
|
+
|
|
194
|
+
// Remove flag from clean data
|
|
195
|
+
CleanData = Table.RemoveColumns(CleanRows, {"_HasError"})
|
|
196
|
+
in
|
|
197
|
+
CleanData // or return both via Record
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Return Both Clean and Error Tables
|
|
201
|
+
|
|
202
|
+
```m
|
|
203
|
+
let
|
|
204
|
+
Source = YourSource,
|
|
205
|
+
|
|
206
|
+
// Validation function
|
|
207
|
+
ValidateRow = (row as record) as logical =>
|
|
208
|
+
row[CustomerID] <> null and
|
|
209
|
+
row[Amount] <> null and
|
|
210
|
+
(try Number.From(row[Amount]) >= 0 otherwise false),
|
|
211
|
+
|
|
212
|
+
// Flag rows
|
|
213
|
+
Flagged = Table.AddColumn(Source, "_IsValid", each ValidateRow(_), type logical),
|
|
214
|
+
|
|
215
|
+
// Split
|
|
216
|
+
Result = [
|
|
217
|
+
CleanData = Table.SelectRows(Flagged, each [_IsValid] = true),
|
|
218
|
+
ErrorData = Table.SelectRows(Flagged, each [_IsValid] = false),
|
|
219
|
+
ErrorCount = Table.RowCount(Table.SelectRows(Flagged, each [_IsValid] = false)),
|
|
220
|
+
CleanCount = Table.RowCount(Table.SelectRows(Flagged, each [_IsValid] = true))
|
|
221
|
+
]
|
|
222
|
+
in
|
|
223
|
+
Result
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Replace Errors
|
|
229
|
+
|
|
230
|
+
### Replace All Errors in Table
|
|
231
|
+
|
|
232
|
+
```m
|
|
233
|
+
let
|
|
234
|
+
Source = YourSource,
|
|
235
|
+
|
|
236
|
+
// Replace errors in specific columns
|
|
237
|
+
ReplacedErrors = Table.ReplaceErrorValues(Source, {
|
|
238
|
+
{"Amount", 0},
|
|
239
|
+
{"Quantity", 0},
|
|
240
|
+
{"Date", null},
|
|
241
|
+
{"Name", "Unknown"}
|
|
242
|
+
})
|
|
243
|
+
in
|
|
244
|
+
ReplacedErrors
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Replace Errors in All Columns
|
|
248
|
+
|
|
249
|
+
```m
|
|
250
|
+
let
|
|
251
|
+
Source = YourSource,
|
|
252
|
+
ColumnNames = Table.ColumnNames(Source),
|
|
253
|
+
|
|
254
|
+
// Create replacement list for all columns
|
|
255
|
+
ReplacementList = List.Transform(
|
|
256
|
+
ColumnNames,
|
|
257
|
+
each {_, null} // Replace with null
|
|
258
|
+
),
|
|
259
|
+
|
|
260
|
+
ReplacedErrors = Table.ReplaceErrorValues(Source, ReplacementList)
|
|
261
|
+
in
|
|
262
|
+
ReplacedErrors
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Validation Patterns
|
|
268
|
+
|
|
269
|
+
### Data Type Validation
|
|
270
|
+
|
|
271
|
+
```m
|
|
272
|
+
let
|
|
273
|
+
Source = YourSource,
|
|
274
|
+
|
|
275
|
+
// Validate data types before processing
|
|
276
|
+
Validated = Table.AddColumn(Source, "ValidationResult",
|
|
277
|
+
each
|
|
278
|
+
let
|
|
279
|
+
Checks = {
|
|
280
|
+
{[CustomerID] <> null, "CustomerID is required"},
|
|
281
|
+
{try Number.From([Amount]) <> null otherwise false, "Amount must be numeric"},
|
|
282
|
+
{try Date.From([OrderDate]) <> null otherwise false, "OrderDate must be valid date"},
|
|
283
|
+
{[Status] <> null and List.Contains({"Open", "Closed", "Pending"}, [Status]), "Invalid Status"}
|
|
284
|
+
},
|
|
285
|
+
Failures = List.Select(Checks, each not _{0}),
|
|
286
|
+
Messages = List.Transform(Failures, each _{1})
|
|
287
|
+
in
|
|
288
|
+
if List.Count(Messages) = 0 then "Valid"
|
|
289
|
+
else Text.Combine(Messages, "; ")
|
|
290
|
+
)
|
|
291
|
+
in
|
|
292
|
+
Validated
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Business Rule Validation
|
|
296
|
+
|
|
297
|
+
```m
|
|
298
|
+
let
|
|
299
|
+
Source = YourSource,
|
|
300
|
+
|
|
301
|
+
ValidatedData = Table.AddColumn(Source, "BusinessRuleCheck",
|
|
302
|
+
each
|
|
303
|
+
let
|
|
304
|
+
Rules = {
|
|
305
|
+
// Rule 1: Amount must be positive
|
|
306
|
+
{[Amount] > 0, "Amount must be positive"},
|
|
307
|
+
|
|
308
|
+
// Rule 2: Discount cannot exceed amount
|
|
309
|
+
{[Discount] <= [Amount], "Discount exceeds amount"},
|
|
310
|
+
|
|
311
|
+
// Rule 3: Order date must be in the past
|
|
312
|
+
{[OrderDate] <= DateTime.Date(DateTime.LocalNow()), "Future order date"},
|
|
313
|
+
|
|
314
|
+
// Rule 4: Customer must exist
|
|
315
|
+
{[CustomerID] <> null, "Missing customer"}
|
|
316
|
+
},
|
|
317
|
+
Failures = List.Select(Rules, each not _{0}),
|
|
318
|
+
ErrorMessages = List.Transform(Failures, each _{1})
|
|
319
|
+
in
|
|
320
|
+
[
|
|
321
|
+
IsValid = List.Count(ErrorMessages) = 0,
|
|
322
|
+
Errors = Text.Combine(ErrorMessages, "; ")
|
|
323
|
+
]
|
|
324
|
+
),
|
|
325
|
+
|
|
326
|
+
// Expand validation results
|
|
327
|
+
Expanded = Table.ExpandRecordColumn(ValidatedData, "BusinessRuleCheck",
|
|
328
|
+
{"IsValid", "Errors"}
|
|
329
|
+
)
|
|
330
|
+
in
|
|
331
|
+
Expanded
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Connection Error Handling
|
|
337
|
+
|
|
338
|
+
### Handle Missing Data Source
|
|
339
|
+
|
|
340
|
+
```m
|
|
341
|
+
let
|
|
342
|
+
// Try to connect, fall back gracefully
|
|
343
|
+
TryConnection = try Sql.Database("server", "database"),
|
|
344
|
+
|
|
345
|
+
Result =
|
|
346
|
+
if TryConnection[HasError] then
|
|
347
|
+
#table(
|
|
348
|
+
{"Status", "Message"},
|
|
349
|
+
{{"Error", "Could not connect to database: " & TryConnection[Error][Message]}}
|
|
350
|
+
)
|
|
351
|
+
else
|
|
352
|
+
let
|
|
353
|
+
Source = TryConnection[Value],
|
|
354
|
+
Data = Source{[Schema="dbo", Item="Sales"]}[Data]
|
|
355
|
+
in
|
|
356
|
+
Data
|
|
357
|
+
in
|
|
358
|
+
Result
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### File Existence Check
|
|
362
|
+
|
|
363
|
+
```m
|
|
364
|
+
let
|
|
365
|
+
FilePath = "C:\Data\input.csv",
|
|
366
|
+
|
|
367
|
+
// Check if file exists
|
|
368
|
+
FileExists = try File.Contents(FilePath) <> null otherwise false,
|
|
369
|
+
|
|
370
|
+
Result =
|
|
371
|
+
if not FileExists then
|
|
372
|
+
error Error.Record("File Not Found", "The file " & FilePath & " does not exist")
|
|
373
|
+
else
|
|
374
|
+
Csv.Document(File.Contents(FilePath), [Delimiter=","])
|
|
375
|
+
in
|
|
376
|
+
Result
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Debugging Helpers
|
|
382
|
+
|
|
383
|
+
### Add Row Numbers for Tracking
|
|
384
|
+
|
|
385
|
+
```m
|
|
386
|
+
let
|
|
387
|
+
Source = YourSource,
|
|
388
|
+
|
|
389
|
+
// Add index for debugging
|
|
390
|
+
WithIndex = Table.AddIndexColumn(Source, "_RowNumber", 1, 1, Int64.Type),
|
|
391
|
+
|
|
392
|
+
// Now you can reference specific rows in error messages
|
|
393
|
+
Processed = Table.TransformColumns(WithIndex, {
|
|
394
|
+
{"Amount", each try Number.From(_) otherwise
|
|
395
|
+
error Error.Record("Parse Error", "Row " & Text.From([_RowNumber]) & ": Invalid amount")}
|
|
396
|
+
})
|
|
397
|
+
in
|
|
398
|
+
Processed
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### Log Processing Steps
|
|
402
|
+
|
|
403
|
+
```m
|
|
404
|
+
let
|
|
405
|
+
// Create log entries
|
|
406
|
+
Log = (message as text) => Diagnostics.Trace(TraceLevel.Information, message, () => true, true),
|
|
407
|
+
|
|
408
|
+
Source = Log("Starting query") then YourSource,
|
|
409
|
+
Step1 = Log("Filtering rows") then Table.SelectRows(Source, each [Status] = "Active"),
|
|
410
|
+
Step2 = Log("Transforming columns") then Table.TransformColumns(Step1, {{"Amount", each _ * 1.1}}),
|
|
411
|
+
Final = Log("Query complete, rows: " & Text.From(Table.RowCount(Step2))) then Step2
|
|
412
|
+
in
|
|
413
|
+
Final
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Conditional Debug Output
|
|
417
|
+
|
|
418
|
+
```m
|
|
419
|
+
let
|
|
420
|
+
DebugMode = true, // Set to false in production
|
|
421
|
+
|
|
422
|
+
Source = YourSource,
|
|
423
|
+
|
|
424
|
+
// Only add debug columns in debug mode
|
|
425
|
+
WithDebug =
|
|
426
|
+
if DebugMode then
|
|
427
|
+
Table.AddColumn(
|
|
428
|
+
Table.AddColumn(Source, "_DebugRowNum", each [Index]),
|
|
429
|
+
"_DebugValues", each Text.From([Amount]) & "," & [Status]
|
|
430
|
+
)
|
|
431
|
+
else
|
|
432
|
+
Source
|
|
433
|
+
in
|
|
434
|
+
WithDebug
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## Custom Error Creation
|
|
440
|
+
|
|
441
|
+
### Raise Custom Error
|
|
442
|
+
|
|
443
|
+
```m
|
|
444
|
+
let
|
|
445
|
+
Source = YourSource,
|
|
446
|
+
RowCount = Table.RowCount(Source),
|
|
447
|
+
|
|
448
|
+
// Validate before processing
|
|
449
|
+
Validated =
|
|
450
|
+
if RowCount = 0 then
|
|
451
|
+
error Error.Record(
|
|
452
|
+
"EmptySourceError",
|
|
453
|
+
"Source data is empty",
|
|
454
|
+
[RowCount = 0, Source = "YourSource"]
|
|
455
|
+
)
|
|
456
|
+
else if RowCount > 1000000 then
|
|
457
|
+
error Error.Record(
|
|
458
|
+
"DataTooLargeError",
|
|
459
|
+
"Source has too many rows: " & Text.From(RowCount),
|
|
460
|
+
[RowCount = RowCount, MaxAllowed = 1000000]
|
|
461
|
+
)
|
|
462
|
+
else
|
|
463
|
+
Source
|
|
464
|
+
in
|
|
465
|
+
Validated
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Error with Details
|
|
469
|
+
|
|
470
|
+
```m
|
|
471
|
+
let
|
|
472
|
+
ValidateAmount = (amount as any) =>
|
|
473
|
+
let
|
|
474
|
+
NumericValue = try Number.From(amount)
|
|
475
|
+
in
|
|
476
|
+
if NumericValue[HasError] then
|
|
477
|
+
error Error.Record(
|
|
478
|
+
"InvalidAmountError",
|
|
479
|
+
"Could not parse amount: " & Text.From(amount),
|
|
480
|
+
[
|
|
481
|
+
OriginalValue = amount,
|
|
482
|
+
OriginalType = Value.Type(amount),
|
|
483
|
+
ParseError = NumericValue[Error][Message]
|
|
484
|
+
]
|
|
485
|
+
)
|
|
486
|
+
else if NumericValue[Value] < 0 then
|
|
487
|
+
error Error.Record(
|
|
488
|
+
"NegativeAmountError",
|
|
489
|
+
"Amount cannot be negative",
|
|
490
|
+
[Value = NumericValue[Value]]
|
|
491
|
+
)
|
|
492
|
+
else
|
|
493
|
+
NumericValue[Value],
|
|
494
|
+
|
|
495
|
+
Source = YourSource,
|
|
496
|
+
Validated = Table.TransformColumns(Source, {
|
|
497
|
+
{"Amount", ValidateAmount, type number}
|
|
498
|
+
})
|
|
499
|
+
in
|
|
500
|
+
Validated
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
505
|
+
## Best Practices Summary
|
|
506
|
+
|
|
507
|
+
### Error Handling Checklist
|
|
508
|
+
|
|
509
|
+
| Scenario | Pattern |
|
|
510
|
+
|----------|---------|
|
|
511
|
+
| Type conversion | `try Number.From(_) otherwise null` |
|
|
512
|
+
| Null values | Check with `= null` before operation |
|
|
513
|
+
| Division | Check denominator before dividing |
|
|
514
|
+
| Missing columns | `try Record.Field(_, "Column") otherwise` |
|
|
515
|
+
| Connection errors | Wrap source in `try` block |
|
|
516
|
+
| Validation | Add validation column, filter errors |
|
|
517
|
+
| Debugging | Add row numbers, use Diagnostics.Trace |
|
|
518
|
+
|
|
519
|
+
### Don'ts
|
|
520
|
+
|
|
521
|
+
| Bad Practice | Why |
|
|
522
|
+
|--------------|-----|
|
|
523
|
+
| Ignoring errors silently | Hides data quality issues |
|
|
524
|
+
| Catching too broadly | May mask real problems |
|
|
525
|
+
| Not logging errors | Can't diagnose issues |
|
|
526
|
+
| Failing on first error | Loses batch visibility |
|