mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-10-06 15:16:56 +01:00
Improve partner onboarding and documentation navigation (#174)
Lead with a complete plugin quick start and add task-oriented usage, troubleshooting, customization, and contribution guides. Preserve the broader plugin framing, correct conflicting contract guidance, support Agents folder reviews, and align repository validation. Convert existing sample references to clickable links without changing knowledge rules. Co-authored-by: Jesper Schulz-Wedde <jesper.schulzwedde@microsoft.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
a21edfec46
commit
2b5550c346
276 changed files with 1287 additions and 756 deletions
|
|
@ -17,10 +17,10 @@ Error method trace telemetry includes the AL error string only when the first `E
|
|||
|
||||
Declare the complete message as a `Label` or `TextConst` and pass it directly to `Error`, followed by substitution values. The client receives the formatted message while telemetry retains the static message template without using the dynamic values as its message. Independently review whether each substitution value is appropriate to show to the current user.
|
||||
|
||||
See sample: `avoid-strsubstno-prebuild-before-error.good.al`.
|
||||
See sample: [`avoid-strsubstno-prebuild-before-error.good.al`](avoid-strsubstno-prebuild-before-error.good.al).
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
`Error(StrSubstNo(CustomerInvalidErr, Customer."No."))` and `Error(HeaderErr + DetailErr)` both make the first argument dynamic. They reduce error telemetry quality; they do not cause that composed string to be logged verbatim as the telemetry message.
|
||||
|
||||
See sample: `avoid-strsubstno-prebuild-before-error.bad.al`.
|
||||
See sample: [`avoid-strsubstno-prebuild-before-error.bad.al`](avoid-strsubstno-prebuild-before-error.bad.al).
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ application-area: [all]
|
|||
|
||||
Set `DataClassification` to the value that matches the data the field actually stores. A `Customer."E-Mail"`-style field is `CustomerContent` (data belonging to the tenant's customers); a personal identifier such as an employee number or user ID is `EndUserIdentifiableInformation` or `EndUserPseudonymousIdentifiers` depending on whether it is directly identifying. A field that identifies an organization rather than a person — a company registration or VAT registration number — is `OrganizationIdentifiableInformation`, and a financial account identifier such as a bank account number or IBAN is `AccountData`. Choose the classification at field definition time — fixing it later is a schema change.
|
||||
|
||||
See sample: `data-classification-required-on-pii-fields.good.al`.
|
||||
See sample: [`data-classification-required-on-pii-fields.good.al`](data-classification-required-on-pii-fields.good.al).
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Declaring a field that stores PII with `DataClassification = SystemMetadata` to silence the compiler warning. The field compiles but the platform now treats customer data as system metadata in telemetry, GDPR exports and admin reports.
|
||||
|
||||
See sample: `data-classification-required-on-pii-fields.bad.al`.
|
||||
See sample: [`data-classification-required-on-pii-fields.bad.al`](data-classification-required-on-pii-fields.bad.al).
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ Runtime 3.0 (BC 14) provides `ErrorInfo.Message`, `DataClassification`, and `Err
|
|||
|
||||
Keep `Message` stable and classify its actual content. Choose `ErrorType` for client usability, not as a telemetry privacy boundary. On BC 19 and later, put only support-safe technical context in `DetailedMessage`, because a user can copy it from the dialog. The samples use only members available at the BC 14 article floor.
|
||||
|
||||
See sample: `errorinfo-telemetry-classification-and-errortype.good.al`.
|
||||
See sample: [`errorinfo-telemetry-classification-and-errortype.good.al`](errorinfo-telemetry-classification-and-errortype.good.al).
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Marking a dynamic customer-bearing `Message` as `SystemMetadata`, or assuming `ErrorType::Internal` keeps it out of telemetry. On BC 19 and later, the same anti-pattern includes placing secrets or personal data in `DetailedMessage` because it is not the primary dialog text.
|
||||
|
||||
See sample: `errorinfo-telemetry-classification-and-errortype.bad.al`.
|
||||
See sample: [`errorinfo-telemetry-classification-and-errortype.bad.al`](errorinfo-telemetry-classification-and-errortype.bad.al).
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ application-area: [all]
|
|||
|
||||
Pass only non-personal context through `CustomDimensions` — feature names, status enums, counts, error codes, durations. For uptake or usage signals that do not need per-call context, prefer the parameterless overload of `LogUptake`/`LogUsage` over a `CustomDimensions` dictionary that risks accreting PII over time.
|
||||
|
||||
See sample: `featuretelemetry-customdimensions-no-pii.good.al`.
|
||||
See sample: [`featuretelemetry-customdimensions-no-pii.good.al`](featuretelemetry-customdimensions-no-pii.good.al).
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
`CustomDimensions.Add('EmployeeNo', ExpenseHeader."Employee No.")` followed by `FeatureTelemetry.LogUsage(...)` — the employee number is a pseudonymous user identifier (EUPI) and is now in telemetry. Same pattern with `'UserName'`, `'CustomerEmail'`, `'AttachmentName'` etc.
|
||||
|
||||
See sample: `featuretelemetry-customdimensions-no-pii.bad.al`.
|
||||
See sample: [`featuretelemetry-customdimensions-no-pii.bad.al`](featuretelemetry-customdimensions-no-pii.bad.al).
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ application-area: [all]
|
|||
|
||||
Review the dedicated error arguments as telemetry payload. Capture `GetLastErrorText(true)` when scrubbed platform error text is sufficient, and pass `GetLastErrorCallStack()` only as a call stack. Keep custom dimensions non-personal too.
|
||||
|
||||
See sample: `featuretelemetry-logerror-implicit-errortext.good.al`.
|
||||
See sample: [`featuretelemetry-logerror-implicit-errortext.good.al`](featuretelemetry-logerror-implicit-errortext.good.al).
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Approving a `LogError` call because its explicit dictionary contains only safe values while it passes unsanitized `GetLastErrorText()` or arbitrary context through `ErrorText` or `ErrorCallStack`. Those arguments become telemetry dimensions outside the dictionary.
|
||||
|
||||
See sample: `featuretelemetry-logerror-implicit-errortext.bad.al`.
|
||||
See sample: [`featuretelemetry-logerror-implicit-errortext.bad.al`](featuretelemetry-logerror-implicit-errortext.bad.al).
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ application-area: [all]
|
|||
|
||||
Do not declare `DataClassification` on `FieldClass = FlowField` or `FieldClass = FlowFilter` fields — the inherited `SystemMetadata` is correct and the property is redundant. If a FlowField exposes sensitive data, ensure the underlying source field has the right `DataClassification`; that is where the platform reads classification from for GDPR and telemetry purposes.
|
||||
|
||||
See sample: `flowfield-flowfilter-classification-systemmetadata.good.al`.
|
||||
See sample: [`flowfield-flowfilter-classification-systemmetadata.good.al`](flowfield-flowfilter-classification-systemmetadata.good.al).
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ Parameterless `GetLastErrorText()` can contain customer content such as field va
|
|||
|
||||
Use a generic label when the user does not need the underlying detail. If showing unsanitized detail is appropriate, put `%1` in a label and pass parameterless `GetLastErrorText()` as a separate argument. This preserves a useful static telemetry message while keeping the dynamic value out of the telemetry message field.
|
||||
|
||||
See sample: `getlasterrortext-customer-content-in-errors.good.al`.
|
||||
See sample: [`getlasterrortext-customer-content-in-errors.good.al`](getlasterrortext-customer-content-in-errors.good.al).
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
`Error(StrSubstNo(AttachmentFailedErr, GetLastErrorText()))` or `Error(AttachmentPrefixErr + GetLastErrorText())`. Both lose the static first argument and trigger AA0231; neither causes the composed text to be logged verbatim as the Error telemetry message.
|
||||
|
||||
See sample: `getlasterrortext-customer-content-in-errors.bad.al`.
|
||||
See sample: [`getlasterrortext-customer-content-in-errors.bad.al`](getlasterrortext-customer-content-in-errors.bad.al).
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ Keep the telemetry message a static, non-personal string ("Customer record proce
|
|||
|
||||
If a pseudonymous identifier (record `No.`, primary key value) genuinely belongs in the diagnostic, prefer attaching it through a custom dimension and set the call's `DataClassification` to match the data actually shipped — `EndUserPseudonymousIdentifiers` for pseudonymous IDs, `CustomerContent` for content-bearing telemetry. Changing the `DataClassification` alone does **not** make embedding a customer name into the message string acceptable; the data still ships in the message, and downstream consumers still see the literal string.
|
||||
|
||||
See sample: `no-pii-in-telemetry-message-string.good.al`.
|
||||
See sample: [`no-pii-in-telemetry-message-string.good.al`](no-pii-in-telemetry-message-string.good.al).
|
||||
|
||||
## Related
|
||||
|
||||
|
|
@ -30,4 +30,4 @@ See sample: `no-pii-in-telemetry-message-string.good.al`.
|
|||
|
||||
`Session.LogMessage('0000', StrSubstNo('Processed %1', Customer.Name), ...)` — the customer name is in telemetry the moment the line runs. Detection signal: a `StrSubstNo` whose result is the second argument of `Session.LogMessage`. The same shape with `FileName`, `EmployeeCode`, or any record field is the same problem.
|
||||
|
||||
See sample: `no-pii-in-telemetry-message-string.bad.al`.
|
||||
See sample: [`no-pii-in-telemetry-message-string.bad.al`](no-pii-in-telemetry-message-string.bad.al).
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ Business Central's `Codeunit "Privacy Notice"` creates notices and records per-i
|
|||
|
||||
Register the custom notice with `CreatePrivacyNotice` during setup or through `OnRegisterPrivacyNotices`. Before sending data, call `ConfirmPrivacyNoticeApproval(<custom id>)` outside a write transaction, or check `GetPrivacyNoticeApprovalState(<custom id>)` when the flow must not show UI. No path should issue the request without approval.
|
||||
|
||||
See sample: `privacy-notice-consent-for-external-data-transfer.good.al`.
|
||||
See sample: [`privacy-notice-consent-for-external-data-transfer.good.al`](privacy-notice-consent-for-external-data-transfer.good.al).
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
A custom integration that posts data without checking its own notice, or that gates the call with a built-in ID such as the Exchange privacy notice ID. Consent for one service does not authorize another.
|
||||
|
||||
See sample: `privacy-notice-consent-for-external-data-transfer.bad.al`.
|
||||
See sample: [`privacy-notice-consent-for-external-data-transfer.bad.al`](privacy-notice-consent-for-external-data-transfer.bad.al).
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ The current extension point is `Codeunit "Privacy Notice"`. Extensions can subsc
|
|||
|
||||
Choose a stable ID owned by the extension. Register it through `OnRegisterPrivacyNotices`, or call `PrivacyNotice.CreatePrivacyNotice` during an intentional setup or upgrade path. Use that same ID for consent checks described in `privacy-notice-consent-for-external-data-transfer.md`.
|
||||
|
||||
See sample: `register-integration-in-privacy-notice-registrations.good.al`.
|
||||
See sample: [`register-integration-in-privacy-notice-registrations.good.al`](register-integration-in-privacy-notice-registrations.good.al).
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ application-area: [all]
|
|||
|
||||
Use the overload that takes `Verbosity`, `DataClassification`, and `TelemetryScope`. For payload-free operational telemetry that does not embed customer data, `DataClassification::SystemMetadata` is the right value. Choose `TelemetryScope::ExtensionPublisher` for telemetry meant for the publishing partner only; `TelemetryScope::All` also forwards to the customer's tenant telemetry.
|
||||
|
||||
See sample: `session-logmessage-requires-dataclassification.good.al`.
|
||||
See sample: [`session-logmessage-requires-dataclassification.good.al`](session-logmessage-requires-dataclassification.good.al).
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Calling `Session.LogMessage('0003', 'Operation completed', Verbosity::Normal)` — the overload omits `DataClassification` and leaves the platform without the information needed to classify the entry. Detection signal: a `Session.LogMessage` call whose argument list ends at `Verbosity`.
|
||||
|
||||
See sample: `session-logmessage-requires-dataclassification.bad.al`.
|
||||
See sample: [`session-logmessage-requires-dataclassification.bad.al`](session-logmessage-requires-dataclassification.bad.al).
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ A valid table-level `DataClassification` is the effective default for the Normal
|
|||
|
||||
Use a table-level classification when it accurately describes the table's fields, and add a field-level classification only where a field stores a different kind of data. Do not flag a Normal field solely because it omits an explicit property when its own table supplies a valid default; verify whether the inherited value matches the field's data instead. A `tableextension` has no default to inherit, so require an explicit `DataClassification` on every Normal field it adds.
|
||||
|
||||
See sample: `table-level-data-classification-cascades.good.al`.
|
||||
See sample: [`table-level-data-classification-cascades.good.al`](table-level-data-classification-cascades.good.al).
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue