`powershell
Get-NWXReport -Name "Active Directory Changes" -StartDate "2025-01-01"
` | `
Get-NWXReport -Name "Active Directory Changes" -StartDate "2025-01-01"
` |
+| **Lists** | Use parallel structure for list items. | • Configure the monitoring plan{
"username": "jsmith",
"email": "[jsmith@example.com](mailto:jsmith@example.com)",
"role": "administrator"
} | {
"username": "string",
"email": "string",
"role": "string"
} |
+| **Parameters** | Document all parameters with descriptions and data types. | `userId` (string, required): The unique identifier for the user. | `userId`: User ID. |
+| **Status codes** | Document expected HTTP status codes. | Returns 200 OK on success or 404 Not Found if the user doesn't exist. | Returns a status code. |
+
+## Error Messages
+
+| Rule | Description | Do | Don't |
+|------|-------------|-----|-------|
+| **Error message formatting** | Format error messages clearly with the error code and description. | Error 1001: Invalid credentials. Verify your username and password. | An error occurred. |
+| **Error message tone** | Use a neutral, helpful tone in error messages. | The monitoring plan configuration is invalid. Check the data collector settings. | You entered an invalid configuration. |
+| **Next steps** | Provide actionable next steps in error messages. | The API request failed. Verify your authentication token and try again. | The API request failed. |
\ No newline at end of file
diff --git a/scripts/copy-kb-to-versions.mjs b/scripts/copy-kb-to-versions.mjs
index 57380af103..b761e1bd8a 100644
--- a/scripts/copy-kb-to-versions.mjs
+++ b/scripts/copy-kb-to-versions.mjs
@@ -39,49 +39,6 @@ const PROJECT_ROOT = path.resolve(__dirname, '..');
const LOCKFILE = path.join(PROJECT_ROOT, '.kb-copy.lock');
const ASSET_DIRS = ['0-images', 'images', 'assets', 'media'];
-// ============================================================================
-// Product Migration Control
-// ============================================================================
-
-/**
- * Products that have been migrated to use the new autogenerated KB sidebar.
- *
- * For products in this list:
- * - KB content will be copied to versioned docs folders
- * - Sidebars use autogenerated structure (no generateKBSidebar() call)
- *
- * For products NOT in this list:
- * - KB content will NOT be copied (script skips them)
- * - Sidebars continue using generateKBSidebar() (old method)
- *
- * Add products to this list as their Phase 2 migration PR is merged.
- */
-const MIGRATED_PRODUCTS = [
- // Add product IDs here as they complete migration
- // Example: 'auditor', 'privilegesecure', 'threatprevention'
- '1secure', // Migrated - uses autogenerated sidebar
- 'accessanalyzer', // Already migrated - uses autogenerated sidebar
- 'endpointpolicymanager', // Migrating - sidebar to be updated
- 'identityrecovery', // Migrated - uses autogenerated sidebar
- 'changetracker', // Migrating - sidebar to be updated
- 'accessinformationcenter', // Migrating - sidebar to be updated
- 'accessinformationcenter', // Migrated - uses autogenerated sidebar
- 'activitymonitor', // Migrated - uses autogenerated sidebar
- 'auditor', // Migrating - sidebar to be updated
- 'changetracker', // Migrated - uses autogenerated sidebar
- 'dataclassification', // Migrated - uses autogenerated sidebar
- 'directorymanager', // Migrating - sidebar to be updated
- 'endpointpolicymanager', // Migrated - uses autogenerated sidebar
- 'endpointprotector', // Migrated - uses autogenerated sidebar
- 'passwordpolicyenforcer', // Migrated - uses autogenerated sidebar
- 'passwordreset', // Migrated - uses autogenerated sidebar
- 'privilegesecure', // Migrated - uses autogenerated sidebar
- 'privilegesecurediscovery', // Migrated - uses autogenerated sidebar
- 'recoveryforactivedirectory', // Migrated - uses autogenerated sidebar
- 'threatmanager', // Migrated - uses autogenerated sidebar
- 'threatprevention', // Migrated - scripts page restructured, uses autogenerated sidebar
-];
-
// Build CONFIG dynamically from PRODUCTS
function buildConfig() {
const config = {};
@@ -640,14 +597,6 @@ function main() {
continue;
}
- // Skip products that haven't been migrated yet
- // If MIGRATED_PRODUCTS is empty, skip all products (nothing migrated yet)
- // If MIGRATED_PRODUCTS has items, only process products in the list
- if (MIGRATED_PRODUCTS.length === 0 || !MIGRATED_PRODUCTS.includes(product)) {
- console.log(`\n⏭️ Skipping ${product} (not yet migrated)`);
- continue;
- }
-
console.log(`\n📚 Product: ${product}`);
console.log('-'.repeat(60));
diff --git a/src/components/VersionSwitcher/index.js b/src/components/VersionSwitcher/index.js
new file mode 100644
index 0000000000..84d40a6e2e
--- /dev/null
+++ b/src/components/VersionSwitcher/index.js
@@ -0,0 +1,80 @@
+import React from 'react';
+import { useLocation } from '@docusaurus/router';
+import { PRODUCTS, generateRouteBasePath, urlToVersion } from '@site/src/config/products.js';
+import styles from './styles.module.css';
+
+export default function VersionSwitcher() {
+ const location = useLocation();
+ const pathname = location.pathname;
+
+ // Parse URL to extract product and version
+ const pathParts = pathname.split('/').filter((part) => part);
+
+ // Find the matching product by checking if pathname starts with product path
+ let matchedProduct = null;
+ let currentVersion = null;
+
+ for (const product of PRODUCTS) {
+ // Extract the product path segment (e.g., 'passwordsecure' from 'docs/passwordsecure')
+ const productPathSegment = product.path.split('/').pop();
+
+ // Check if this product's path segment is in the URL
+ const productIndex = pathParts.indexOf(productPathSegment);
+
+ if (productIndex !== -1) {
+ matchedProduct = product;
+
+ // Check if there's a version segment after the product
+ if (pathParts[productIndex + 1]) {
+ const versionFromUrl = pathParts[productIndex + 1];
+ // Check if it looks like a version (e.g., "9_3", "saas", "current")
+ if (/^(v?\d+(_\d+)*|saas|current)$/i.test(versionFromUrl)) {
+ currentVersion = urlToVersion(versionFromUrl);
+ }
+ }
+ break;
+ }
+ }
+
+ // Don't render anything if:
+ // - No product matched
+ // - Product only has one version
+ if (!matchedProduct || matchedProduct.versions.length <= 1) {
+ return null;
+ }
+
+ // Sort versions so latest is always first (on the left)
+ const sortedVersions = [...matchedProduct.versions].sort((a, b) => {
+ if (a.isLatest) return -1;
+ if (b.isLatest) return 1;
+ return 0;
+ });
+
+ return (
+